Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions Storage/Extensions/HttpClientProgress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,31 +130,34 @@ public static Task<HttpResponseMessage> UploadFileAsync(
Uri uri,
string filePath,
Dictionary<string, string>? headers = null,
Progress<float>? progress = null
Progress<float>? progress = null,
CancellationToken cancellationToken = default
)
{
var fileStream = new FileStream(filePath, mode: FileMode.Open, FileAccess.Read);
return UploadAsync(client, uri, fileStream, headers, progress);
return UploadAsync(client, uri, fileStream, headers, progress, cancellationToken);
}

public static Task<HttpResponseMessage> UploadBytesAsync(
this HttpClient client,
Uri uri,
byte[] data,
Dictionary<string, string>? headers = null,
Progress<float>? progress = null
Progress<float>? progress = null,
CancellationToken cancellationToken = default
)
{
var stream = new MemoryStream(data);
return UploadAsync(client, uri, stream, headers, progress);
return UploadAsync(client, uri, stream, headers, progress, cancellationToken);
}

public static async Task<HttpResponseMessage> UploadAsync(
this HttpClient client,
Uri uri,
Stream stream,
Dictionary<string, string>? headers = null,
Progress<float>? progress = null
Progress<float>? progress = null,
CancellationToken cancellationToken = default
)
{
var content = new ProgressableStreamContent(stream, 4096, progress);
Expand All @@ -172,7 +175,7 @@ public static async Task<HttpResponseMessage> UploadAsync(
}
}

var response = await client.PostAsync(uri, content);
var response = await client.PostAsync(uri, content, cancellationToken);

