-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b4719cc
commit 12dc690
Showing
7 changed files
with
121 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
ACCESS_KEY_ID="" | ||
SECRET_ACCESS_KEY="" | ||
ACCOUNT_ID="" | ||
BUCKET_NAME="" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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). |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>; | ||
}; |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.