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 @@ + + + + + Signed Resumable Upload Supabase + UppyJS + + + + + + +
+ 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');