if (!response.IsSuccessStatusCode)
{
Expand Down
6 changes: 4 additions & 2 deletions Storage/Interfaces/IStorageFileApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,16 @@ Task<string> Upload(
string supabasePath,
FileOptions? options = null,
EventHandler<float>? onProgress = null,
bool inferContentType = true
bool inferContentType = true,
CancellationToken cancellationToken = default
);
Task<string> Upload(
string localFilePath,
string supabasePath,
FileOptions? options = null,
EventHandler<float>? onProgress = null,
bool inferContentType = true
bool inferContentType = true,
CancellationToken cancellationToken = default
);
Task UploadOrResume(
string localPath,
Expand Down
20 changes: 12 additions & 8 deletions Storage/StorageFileApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,15 +227,16 @@ public async Task<string> Upload(
string supabasePath,
FileOptions? options = null,
EventHandler<float>? onProgress = null,
bool inferContentType = true
bool inferContentType = true,
CancellationToken cancellationToken = default
)
{
options ??= new FileOptions();

if (inferContentType)
options.ContentType = MimeMapping.MimeUtility.GetMimeMapping(localFilePath);

var result = await UploadOrUpdate(localFilePath, supabasePath, options, onProgress);
var result = await UploadOrUpdate(localFilePath, supabasePath, options, onProgress, cancellationToken);
return result;
}

Expand All @@ -253,15 +254,16 @@ public async Task<string> Upload(
string supabasePath,
FileOptions? options = null,
EventHandler<float>? onProgress = null,
bool inferContentType = true
bool inferContentType = true,
CancellationToken cancellationToken = default
)
{
options ??= new FileOptions();

if (inferContentType)
options.ContentType = MimeMapping.MimeUtility.GetMimeMapping(supabasePath);

var result = await UploadOrUpdate(data, supabasePath, options, onProgress);
var result = await UploadOrUpdate(data, supabasePath, options, onProgress, cancellationToken);
return result;
}

Expand Down Expand Up @@ -668,7 +670,8 @@ private async Task<string> UploadOrUpdate(
string localPath,
string supabasePath,
FileOptions options,
EventHandler<float>? onProgress = null
EventHandler<float>? onProgress = null,
CancellationToken cancellationToken = default
)
{
Uri uri = new Uri($"{Url}/object/{GetFinalPath(supabasePath)}");
Expand All @@ -695,7 +698,7 @@ private async Task<string> UploadOrUpdate(
if (onProgress != null)
progress.ProgressChanged += onProgress;

await Helpers.HttpUploadClient!.UploadFileAsync(uri, localPath, headers, progress);
await Helpers.HttpUploadClient!.UploadFileAsync(uri, localPath, headers, progress, cancellationToken);

return GetFinalPath(supabasePath);
}
Expand Down Expand Up @@ -808,7 +811,8 @@ private async Task<string> UploadOrUpdate(
byte[] data,
string supabasePath,
FileOptions options,
EventHandler<float>? onProgress = null
EventHandler<float>? onProgress = null,
CancellationToken cancellationToken = default
)
{
Uri uri = new Uri($"{Url}/object/{GetFinalPath(supabasePath)}");
Expand All @@ -835,7 +839,7 @@ private async Task<string> UploadOrUpdate(
if (onProgress != null)
progress.ProgressChanged += onProgress;

await Helpers.HttpUploadClient!.UploadBytesAsync(uri, data, headers, progress);
await Helpers.HttpUploadClient!.UploadBytesAsync(uri, data, headers, progress, cancellationToken);

return GetFinalPath(supabasePath);
}
Expand Down
29 changes: 28 additions & 1 deletion StorageTests/StorageFileTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ public async Task UploadOrResumeByteWithInterruptionAndResume()

var options = new FileOptions { Duplex = "duplex", Metadata = metadata };

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(300));

try
{
Expand Down Expand Up @@ -406,6 +406,33 @@ await _bucket.Upload(

await _bucket.Remove(new List<string> { name });
}

[TestMethod("File: Cancel Upload Arbitrary Byte Array")]
public async Task UploadArbitraryByteArrayCanceled()
{
var tsc = new TaskCompletionSource<bool>();
using var ctk = new CancellationTokenSource(TimeSpan.FromMilliseconds(1));

var data = new byte[20 * 1024 * 1024];
var rng = new Random();
rng.NextBytes(data);
var name = $"{Guid.NewGuid()}.bin";

var action = async () =>
{
await _bucket.Upload(data, name, null, (_, _) => tsc.TrySetResult(true), true, ctk.Token);
};

await Assert.ThrowsExceptionAsync<TaskCanceledException>(action);

var list = await _bucket.List();
Assert.IsNotNull(list);

var existing = list.Find(item => item.Name == name);
Assert.IsNull(existing);

await _bucket.Remove([name]);
}

[TestMethod("File: Download")]
public async Task DownloadFile()
Expand Down
40 changes: 40 additions & 0 deletions supabase/migrations/00-schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
-- Set up reatime
create publication supabase_realtime for all tables;

-- Supabase super admin
create user supabase_admin;
alter user supabase_admin with superuser createdb createrole replication bypassrls;


-- Extension namespacing
create schema extensions;
create extension if not exists "uuid-ossp" with schema extensions;
create extension if not exists pgcrypto with schema extensions;
-- create extension if not exists pgjwt with schema extensions;

-- Set up auth roles for the developer
create role anon nologin noinherit;
create role authenticated nologin noinherit; -- "logged in" user: web_user, app_user, etc
create role service_role nologin noinherit bypassrls; -- allow developers to create JWT's that bypass their policies

create user authenticator noinherit;
grant anon to authenticator;
grant authenticated to authenticator;
grant service_role to authenticator;
grant supabase_admin to authenticator;

grant usage on schema public to postgres, anon, authenticated, service_role;
alter default privileges in schema public grant all on tables to postgres, anon, authenticated, service_role;
alter default privileges in schema public grant all on functions to postgres, anon, authenticated, service_role;
alter default privileges in schema public grant all on sequences to postgres, anon, authenticated, service_role;

-- Set up namespacing
alter user supabase_admin SET search_path TO public, extensions; -- don't include the "auth" schema

-- These are required so that the users receive grants whenever "supabase_admin" creates tables/function
alter default privileges for user supabase_admin in schema public grant all
on sequences to postgres, anon, authenticated, service_role;
alter default privileges for user supabase_admin in schema public grant all
on tables to postgres, anon, authenticated, service_role;
alter default privileges for user supabase_admin in schema public grant all
on functions to postgres, anon, authenticated, service_role;
102 changes: 102 additions & 0 deletions supabase/migrations/01-auth-schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@

CREATE SCHEMA IF NOT EXISTS auth AUTHORIZATION supabase_admin;

-- auth.users definition

CREATE TABLE auth.users (
instance_id uuid NULL,
id uuid NOT NULL UNIQUE,
aud varchar(255) NULL,
"role" varchar(255) NULL,
email varchar(255) NULL UNIQUE,
encrypted_password varchar(255) NULL,
confirmed_at timestamptz NULL,
invited_at timestamptz NULL,
confirmation_token varchar(255) NULL,
confirmation_sent_at timestamptz NULL,
recovery_token varchar(255) NULL,
recovery_sent_at timestamptz NULL,
email_change_token varchar(255) NULL,
email_change varchar(255) NULL,
email_change_sent_at timestamptz NULL,
last_sign_in_at timestamptz NULL,
raw_app_meta_data jsonb NULL,
raw_user_meta_data jsonb NULL,
is_super_admin bool NULL,
created_at timestamptz NULL,
updated_at timestamptz NULL,
CONSTRAINT users_pkey PRIMARY KEY (id)
);
CREATE INDEX users_instance_id_email_idx ON auth.users USING btree (instance_id, email);
CREATE INDEX users_instance_id_idx ON auth.users USING btree (instance_id);

-- auth.refresh_tokens definition

CREATE TABLE auth.refresh_tokens (
instance_id uuid NULL,
id bigserial NOT NULL,
"token" varchar(255) NULL,
user_id varchar(255) NULL,
revoked bool NULL,
created_at timestamptz NULL,
updated_at timestamptz NULL,
CONSTRAINT refresh_tokens_pkey PRIMARY KEY (id)
);
CREATE INDEX refresh_tokens_instance_id_idx ON auth.refresh_tokens USING btree (instance_id);
CREATE INDEX refresh_tokens_instance_id_user_id_idx ON auth.refresh_tokens USING btree (instance_id, user_id);
CREATE INDEX refresh_tokens_token_idx ON auth.refresh_tokens USING btree (token);

-- auth.instances definition

CREATE TABLE auth.instances (
id uuid NOT NULL,
uuid uuid NULL,
raw_base_config text NULL,
created_at timestamptz NULL,
updated_at timestamptz NULL,
CONSTRAINT instances_pkey PRIMARY KEY (id)
);

-- auth.audit_log_entries definition

CREATE TABLE auth.audit_log_entries (
instance_id uuid NULL,
id uuid NOT NULL,
payload json NULL,
created_at timestamptz NULL,
CONSTRAINT audit_log_entries_pkey PRIMARY KEY (id)
);
CREATE INDEX audit_logs_instance_id_idx ON auth.audit_log_entries USING btree (instance_id);

-- auth.schema_migrations definition

CREATE TABLE auth.schema_migrations (
"version" varchar(255) NOT NULL,
CONSTRAINT schema_migrations_pkey PRIMARY KEY ("version")
);

INSERT INTO auth.schema_migrations (version)
VALUES ('20171026211738'),
('20171026211808'),
('20171026211834'),
('20180103212743'),
('20180108183307'),
('20180119214651'),
('20180125194653');

-- Gets the User ID from the request cookie
create or replace function auth.uid() returns uuid as $$
select nullif(current_setting('request.jwt.claim.sub', true), '')::uuid;
$$ language sql stable;

-- Gets the User ID from the request cookie
create or replace function auth.role() returns text as $$
select nullif(current_setting('request.jwt.claim.role', true), '')::text;
$$ language sql stable;

-- Supabase super admin
CREATE USER supabase_auth_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION;
GRANT ALL PRIVILEGES ON SCHEMA auth TO supabase_auth_admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA auth TO supabase_auth_admin;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA auth TO supabase_auth_admin;
ALTER USER supabase_auth_admin SET search_path = "auth";
Loading
Loading