Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
cadensstudio committed Sep 14, 2024
1 parent b4719cc commit 12dc690
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 52 deletions.
4 changes: 4 additions & 0 deletions .dev.vars.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ACCESS_KEY_ID=""
SECRET_ACCESS_KEY=""
ACCOUNT_ID=""
BUCKET_NAME=""
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# pURL

## Overview

PURL is a Cloudflare Worker that generates presigned URLs to securely upload files to Cloudflare R2 using the S3 API. Presigned URLs allow clients to upload files directly to R2 without requiring full access to your Cloudflare R2 credentials. This worker generates these URLs on the fly, ensuring that the upload process is secure and controlled.

## Key Features

- Secure Uploads: Generate presigned URLs that authorize specific PUT operations to R2 buckets, allowing for secure and temporary access to upload files.
- Flexible Configuration: Easily configure the Worker to handle uploads to specific folders and files within your R2 bucket.
- Expiry Control: Set expiration times for the presigned URLs, ensuring that the URLs can only be used within a limited time window.
- R2 Integration: Directly integrates with Cloudflare R2 using the S3-compatible API, making it straightforward to manage object storage in R2.

## Local Development Setup

1. Ensure you have configured the necessary environment variables outlined in `.dev.vars.example`. The `ACCESS_KEY_ID` and `SECRET_ACCESS_KEY` variables can be obtained from your Cloudflare R2 dashboard.

```bash
cp .dev.vars.example .dev.vars
```
2. Start the development server.

```bash
npm run dev
```

3. Send a GET request to the running worker.

```bash
curl http://localhost:8787/image/example.png
```
4. Send a PUT request to the presigned URL to upload the image file.

```bash
curl -X PUT "<presignedURL>" -H "Content-Type: image/png" --upload-file "/path/to/example.png"
```
## Configuration Options

- **URL Expiration**: This Worker sets an expiry time of 3600 seconds (1 hour). This value can be anything between 1 second and 604,800 seconds (7 days).
```ts
r2Url.searchParams.set('X-Amz-Expires', '3600'); // URL valid for 1 hour
```

- **URL Parsing**: The Worker extracts the folder and file names from the incoming request URL to ensure that a valid object path is provided.
```ts
const url = new URL(req.url);
const pathname = url.pathname;
// Extract the path to the file from the URL (expected format: url/path/to/upload/file)
const pathSegments = pathname.split('/').filter(Boolean); // Remove empty segments
if (pathSegments.length < 2) {
return new Response('Invalid URL format', { status: 400 });
}
```
- For additional configuration options, see [Cloudflare's Docs](https://developers.cloudflare.com/r2/api/s3/presigned-urls/).
## License
This project is licensed under the [MIT License](LICENSE).
17 changes: 13 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@
"typescript": "^5.5.2",
"vitest": "1.5.0",
"wrangler": "^3.60.3"
},
"dependencies": {
"aws4fetch": "^1.0.20"
}
}
56 changes: 41 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,44 @@
/**
* Welcome to Cloudflare Workers! This is your first worker.
*
* - Run `npm run dev` in your terminal to start a development server
* - Open a browser tab at http://localhost:8787/ to see your worker in action
* - Run `npm run deploy` to publish your worker
*
* Bind resources to your worker in `wrangler.toml`. After adding bindings, a type definition for the
* `Env` object can be regenerated with `npm run cf-typegen`.
*
* Learn more at https://developers.cloudflare.com/workers/
*/
import { AwsClient } from 'aws4fetch';

interface Env {
ACCESS_KEY_ID: string;
SECRET_ACCESS_KEY: string;
ACCOUNT_ID: string;
BUCKET_NAME: string;
}

export default {
async fetch(request, env, ctx): Promise<Response> {
return new Response('Hello World!');
async fetch(req: Request, env: Env): Promise<Response> {
const r2 = new AwsClient({
accessKeyId: env.ACCESS_KEY_ID,
secretAccessKey: env.SECRET_ACCESS_KEY,
});

const url = new URL(req.url);
const pathname = url.pathname;

// Extract the path to the file from the URL (expected format: url/path/to/upload/file)
const pathSegments = pathname.split('/').filter(Boolean); // Remove empty segments
if (pathSegments.length < 2) {
return new Response('Invalid URL format', { status: 400 });
}

const folderName = pathSegments.slice(0, -1).join('/');
const filename = pathSegments[pathSegments.length - 1];

if (!filename || !folderName) {
return new Response('Missing filename or folderName', { status: 400 });
}

const accountId = env.ACCOUNT_ID;
const bucketName = env.BUCKET_NAME;

const r2Url = new URL(`https://${bucketName}.${accountId}.r2.cloudflarestorage.com/${folderName}/${filename}`);

r2Url.searchParams.set('X-Amz-Expires', '3600'); // URL valid for 1 hour

const signedUrl = await r2.sign(new Request(r2Url, { method: 'PUT' }), { aws: { signQuery: true } });

return new Response(JSON.stringify({ url: signedUrl.url }), { status: 200 });
},
} satisfies ExportedHandler<Env>;
};
25 changes: 0 additions & 25 deletions test/index.spec.ts

This file was deleted.

8 changes: 0 additions & 8 deletions test/tsconfig.json

This file was deleted.

0 comments on commit 12dc690

Please sign in to comment.