diff --git a/apps/docs/content/guides/storage/uploads/resumable-uploads.mdx b/apps/docs/content/guides/storage/uploads/resumable-uploads.mdx index 2a1db32286c..4a9619a3847 100644 --- a/apps/docs/content/guides/storage/uploads/resumable-uploads.mdx +++ b/apps/docs/content/guides/storage/uploads/resumable-uploads.mdx @@ -302,6 +302,12 @@ Uppy has integrations with different frameworks: - [Vue](https://uppy.io/docs/vue/) - [Angular](https://uppy.io/docs/angular/) +### Presigned uploads + +Resumable uploads also supports using signed upload tokens to created time-limited URLs that you can share to your users by invoking the `createSignedUploadUrl` method on the SDK and including the returned token in the `x-signature` header of the resumable upload. + +See this [full example using Uppy with signed URLs](https://github.com/supabase/supabase/tree/master/examples/storage/resumable-upload-signed-uppy) for more context. + ## Overwriting files When uploading a file to a path that already exists, the default behavior is to return a `400 Asset Already Exists` error. diff --git a/examples/storage/resumable-upload-signed-uppy/README.md b/examples/storage/resumable-upload-signed-uppy/README.md new file mode 100644 index 00000000000..a762be9e617 --- /dev/null +++ b/examples/storage/resumable-upload-signed-uppy/README.md @@ -0,0 +1,23 @@ +## Resumable Uploads with Supabase Storage and Uppy + +This example shows how to use signed urls from [Supabase Storage](https://supabase.io/docs/reference/javascript/storage) with [Uppy](https://uppy.io/) to upload files to Supabase Storage using the TUS protocol (signed resumable uploads). + +This works by calling `createSignedUploadUrl()` to get a token for each file, and passing that token via the `x-signature` header when uploading the files + +### Running the example + +- Start local supabase project `supabase start` +- Open the index.html file and set `SUPABASE_PUBLISHABLE_KEY` to the value output when starting the supabase cli +- Serve the index.html file locally (e.g. with Python Simple HTTP Server or http-server npm package) and start uploading: + +```bash +# python http server +python3 -m http.server + +# npm http-server +npx http-server +``` + +### How it works + +In index.html the `uppy.on('file-added')` hook calls the [create-upload-token](supabase/functions/create-upload-token/index.ts) function which creates a token for each added file and attaches it to that file's header config as `x-signature`. diff --git a/examples/storage/resumable-upload-signed-uppy/index.html b/examples/storage/resumable-upload-signed-uppy/index.html new file mode 100644 index 00000000000..a1bba5f1b66 --- /dev/null +++ b/examples/storage/resumable-upload-signed-uppy/index.html @@ -0,0 +1,113 @@ + + +
+ +
+
+ Read the docs.
+
+
+
+
diff --git a/examples/storage/resumable-upload-signed-uppy/supabase-logo-wordmark--dark.png b/examples/storage/resumable-upload-signed-uppy/supabase-logo-wordmark--dark.png
new file mode 100644
index 00000000000..dccffd4247d
Binary files /dev/null and b/examples/storage/resumable-upload-signed-uppy/supabase-logo-wordmark--dark.png differ
diff --git a/examples/storage/resumable-upload-signed-uppy/supabase/.gitignore b/examples/storage/resumable-upload-signed-uppy/supabase/.gitignore
new file mode 100644
index 00000000000..a3ad88055b7
--- /dev/null
+++ b/examples/storage/resumable-upload-signed-uppy/supabase/.gitignore
@@ -0,0 +1,4 @@
+# Supabase
+.branches
+.temp
+.env
diff --git a/examples/storage/resumable-upload-signed-uppy/supabase/config.toml b/examples/storage/resumable-upload-signed-uppy/supabase/config.toml
new file mode 100644
index 00000000000..c54410aa307
--- /dev/null
+++ b/examples/storage/resumable-upload-signed-uppy/supabase/config.toml
@@ -0,0 +1,32 @@
+# A string used to distinguish different Supabase projects on the same host. Defaults to the
+# working directory name when running `supabase init`.
+project_id = "resumable-upload-uppy"
+
+[api]
+# Disable data API since we are not using the PostgREST client in this example.
+enabled = false
+
+[storage]
+# The maximum file size allowed for all buckets in the project.
+file_size_limit = "50MiB"
+
+[storage.image_transformation]
+enabled = false
+
+[storage.buckets.uploads]
+public = true
+# file_size_limit = "50MiB"
+# allowed_mime_types = ["image/png", "image/jpeg"]
+# Uncomment to specify a local directory to upload objects to the bucket.
+# objects_path = "./buckets/uploads"
+
+[functions.create-upload-token]
+enabled = true
+verify_jwt = true
+import_map = "./functions/create-upload-token/deno.json"
+# Uncomment to specify a custom file path to the entrypoint.
+# Supported file extensions are: .ts, .js, .mjs, .jsx, .tsx
+entrypoint = "./functions/create-upload-token/index.ts"
+# Specifies static files to be bundled with the function. Supports glob patterns.
+# For example, if you want to serve static HTML pages in your function:
+# static_files = [ "./functions/create-upload-token/*.html" ]
diff --git a/examples/storage/resumable-upload-signed-uppy/supabase/functions/create-upload-token/.npmrc b/examples/storage/resumable-upload-signed-uppy/supabase/functions/create-upload-token/.npmrc
new file mode 100644
index 00000000000..48c63886380
--- /dev/null
+++ b/examples/storage/resumable-upload-signed-uppy/supabase/functions/create-upload-token/.npmrc
@@ -0,0 +1,3 @@
+# Configuration for private npm package dependencies
+# For more information on using private registries with Edge Functions, see:
+# https://supabase.com/docs/guides/functions/import-maps#importing-from-private-registries
diff --git a/examples/storage/resumable-upload-signed-uppy/supabase/functions/create-upload-token/deno.json b/examples/storage/resumable-upload-signed-uppy/supabase/functions/create-upload-token/deno.json
new file mode 100644
index 00000000000..f6ca8454c56
--- /dev/null
+++ b/examples/storage/resumable-upload-signed-uppy/supabase/functions/create-upload-token/deno.json
@@ -0,0 +1,3 @@
+{
+ "imports": {}
+}
diff --git a/examples/storage/resumable-upload-signed-uppy/supabase/functions/create-upload-token/index.ts b/examples/storage/resumable-upload-signed-uppy/supabase/functions/create-upload-token/index.ts
new file mode 100644
index 00000000000..ff8cdf0a674
--- /dev/null
+++ b/examples/storage/resumable-upload-signed-uppy/supabase/functions/create-upload-token/index.ts
@@ -0,0 +1,29 @@
+import 'jsr:@supabase/functions-js/edge-runtime.d.ts'
+import { createClient } from 'jsr:@supabase/supabase-js'
+
+Deno.serve(async (req) => {
+ const SUPABASE_URL = Deno.env.get('SUPABASE_URL') ?? ''
+ const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
+
+ const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY)
+
+ try {
+ const { filename } = await req.json()
+ if (!filename) {
+ return new Response('Missing filename', { status: 400 })
+ }
+
+ const { data, error } = await supabase.storage.from('uploads').createSignedUploadUrl(filename)
+
+ if (error) {
+ return new Response(error.message, { status: 500 })
+ }
+
+ return new Response(JSON.stringify({ token: data.token }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ })
+ } catch (error) {
+ return new Response((error as Error).message, { status: 500 })
+ }
+})
diff --git a/examples/storage/resumable-upload-signed-uppy/supabase/migrations/20241128121139_storage_rls.sql b/examples/storage/resumable-upload-signed-uppy/supabase/migrations/20241128121139_storage_rls.sql
new file mode 100644
index 00000000000..7769adecadb
--- /dev/null
+++ b/examples/storage/resumable-upload-signed-uppy/supabase/migrations/20241128121139_storage_rls.sql
@@ -0,0 +1 @@
+CREATE POLICY "allow uploads" ON storage.objects FOR INSERT TO public WITH CHECK (bucket_id = 'uploads');