diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index aa792a59a5a..dd368b2c605 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -1338,6 +1338,10 @@ export const storage: NavMenuConstant = { name: 'Security', url: undefined, items: [ + { + name: 'Ownership', + url: '/guides/storage/security/ownership', + }, { name: 'Access Control', url: '/guides/storage/security/access-control', @@ -1356,6 +1360,10 @@ export const storage: NavMenuConstant = { name: 'Resumable Uploads', url: '/guides/storage/uploads/resumable-uploads', }, + { + name: 'S3 Uploads', + url: '/guides/storage/uploads/s3-uploads', + }, { name: 'Limits', url: '/guides/storage/uploads/file-limits' }, ], }, @@ -1370,6 +1378,22 @@ export const storage: NavMenuConstant = { }, ], }, + { + name: 'Management', + url: undefined, + items: [ + { name: 'Copy / Move Objects', url: '/guides/storage/management/copy-move-objects' }, + { name: 'Delete Objects', url: '/guides/storage/management/delete-objects' }, + ], + }, + { + name: 'S3', + url: undefined, + items: [ + { name: 'Authentication', url: '/guides/storage/s3/authentication' }, + { name: 'API Compatibility', url: '/guides/storage/s3/compatibility' }, + ], + }, { name: 'CDN', url: undefined, @@ -1396,6 +1420,7 @@ export const storage: NavMenuConstant = { name: 'Helper Functions', url: '/guides/storage/schema/helper-functions', }, + { name: 'Custom Roles', url: '/guides/storage/schema/custom-roles' }, ], }, { diff --git a/apps/docs/content/guides/getting-started/features.mdx b/apps/docs/content/guides/getting-started/features.mdx index d9896c29346..cfb38b830bd 100644 --- a/apps/docs/content/guides/getting-started/features.mdx +++ b/apps/docs/content/guides/getting-started/features.mdx @@ -142,6 +142,10 @@ Transform images on the fly. [Docs](/docs/guides/storage/serving/image-transform Upload large files using resumable uploads. [Docs](/docs/guides/storage/uploads/resumable-uploads). +### S3 compatibility + +Interact with Storage from tool which supports with the S3 protocol. [Docs](/docs/guides/storage/s3/compatibility). + ## Edge Functions ### Deno Edge Functions @@ -218,6 +222,7 @@ In addition to the Beta requirements, features in GA are covered by the [uptime | Storage | Smart CDN | `GA` | 🚧 [Cloudflare](https://www.cloudflare.com) | | Storage | Image Transformations | `GA` | ✅ | | Storage | Resumable Uploads | `GA` | ✅ | +| Storage | S3 compatibility | `public alpha` | ✅ | | Edge Functions | | `beta` | ✅ | | Edge Functions | Regional Invocations | `beta` | ✅ | | Edge Functions | NPM compatibility | `beta` | ✅ | diff --git a/apps/docs/content/guides/storage/debugging/error-codes.mdx b/apps/docs/content/guides/storage/debugging/error-codes.mdx index a190912b3cd..bb22f83afae 100644 --- a/apps/docs/content/guides/storage/debugging/error-codes.mdx +++ b/apps/docs/content/guides/storage/debugging/error-codes.mdx @@ -2,12 +2,79 @@ id: 'storage-errors-codes' title: 'Error Codes' description: 'Supabase Error Codes' +subtitle: 'Learn about the Storage error codes and how to resolve them' sidebar_label: 'Debugging' --- +## Storage Error Codes + + + We are transitioning to a new error code system. For backwards compatibility you'll still be able + to see the old error codes + + +Error codes in Storage are returned as part of the response body. They are useful for debugging and understanding what went wrong with your request. +The error codes are returned in the following format: + +```json +{ + "code": "error_code", + "message": "error_message" +} +``` + +Here is the full list of error codes and their descriptions: + +| ErrorCode | Description | StatusCode | Resolution | +| ------------------------- | --------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| NoSuchBucket | The specified bucket does not exist. | 404 | Verify the bucket name and ensure it exists in the system, if it exists you don't have permissions to access it. | +| NoSuchKey | The specified key does not exist. | 404 | Check the key name and ensure it exists in the specified bucket, if it exists you don't have permissions to access it. | +| NoSuchUpload | The specified upload does not exist. | 404 | The uploadID provided might not exists or the Upload was previously aborted | +| InvalidJWT | The provided JWT (JSON Web Token) is invalid. | 401 | The JWT provided might be expired or malformed, provide a valid JWT | +| InvalidRequest | The request is not properly formed. | 400 | Review the request parameters and structure, ensure they meet the API's requirements, the error message will provide more details | +| TenantNotFound | The specified tenant does not exist. | 404 | The Storage service had issues while provisioning, please [Contact Support](https://supabase.com/dashboard/support/new) | +| EntityTooLarge | The entity being uploaded is too large. | 413 | Verify the max-file-limit is equal or higher to the resource you are trying to upload, you can change this value on the [Project Setting](https://supabase.com/dashboard/project/_/settings/storage) | +| InternalError | An internal server error occurred. | 500 | Investigate server logs to identify the cause of the internal error. If you think it's a Storage error please [Contact Support](https://supabase.com/dashboard/support/new) | +| ResourceAlreadyExists | The specified resource already exists. | 409 | Use a different name or identifier for the resource to avoid conflicts. Use `x-upsert:true` header to overwrite the resource. | +| InvalidBucketName | The specified bucket name is invalid. | 400 | Ensure the bucket name follows the naming conventions and does not contain invalid characters. | +| InvalidKey | The specified key is invalid. | 400 | Verify the key name and ensure it follows the naming conventions. | +| InvalidRange | The specified range is not valid. | 416 | Make sure that range provided is within the file size boundary and follow the [HTTP Range spec](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range) | +| InvalidMimeType | The specified MIME type is not valid. | 400 | Provide a valid MIME type, ensure using the standard MIME type format | +| InvalidUploadId | The specified upload ID is invalid. | 400 | The upload ID provided is invalid or missing. Make sure to provide a active uploadID | +| KeyAlreadyExists | The specified key already exists. | 409 | Use a different key name to avoid conflicts with existing keys. Use `x-upsert:true` header to overwrite the resource. | +| BucketAlreadyExists | The specified bucket already exists. | 409 | Choose a unique name for the bucket that does not conflict with existing buckets. | +| DatabaseTimeout | Timeout occurred while accessing the database. | 504 | Investigate database performance and increase the default pool size. If this error still occurs please upgrade your instance | +| InvalidSignature | The signature provided does not match the calculated signature. | 403 | Check that you are providing the correct signature format, for more information refer to [SignatureV4](https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html) | +| SignatureDoesNotMatch | The request signature does not match the calculated signature. | 403 | Check your credentials, access key id / access secret key / region that are all correct, refer to [S3 Authentication](/docs/guides/storage/s3/authentication). | +| AccessDenied | Access to the specified resource is denied. | 403 | Check that you have the correct RLS policy to allow access to this resource | +| ResourceLocked | The specified resource is locked. | 423 | This resource cannot be altered while there is a lock. Wait and try the request again | +| DatabaseError | An error occurred while accessing the database. | 500 | Investigate database logs and system configuration to identify and address the database error. | +| MissingContentLength | The Content-Length header is missing. | 411 | Ensure the Content-Length header is included in the request with the correct value. | +| MissingParameter | A required parameter is missing in the request. | 400 | Provide all required parameters in the request to fulfill the API's requirements. The message field will contain more details | +| InvalidUploadSignature | The provided upload signature is invalid. | 403 | The MultiPartUpload record was altered while the upload was ongoing, the signature do not match. Do not alter the upload record | +| LockTimeout | Timeout occurred while waiting for a lock. | 423 | The lock couldn't be acquired within the specified timeout. Wait and try the request again | +| S3Error | An error occurred related to Amazon S3. | - | Refer to Amazon S3 documentation or [Contact Support](https://supabase.com/dashboard/support/new) for assistance with resolving the S3 error. | +| S3InvalidAccessKeyId | The provided AWS access key ID is invalid. | 403 | Verify the AWS access key ID provided and ensure it is correct and active. | +| S3MaximumCredentialsLimit | The maximum number of credentials has been reached. | 400 | The maximum limit of credentials is reached. | +| InvalidChecksum | The checksum of the entity does not match. | 400 | Recalculate the checksum of the entity and ensure it matches the one provided in the request. | +| MissingPart | A part of the entity is missing. | 400 | Ensure all parts of the entity are included in the request before completing the operation. | +| SlowDown | The request rate is too high and has been throttled. | 503 | Reduce the request rate or implement exponential backoff and retry mechanisms to handle throttling. | + +## Legacy Error Codes + +As we are transitioning to a new error code system, you might still see the following error format: + +```json +{ + "httpStatusCode": 400, + "code": "error_code", + "message": "error_message" +} +``` + Here's a list of the most common error codes and their potential resolutions: -## 404 not_found +### 404 not_found Indicates that the resource is not found or you don't have the correct permission to access it **Resolution:** @@ -16,14 +83,14 @@ Indicates that the resource is not found or you don't have the correct permissio - Ensure you include the user `Authorization` header - Verify the object exists -## 409 already_exists +### 409 already_exists Indicates that the resource already exists. **Resolution:** - Use the `upsert` functionality in order to overwrite the file. Find out more [here](/docs/guides/storage/uploads/standard-uploads#overwriting-files). -## 403 unauthorized +### 403 unauthorized You don't have permission to action this request **Resolution:** @@ -31,7 +98,7 @@ You don't have permission to action this request - Add RLS policy to grant permission. See our [Access Control docs](/docs/guides/storage/uploads/access-control) for more information. - Ensure you include the user `Authorization` header -## 429 too many requests +### 429 too many requests This problem typically arises when a large number of clients are concurrently interacting with the Storage service, and the pooler has reached its `max_clients` limit. @@ -40,7 +107,7 @@ This problem typically arises when a large number of clients are concurrently in - Increase the max_clients limits of the pooler. - Upgrade to a bigger project compute instance [here](https://supabase.com/dashboard/project/_/settings/addons). -## 544 database_timeout +### 544 database_timeout This problem arises when a high number of clients are concurrently using the Storage service, and Postgres doesn't have enough available connections to efficiently handle requests to Storage. @@ -49,7 +116,7 @@ This problem arises when a high number of clients are concurrently using the Sto - Increase the pool_size limits of the pooler. - Upgrade to a bigger project compute instance [here](https://supabase.com/dashboard/project/_/settings/addons). -## 500 internal_server_error +### 500 internal_server_error This issue occurs where there is a unhandled error. **Resolution:** diff --git a/apps/docs/content/guides/storage/management/copy-move-objects.mdx b/apps/docs/content/guides/storage/management/copy-move-objects.mdx new file mode 100644 index 00000000000..26bf21eab08 --- /dev/null +++ b/apps/docs/content/guides/storage/management/copy-move-objects.mdx @@ -0,0 +1,79 @@ +--- +id: 'storage-management' +title: 'Copy Objects' +description: 'Learn how to copy and move objects' +subtitle: 'Learn how to copy and move objects' +sidebar_label: 'Copy / Move Objects' +--- + +## Copy objects + +You can copy objects between buckets or within the same bucket. Currently only objects up to 5 GB can be copied using the API. + +When making a copy of an object, the owner of the new object will be the user who initiated the copy operation. + +### Copying objects within the same bucket + +To copy an object within the same bucket, use the `copy` method. + +```javascript +await supabase.storage.from('avatars').copy('public/avatar1.png', 'private/avatar2.png') +``` + +### Copying objects across buckets + +To copy an object across buckets, use the `copy` method and specify the destination bucket. + +```javascript +await supabase.storage.from('avatars').copy('public/avatar1.png', 'private/avatar2.png', { + destinationBucket: 'avatars2', +}) +``` + +## Move objects + +You can move objects between buckets or within the same bucket. Currently only objects up to 5GB can be moved using the API. + +When moving an object, the owner of the new object will be the user who initiated the move operation. Once the object is moved, the original object will no longer exist. + +### Moving objects within the same bucket + +To move an object within the same bucket, you can use the `move` method. + +```javascript +const { data, error } = await supabase.storage + .from('avatars') + .move('public/avatar1.png', 'private/avatar2.png') +``` + +### Moving objects across buckets + +To move an object across buckets, use the `move` method and specify the destination bucket. + +```javascript +await supabase.storage.from('avatars').move('public/avatar1.png', 'private/avatar2.png', { + destinationBucket: 'avatars2', +}) +``` + +## Permissions + +For a user to move and copy objects, they need `select` permission on the source object and `insert` permission on the destination object. For example: + +```sql +create policy "User can select their own objects (in any buckets)" +on storage.objects +for select +to authenticated +using ( + owner_id = (select auth.uid()) +); + +create policy "User can upload in their own folders (in any buckets)" +on storage.objects +for insert +to authenticated +with check ( + (storage.folder(name))[1] = (select auth.uid()) +); +``` diff --git a/apps/docs/content/guides/storage/management/delete-objects.mdx b/apps/docs/content/guides/storage/management/delete-objects.mdx new file mode 100644 index 00000000000..2c54eb2300f --- /dev/null +++ b/apps/docs/content/guides/storage/management/delete-objects.mdx @@ -0,0 +1,37 @@ +--- +id: 'storage-management' +title: 'Delete Objects' +description: 'Learn about deleting objects' +subtitle: 'Learn about deleting objects' +sidebar_label: 'Delete Objects' +--- + +When you delete one or more objects from a bucket, the files are permanently removed and not recoverable. You can delete a single object or multiple objects at once. + + + +Deleting objects should always be done via the **Storage API** and NOT via a **SQL query**. Deleting objects via a SQL query will not remove the object from the bucket and will result in the object being orphaned. + + + +## Delete objects + +To delete one or more objects, use the `remove` method. + +```javascript +await supabase.storage.from('bucket').remove(['object-key-1', 'object-key-2']) +``` + +## RLS + +To delete an object, the user must have the `delete` permission on the object. For example: + +```sql +create policy "User can delete their own objects" +on storage.objects +for delete +TO authenticated +USING ( + owner_id = (select auth.uid()) +); +``` diff --git a/apps/docs/content/guides/storage/s3/authentication.mdx b/apps/docs/content/guides/storage/s3/authentication.mdx new file mode 100644 index 00000000000..ab127a2d1a4 --- /dev/null +++ b/apps/docs/content/guides/storage/s3/authentication.mdx @@ -0,0 +1,96 @@ +--- +id: 'storage-s3-authentication' +title: 'S3 Authentication' +description: 'Authentication' +subtitle: 'Learn about authenticating with Supabase Storage S3.' +sidebar_label: 'S3' +--- + +You have two options to authenticate with Supabase Storage S3: + +- Using the generated S3 access keys from your [project settings](/dashboard/project/_/settings/storage) (Intended exclusively for server-side use) +- Using a Session Token, which will allow you to authenticate with a user JWT token and provide limited access via Row Level Security (RLS). + +## S3 access keys + + + +S3 access keys provide full access to all S3 operations across all buckets and bypass RLS policies. These are meant to be used only on the server. + + + +To authenticate with S3, generate a pair of credentials (Access Key ID and Secret Access Key), copy the endpoint and region from the [project settings page](/dashboard/project/_/settings/storage). + +This is all the information you need to connect to Supabase Storage using any S3-compatible service. + +Storage S3 Access keys + + + + ```js + import { S3Client } from '@aws-sdk/client-s3'; + + const client = new S3Client({ + forcePathStyle: true, + region: 'project_region', + endpoint: 'https://project_ref.supabase.co/storage/v1/s3', + credentials: { + accessKeyId: 'your_access_key_id', + accessSecretKey: 'your_secret_access_key', + } + }) + ``` + + + + ```bash + # ~/.aws/credentials + + [supabase] + aws_access_key_id = your_access_key_id + aws_secret_access_key = your_secret_access_key + endpoint_url = https://project_ref.supabase.co/storage/v1/s3 + region = project_region + ``` + + + + +## Session token + +You can authenticate to Supabase S3 with a user JWT token to provide limited access via RLS to all S3 operations. This is useful when you want initialize the S3 client on the server scoped to a specific user, or use the S3 client directly from the client side. + +All S3 operations performed with the Session Token are scoped to the authenticated user. RLS policies on the Storage Schema are respected. + +To authenticate with S3 using a Session Token, use the following credentials: + +- access_key_id: `project_ref` +- access_secret_key: `anonKey` +- session_token: `valid jwt token` + +For example, using the `aws-sdk` library: + +```javascript +import { S3Client } from '@aws-sdk/client-s3' + +const { + data: { session }, +} = await supabase.auth.getSession() + +const client = new S3Client({ + forcePathStyle: true, + region: 'project_region', + endpoint: 'https://project_ref.supabase.co/storage/v1/s3', + credentials: { + accessKeyId: 'project_ref', + accessSecretKey: 'anonKey', + sessionToken: session.access_token, + }, +}) +``` diff --git a/apps/docs/content/guides/storage/s3/compatibility.mdx b/apps/docs/content/guides/storage/s3/compatibility.mdx new file mode 100644 index 00000000000..b6770b12da8 --- /dev/null +++ b/apps/docs/content/guides/storage/s3/compatibility.mdx @@ -0,0 +1,57 @@ +--- +id: 'storage-s3-compatibility' +title: 'S3 Compatibility' +description: 'Compatibility spec' +subtitle: 'Learn about the compatibility of Supabase Storage with S3.' +sidebar_label: 'S3' +--- + +Supabase Storage is compatible with the S3 protocol. You can use any S3 client to interact with your Storage objects. + +Storage supports [standard](/docs/guides/storage/uploads/standard-uploads), [resumable](/docs/guides/storage/uploads/resumable-uploads) and [S3 uploads](/docs/guides/storage/uploads/s3-uploads) and all these protocols are interoperable. You can upload a file with the S3 protocol and list it with the REST API or upload with Resumable uploads and list with S3. + + + +The S3 protocol is currently in Public Alpha. If you encounter any issues or have feature requests, [contact us](/dashboard/support/new). + + + +## Implemented endpoints + +The most commonly used endpoints are implemented, and more will be added. Implemented S3 endpoints are marked with ✅ in the following tables. + +### Bucket operations + +| API Name | Feature | +| ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ✅ [ListBuckets](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListBuckets.html) | | +| ✅ [HeadBucket](https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadBucket.html) | ❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ✅ [CreateBucket](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html) | ❌ ACL:
❌ x-amz-acl
❌ x-amz-grant-full-control
❌ x-amz-grant-read
❌ x-amz-grant-read-acp
❌ x-amz-grant-write
❌ x-amz-grant-write-acp
❌ Object Locking:
❌ x-amz-bucket-object-lock-enabled
❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ✅ [DeleteBucket](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucket.html) | ❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ✅ [GetBucketLocation](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html) | ❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ❌ [DeleteBucketCors](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketCors.html) | ❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ❌ [GetBucketEncryption](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketEncryption.html) | ❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ❌ [GetBucketLifecycleConfiguration](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html) | ❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ❌ [GetBucketCors](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketCors.html) | ❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ❌ [PutBucketCors](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketCors.html) | ❌ Checksums:
❌ x-amz-sdk-checksum-algorithm
❌ x-amz-checksum-algorithm
❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ❌ [PutBucketLifecycleConfiguration](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html) | ❌ Checksums:
❌ x-amz-sdk-checksum-algorithm
❌ x-amz-checksum-algorithm
❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | + +### Object operations + +| API Name | Feature | +| -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ✅ [HeadObject](https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html) | ✅ Conditional Operations:
✅ If-Match
✅ If-Modified-Since
✅ If-None-Match
✅ If-Unmodified-Since
✅ Range:
✅ Range (has no effect in HeadObject)
✅ partNumber
❌ SSE-C:
❌ x-amz-server-side-encryption-customer-algorithm
❌ x-amz-server-side-encryption-customer-key
❌ x-amz-server-side-encryption-customer-key-MD5
❌ Request Payer:
❌ x-amz-request-payer
❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ✅ [ListObjects](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjects.html) | Query Parameters:
✅ delimiter
✅ encoding-type
✅ marker
✅ max-keys
✅ prefix
❌ Request Payer:
❌ x-amz-request-payer
❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ✅ [ListObjectsV2](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html) | Query Parameters:
✅ list-type
✅ continuation-token
✅ delimiter
✅ encoding-type
✅ fetch-owner
✅ max-keys
✅ prefix
✅ start-after
❌ Request Payer:
❌ x-amz-request-payer
❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ✅ [GetObject](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html) | ✅ Conditional Operations:
✅ If-Match
✅ If-Modified-Since
✅ If-None-Match
✅ If-Unmodified-Since
✅ Range:
✅ Range
✅ PartNumber
❌ SSE-C:
❌ x-amz-server-side-encryption-customer-algorithm
❌ x-amz-server-side-encryption-customer-key
❌ x-amz-server-side-encryption-customer-key-MD5
❌ Request Payer:
❌ x-amz-request-payer
❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ✅ [PutObject](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html) | System Metadata:
✅ Content-Type
✅ Cache-Control
✅ Content-Disposition
✅ Content-Encoding
✅ Content-Language
✅ Expires
❌ Content-MD5
❌ Object Lifecycle
❌ Website:
❌ x-amz-website-redirect-location
❌ SSE-C:
❌ x-amz-server-side-encryption
❌ x-amz-server-side-encryption-customer-algorithm
❌ x-amz-server-side-encryption-customer-key
❌ x-amz-server-side-encryption-customer-key-MD5
❌ x-amz-server-side-encryption-aws-kms-key-id
❌ x-amz-server-side-encryption-context
❌ x-amz-server-side-encryption-bucket-key-enabled
❌ Request Payer:
❌ x-amz-request-payer
❌ Tagging:
❌ x-amz-tagging
❌ Object Locking:
❌ x-amz-object-lock-mode
❌ x-amz-object-lock-retain-until-date
❌ x-amz-object-lock-legal-hold
❌ ACL:
❌ x-amz-acl
❌ x-amz-grant-full-control
❌ x-amz-grant-read
❌ x-amz-grant-read-acp
❌ x-amz-grant-write-acp
❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ✅ [DeleteObject](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html) | ❌ Multi-factor authentication:
❌ x-amz-mfa
❌ Object Locking:
❌ x-amz-bypass-governance-retention
❌ Request Payer:
❌ x-amz-request-payer
❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ✅ [DeleteObjects](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html) | ❌ Multi-factor authentication:
❌ x-amz-mfa
❌ Object Locking:
❌ x-amz-bypass-governance-retention
❌ Request Payer:
❌ x-amz-request-payer
❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ✅ [ListMultipartUploads](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListMultipartUploads.html) | ✅ Query Parameters:
✅ delimiter
✅ encoding-type
✅ key-marker
✅️ max-uploads
✅ prefix
✅ upload-id-marker | +| ✅ [CreateMultipartUpload](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateMultipartUpload.html) | ✅ System Metadata:
✅ Content-Type
✅ Cache-Control
✅ Content-Disposition
✅ Content-Encoding
✅ Content-Language
✅ Expires
❌ Content-MD5
❌ Website:
❌ x-amz-website-redirect-location
❌ SSE-C:
❌ x-amz-server-side-encryption
❌ x-amz-server-side-encryption-customer-algorithm
❌ x-amz-server-side-encryption-customer-key
❌ x-amz-server-side-encryption-customer-key-MD5
❌ x-amz-server-side-encryption-aws-kms-key-id
❌ x-amz-server-side-encryption-context
❌ x-amz-server-side-encryption-bucket-key-enabled
❌ Request Payer:
❌ x-amz-request-payer
❌ Tagging:
❌ x-amz-tagging
❌ Object Locking:
❌ x-amz-object-lock-mode
❌ x-amz-object-lock-retain-until-date
❌ x-amz-object-lock-legal-hold
❌ ACL:
❌ x-amz-acl
❌ x-amz-grant-full-control
❌ x-amz-grant-read
❌ x-amz-grant-read-acp
❌ x-amz-grant-write-acp
❌ Storage class:
❌ x-amz-storage-class
❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ✅ [CompleteMultipartUpload](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html) | ❌ Bucket Owner:
❌ x-amz-expected-bucket-owner
❌ Request Payer:
❌ x-amz-request-payer | +| ✅ [AbortMultipartUpload](https://docs.aws.amazon.com/AmazonS3/latest/API/API_AbortMultipartUpload.html) | ❌ Request Payer:
❌ x-amz-request-payer | +| ✅ [CopyObject](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html) | ✅ Operation Metadata:
⚠️ x-amz-metadata-directive
✅ System Metadata:
✅ Content-Type
✅ Cache-Control
✅ Content-Disposition
✅ Content-Encoding
✅ Content-Language
✅ Expires
✅ Conditional Operations:
✅ x-amz-copy-source
✅ x-amz-copy-source-if-match
✅ x-amz-copy-source-if-modified-since
✅ x-amz-copy-source-if-none-match
✅ x-amz-copy-source-if-unmodified-since
❌ ACL:
❌ x-amz-acl
❌ x-amz-grant-full-control
❌ x-amz-grant-read
❌ x-amz-grant-read-acp
❌ x-amz-grant-write-acp
❌ Website:
❌ x-amz-website-redirect-location
❌ SSE-C:
❌ x-amz-server-side-encryption
❌ x-amz-server-side-encryption-customer-algorithm
❌ x-amz-server-side-encryption-customer-key
❌ x-amz-server-side-encryption-customer-key-MD5
❌ x-amz-server-side-encryption-aws-kms-key-id
❌ x-amz-server-side-encryption-context
❌ x-amz-server-side-encryption-bucket-key-enabled
❌ x-amz-copy-source-server-side-encryption-customer-algorithm
❌ x-amz-copy-source-server-side-encryption-customer-key
❌ x-amz-copy-source-server-side-encryption-customer-key-MD5
❌ Request Payer:
❌ x-amz-request-payer
❌ Tagging:
❌ x-amz-tagging
❌ x-amz-tagging-directive
❌ Object Locking:
❌ x-amz-object-lock-mode
❌ x-amz-object-lock-retain-until-date
❌ x-amz-object-lock-legal-hold
❌ Bucket Owner:
❌ x-amz-expected-bucket-owner
❌ x-amz-source-expected-bucket-owner
❌ Checksums:
❌ x-amz-checksum-algorithm | +| ✅ [UploadPart](https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPart.html) | ✅ System Metadata:
❌ Content-MD5
❌ SSE-C:
❌ x-amz-server-side-encryption
❌ x-amz-server-side-encryption-customer-algorithm
❌ x-amz-server-side-encryption-customer-key
❌ x-amz-server-side-encryption-customer-key-MD5
❌ Request Payer:
❌ x-amz-request-payer
❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | +| ✅ [UploadPartCopy](https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPartCopy.html) | ❌ Conditional Operations:
❌ x-amz-copy-source
❌ x-amz-copy-source-if-match
❌ x-amz-copy-source-if-modified-since
❌ x-amz-copy-source-if-none-match
❌ x-amz-copy-source-if-unmodified-since
✅ Range:
✅ x-amz-copy-source-range
❌ SSE-C:
❌ x-amz-server-side-encryption-customer-algorithm
❌ x-amz-server-side-encryption-customer-key
❌ x-amz-server-side-encryption-customer-key-MD5
❌ x-amz-copy-source-server-side-encryption-customer-algorithm
❌ x-amz-copy-source-server-side-encryption-customer-key
❌ x-amz-copy-source-server-side-encryption-customer-key-MD5
❌ Request Payer:
❌ x-amz-request-payer
❌ Bucket Owner:
❌ x-amz-expected-bucket-owner
❌ x-amz-source-expected-bucket-owner | +| ✅ [ListParts](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListParts.html) | Query Parameters:
✅ max-parts
✅ part-number-marker
❌ Request Payer:
❌ x-amz-request-payer
❌ Bucket Owner:
❌ x-amz-expected-bucket-owner | diff --git a/apps/docs/content/guides/storage/schema/custom-roles.mdx b/apps/docs/content/guides/storage/schema/custom-roles.mdx new file mode 100644 index 00000000000..8d2b86cc8dc --- /dev/null +++ b/apps/docs/content/guides/storage/schema/custom-roles.mdx @@ -0,0 +1,73 @@ +--- +id: 'storage-schema-design' +title: 'Custom Roles' +description: 'Learn about the storage schema' +subtitle: 'Learn about using custom roles with storage schema' +sidebar_label: 'Schema' +--- + +In this guide, you will learn how to create and use custom roles with Storage to manage role-based access to objects and buckets. + +Supabase Storage uses the same role-based access control system as any other Supabase service using RLS (Row Level Security). + +## Create a Custom Role + +Let's create a custom role `manager` to provide full read access to a specific bucket. For a more advanced setup, see the [RBAC Guide](/docs/guides/auth/custom-claims-and-role-based-access-control-rbac#create-auth-hook-to-apply-user-role). + +```sql +create role 'manager'; + +-- Important to grant the role to the authenticator and anon role +grant manager to authenticator; +grant anon to manager; +``` + +## Create a policy + +Let's create a policy that gives full read permissions to all objects in the bucket `teams` for the `manager` role. + +```sql +create policy "Manager can view all files in the bucket 'teams'" +on storage.objects +for select +to manager +using ( + bucker_id = 'teams' +); +``` + +## Test the policy + +To impersonate the `manager` role, you will need a valid JWT token with the `manager` role. +You can quickly create one using the `jsonwebtoken` library in Node.js. + + + +Signing a new JWT requires your `JWT_SECRET`. You must store this secret securely. Never expose it in frontend code, and do not check it into version control. + + + +```js +const jwt = require('jsonwebtoken') + +const JWT_SECRET = 'your-jwt-secret' // You can find this in your Supabase project settings under API. Store this securely. +const USER_ID = '' // the user id that we want to give the manager role + +const token = jwt.sign({ role: 'manager', sub: USER_ID }, JWT_SECRET, { + expiresIn: '1h', +}) +``` + +Now you can use this token to access the Storage API. + +```js +const { StorageClient } = require('@supabase/storage-js') + +const PROJECT_URL = 'https://your-project-id.supabase.co/storage/v1' + +const storage = new StorageClient(PROJECT_URL, { + authorization: `Bearer ${token}`, +}) + +await storage.from('teams').list() +``` diff --git a/apps/docs/content/guides/storage/security/ownership.mdx b/apps/docs/content/guides/storage/security/ownership.mdx new file mode 100644 index 00000000000..a5d20510fe7 --- /dev/null +++ b/apps/docs/content/guides/storage/security/ownership.mdx @@ -0,0 +1,39 @@ +--- +id: 'storage-access-control' +title: 'Ownership' +description: 'Learn how ownership works in Supabase Storage and how to control access' +sidebar_label: 'Security' +--- + +When creating new buckets or objects in Supabase Storage, an owner is automatically assigned to the bucket or object. The owner is the user who created the resource and the value is derived from the `sub` claim in the JWT. +We store the `owner` in the `owner_id` column. + + + +When using the `service_key` to create a resource, the owner will not be set and the resource will be owned by anyone. This is also the case when you are creating Storage resources via the Dashboard. + + + + + +The Storage schema has 2 fields to represent ownership: `owner` and `owner_id`. `owner` is deprecated and will be removed. Use `owner_id` instead. + + + +## Access control + +By itself, the ownership of a resource does not provide any access control. However, you can enforce the ownership by implementing access control against storage resources scoped to their owner. + +For example, you can implement a policy where only the owner of an object can delete it. To do this, check the `owner_id` field of the object and compare it with the `sub` claim of the JWT: + +```sql +create policy "User can delete their own objects" +on storage.objects +for delete +to authenticated +using ( + owner_id = (select auth.uid()) +); +``` + +The use of RLS policies is just one way to enforce access control. You can also implement access control in your server code by following the same pattern. diff --git a/apps/docs/content/guides/storage/uploads/file-limits.mdx b/apps/docs/content/guides/storage/uploads/file-limits.mdx index 539d1cc0480..af1628c5bc9 100644 --- a/apps/docs/content/guides/storage/uploads/file-limits.mdx +++ b/apps/docs/content/guides/storage/uploads/file-limits.mdx @@ -1,35 +1,30 @@ --- id: 'storage-file-limits' title: 'Limits' +subtitle: 'Learn how to increase Supabase file limits.' description: 'Learn how to increase Supabase file limits.' -sidebar_label: 'Uploads' +sidebar_label: 'Limits' --- -You can customize the Max file size for file uploads. - ## Global file size -You can set the max file size across all your buckets by setting this global value in the dashboard [here](https://supabase.com/dashboard/project/_/settings/storage). - -For Free projects the limit cannot exceed 50MB. On the Pro Plan and above, you can set this value to up to 50GB. +You can set the max file size across all your buckets by setting this global value in the dashboard [here](https://supabase.com/dashboard/project/_/settings/storage). For Free projects, the limit can't exceed 50 MB. On the Pro Plan and up, you can set this value to up to 50 GB. If you need more than 50 GB, [contact us](https://supabase.com/dashboard/support/new). | Plan | Max File Size Limit | | ---------- | ------------------- | -| Free | 50MB | -| Pro | 50GB | -| Team | 50GB | +| Free | 50 MB | +| Pro | 50 GB | +| Team | 50 GB | | Enterprise | Custom | -If you need more than 50GB please [contact us](https://supabase.com/dashboard/support/new). + -Remember, this option is a global limit, which applies to all your buckets. -You can additionally specify the max file size on a per [bucket level](/docs/guides/storage/buckets/creating-buckets#restricting-uploads) but it cannot be higher than this global limit. +This option is a global limit, which applies to all your buckets. -It is a good practice to have the global limit set to the highest possible file size that your application accepts, and apply per bucket limits. + + +Additionally, you can specify the max file size on a per [bucket level](/docs/guides/storage/buckets/creating-buckets#restricting-uploads) but it can't be higher than this global limit. As a good practice, the global limit should be set to the highest possible file size that your application accepts, and apply per bucket limits. ## Per bucket restrictions -You can have different restrictions on a per bucket level. -You are able to restrict the file types (eg. `pdf`, `images`, `videos`) along with the max file size which should be lower than the global limit. - -To apply these limit on a bucket level see [Creating Bucket](/docs/guides/storage/buckets/creating-buckets#restricting-uploads) +You can have different restrictions on a per bucket level such as restricting the file types (e.g. `pdf`, `images`, `videos`) or the max file size, which should be lower than the global limit. To apply these limit on a bucket level see [Creating Buckets](/docs/guides/storage/buckets/creating-buckets#restricting-uploads). diff --git a/apps/docs/content/guides/storage/uploads/s3-uploads.mdx b/apps/docs/content/guides/storage/uploads/s3-uploads.mdx new file mode 100644 index 00000000000..1a807f813aa --- /dev/null +++ b/apps/docs/content/guides/storage/uploads/s3-uploads.mdx @@ -0,0 +1,71 @@ +--- +id: 's3-uploads' +title: 'S3 Uploads' +description: 'Learn how to upload files to Supabase Storage using S3.' +subtitle: 'Learn how to upload files to Supabase Storage using S3.' +sidebar_label: 'Uploads' +--- + +You can use the S3 protocol to upload files to Supabase Storage. To get started with S3, see the [S3 setup guide](/docs/guides/storage/s3/authentication). + +The S3 protocol supports file upload using: + +- A single request +- Multiple requests via Multipart Upload + +## Single request uploads + +The `PutObject` action uploads the file in a single request. This matches the behavior of the Supabase SDK [Standard Upload](/docs/guides/storage/uploads/standard-uploads). + +Use `PutObject` to upload smaller files, where retrying the entire upload won't be an issue. The maximum file size on paid plans is 5 GB. + +For example, using JavaScript and the `aws-sdk` client: + +```javascript +import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3' + +const s3Client = new S3Client({...}) + +const file = fs.createReadStream('path/to/file') + +const uploadCommand = new PutObjectCommand({ + Bucket: 'bucket-name', + Key: 'path/to/file', + Body: file, + ContentType: 'image/jpeg', +}) + +await s3Client.send(uploadCommand) +``` + +## Multipart uploads + +Multipart Uploads split the file into smaller parts and upload them in parallel, maximizing the upload speed on a fast network. When uploading large files, this allows you to retry the upload of individual parts in case of network issues. + +This method is preferable over [Resumable Upload](/docs/guides/storage/uploads/resumable-uploads) for server-side uploads, when you want to maximize upload speed at the cost of resumability. The maximum file size on paid plans is 50 GB. + +### Upload a file in parts + +Use the `Upload` class from an S3 client to upload a file in parts. For example, using JavaScript: + +```javascript +import { S3Client } from '@aws-sdk/client-s3' +import { Upload } from '@aws-sdk/lib-storage' + +const s3Client = new S3Client({...}) + +const file = fs.createReadStream('path/to/very-large-file') + +const upload = new Upload(s3Client, { + Bucket: 'bucket-name', + Key: 'path/to/file', + ContentType: 'image/jpeg', + Body: file, +}) + +await uploader.done() +``` + +### Aborting multipart uploads + +All multipart uploads are automatically aborted after 24 hours. To abort a multipart upload before that, you can use the [`AbortMultipartUpload`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_AbortMultipartUpload.html) action. diff --git a/apps/docs/public/img/storage/cyberduck.png b/apps/docs/public/img/storage/cyberduck.png new file mode 100644 index 00000000000..b1bd8aea839 Binary files /dev/null and b/apps/docs/public/img/storage/cyberduck.png differ diff --git a/apps/docs/public/img/storage/s3-credentials.png b/apps/docs/public/img/storage/s3-credentials.png new file mode 100644 index 00000000000..a076a8492ef Binary files /dev/null and b/apps/docs/public/img/storage/s3-credentials.png differ diff --git a/apps/www/_blog/2024-04-18-s3-compatible-storage.mdx b/apps/www/_blog/2024-04-18-s3-compatible-storage.mdx new file mode 100644 index 00000000000..e808c2c8605 --- /dev/null +++ b/apps/www/_blog/2024-04-18-s3-compatible-storage.mdx @@ -0,0 +1,117 @@ +--- +title: 'Supabase Storage: now supports the S3 protocol' +description: 'Supabase Storage is now officially an S3-Compatible Storage Provider.' +author: fabrizio +image: ga-week/s3-compatible-storage/og.png?v=2 +thumb: ga-week/s3-compatible-storage/thumb.png?v=2 +categories: + - product +tags: + - launch-week + - storage +date: '2024-04-18' +toc_depth: 3 +launchweek: 11 +--- + +Supabase Storage is now officially an S3-Compatible Storage Provider. This is one of the most-requested features and is available today in public alpha. Resumable Uploads are also transitioning from Beta to Generally Available. + +The [Supabase Storage Engine](https://github.com/supabase/storage) is fully open source and is one of the few storage solutions that offer 3 interoperable protocols to manage your files: + +- [Standard uploads](/docs/guides/storage/uploads/standard-uploads): simple to get started +- [Resumable uploads](/docs/guides/storage/uploads/resumable-uploads): for resumable uploads with large uploads +- [S3 uploads](/docs/guides/storage/s3/compatibility): for compatibility across a plethora of tools + +
+