diff --git a/Examples/BlazorWebAssemblySupabaseTemplate/.vscode/launch.json b/Examples/BlazorWebAssemblySupabaseTemplate/.vscode/launch.json new file mode 100644 index 00000000..b0726229 --- /dev/null +++ b/Examples/BlazorWebAssemblySupabaseTemplate/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch and Debug Standalone Blazor WebAssembly App", + "type": "blazorwasm", + "request": "launch", + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Examples/BlazorWebAssemblySupabaseTemplate/.vscode/tasks.json b/Examples/BlazorWebAssemblySupabaseTemplate/.vscode/tasks.json new file mode 100644 index 00000000..95febf9a --- /dev/null +++ b/Examples/BlazorWebAssemblySupabaseTemplate/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/BlazorWebAssemblySupabaseTemplate.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/BlazorWebAssemblySupabaseTemplate.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/BlazorWebAssemblySupabaseTemplate.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/Examples/BlazorWebAssemblySupabaseTemplate/Dtos/TodoPrivate.cs b/Examples/BlazorWebAssemblySupabaseTemplate/Dtos/TodoPrivate.cs new file mode 100644 index 00000000..faf9753e --- /dev/null +++ b/Examples/BlazorWebAssemblySupabaseTemplate/Dtos/TodoPrivate.cs @@ -0,0 +1,19 @@ +using System; +using Postgrest.Attributes; +using Postgrest.Models; +using Supabase; + +namespace BlazorWebAssemblySupabaseTemplate.Dtos; + +[Table("TodoPrivate")] +public class TodoPrivate : BaseModel +{ + [PrimaryKey("id", false)] // Key is Autogenerated + public int Id { get; set; } + + [Column("title")] + public string? Title { get; set; } + + [Column("user_id")] + public string User_id { get; set; } +} diff --git a/Examples/BlazorWebAssemblySupabaseTemplate/Pages/CrudPrivate/CrudPagePrivate.razor b/Examples/BlazorWebAssemblySupabaseTemplate/Pages/CrudPrivate/CrudPagePrivate.razor new file mode 100644 index 00000000..ad95a736 --- /dev/null +++ b/Examples/BlazorWebAssemblySupabaseTemplate/Pages/CrudPrivate/CrudPagePrivate.razor @@ -0,0 +1,118 @@ +@page "/crud-private" +@using Dtos +@using Blazored.LocalStorage + +@inject DatabaseService DatabaseService +@inject NavigationManager NavigationManager +@inject ISnackbar Snackbar +@inject ILocalStorageService localStorage + +
+ + Todos private by RLS +
+ + + + +

New item

+
+
+ + + + + + @if (_processingNewItem) + { + + Processing + } + else + { + Save + } + + + +
+
+ +
+ + @if (_todoListFiltered == null) + { + + + + Title + Action + + + + + + + + + + } + else if (_todoListFiltered.Count == 0) + { + + + + Title + Action + + + +
+ There is no items in this table. +
+ +
+ } + else + { + + + + + + + + Title + + + User_id + + + + Action + + + + @context?.Title + @context?.User_id + + @* *@ + + + + + + + + } + +
\ No newline at end of file diff --git a/Examples/BlazorWebAssemblySupabaseTemplate/Pages/CrudPrivate/CrudPagePrivate.razor.cs b/Examples/BlazorWebAssemblySupabaseTemplate/Pages/CrudPrivate/CrudPagePrivate.razor.cs new file mode 100644 index 00000000..b443696c --- /dev/null +++ b/Examples/BlazorWebAssemblySupabaseTemplate/Pages/CrudPrivate/CrudPagePrivate.razor.cs @@ -0,0 +1,59 @@ +using BlazorWebAssemblySupabaseTemplate.Dtos; +using BlazorWebAssemblySupabaseTemplate.Services; +using MudBlazor; + +namespace BlazorWebAssemblySupabaseTemplate.Pages.CrudPrivate; + +public partial class CrudPagePrivate +{ + protected override async Task OnInitializedAsync() + { + await GetTable(); + } + + // ---------------- SELECT TABLE + private IReadOnlyList? _todoList { get; set; } + private IReadOnlyList? _todoListFiltered { get; set; } + private MudTable? table; + protected async Task GetTable() + { + // await Task.Delay(10000); + IReadOnlyList todos = await DatabaseService.From(); + _todoList = todos; + _todoListFiltered = todos; + await InvokeAsync(StateHasChanged); + } + + // ---------------- SEARCH + private void OnValueChangedSearch(string text) + { + _todoListFiltered = _todoList?.Where(row => row.Title.Contains(text)).ToList(); + } + + // ---------------- DELETE + private async Task OnClickDelete(TodoPrivate item) + { + await DatabaseService.Delete(item); + await GetTable(); + } + + // ---------------- CREATE NEW + + protected TodoPrivate model = new(); + private bool success = false; + string[] errors = { }; + MudForm? form; + private bool _processingNewItem = false; + private async Task OnClickSave() + { + string user_id = await localStorage.GetItemAsync("user_id"); + + model.User_id = user_id; + _processingNewItem = true; + await DatabaseService.Insert(model); + model = new(); + await GetTable(); + success = false; + _processingNewItem = false; + } +} \ No newline at end of file diff --git a/Examples/BlazorWebAssemblySupabaseTemplate/Program.cs b/Examples/BlazorWebAssemblySupabaseTemplate/Program.cs index 519033fc..e690f2a4 100644 --- a/Examples/BlazorWebAssemblySupabaseTemplate/Program.cs +++ b/Examples/BlazorWebAssemblySupabaseTemplate/Program.cs @@ -28,16 +28,37 @@ // ---------- BLAZOR AUTH -builder.Services.AddScoped(); +builder.Services.AddScoped( + provider => new CustomAuthStateProvider( + provider.GetRequiredService(), + provider.GetRequiredService() + ) +) + ; builder.Services.AddAuthorizationCore(); - - // ---------- SUPABASE var url = "https://pylnesfgmytjegzzculn.supabase.co"; var key = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InB5bG5lc2ZnbXl0amVnenpjdWxuIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NjgyOTMwMzcsImV4cCI6MTk4Mzg2OTAzN30.kI29Q_qYWDH5SD6oi5NTwHG6Pxy1e1AUfR8s_ga45lE"; -builder.Services.AddScoped>(args => new Supabase.Client(url, key, new Supabase.SupabaseOptions { AutoConnectRealtime = true })); +builder.Services.AddScoped( + provider => new Supabase.Client( + url, + key, + new Supabase.SupabaseOptions + { + AutoRefreshToken = true, + AutoConnectRealtime = true, + PersistSession = true, + SessionHandler = new CustomSupabaseSessionHandler( + provider.GetRequiredService(), + provider.GetRequiredService>() + ) + } + ) +); + +// builder.Services.AddScoped>(args => new Supabase.Client(url, key, new Supabase.SupabaseOptions { AutoConnectRealtime = true })); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/Examples/BlazorWebAssemblySupabaseTemplate/Providers/CustomAuthStateProvider.cs b/Examples/BlazorWebAssemblySupabaseTemplate/Providers/CustomAuthStateProvider.cs index 4ae7b575..114a391d 100644 --- a/Examples/BlazorWebAssemblySupabaseTemplate/Providers/CustomAuthStateProvider.cs +++ b/Examples/BlazorWebAssemblySupabaseTemplate/Providers/CustomAuthStateProvider.cs @@ -11,32 +11,35 @@ namespace BlazorWebAssemblySupabaseTemplate.Providers; public class CustomAuthStateProvider : AuthenticationStateProvider { private readonly ILocalStorageService _localStorage; + private readonly Supabase.Client _client; // private readonly IHttpClientFactory httpClientFactory; // private readonly HttpClient _http; public CustomAuthStateProvider( - ILocalStorageService localStorage + ILocalStorageService localStorage, + Supabase.Client client // IHttpClientFactory httpClientFactory // HttpClient http ) { _localStorage = localStorage; + _client = client; // this.httpClientFactory = httpClientFactory; // _http = http; } public override async Task GetAuthenticationStateAsync() { - string token = await _localStorage.GetItemAsStringAsync("token"); - // string token = "eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTUxMiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiVG9ueSBTdGFyayIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6Iklyb24gTWFuIiwiZXhwIjozMTY4NTQwMDAwfQ.IbVQa1lNYYOzwso69xYfsMOHnQfO3VLvVqV2SOXS7sTtyyZ8DEf5jmmwz2FGLJJvZnQKZuieHnmHkg7CGkDbvA"; - + // Sets client auth and connects to realtime (if enabled) + await _client.InitializeAsync(); + var identity = new ClaimsIdentity(); // _http.DefaultRequestHeaders.Authorization = null; - if (!string.IsNullOrEmpty(token)) + if (!string.IsNullOrEmpty(_client.Auth.CurrentSession?.AccessToken)) { - identity = new ClaimsIdentity(ParseClaimsFromJwt(token), "jwt"); + identity = new ClaimsIdentity(ParseClaimsFromJwt(_client.Auth.CurrentSession.AccessToken), "jwt"); // _http.DefaultRequestHeaders.Authorization = // new AuthenticationHeaderValue("Bearer", token.Replace("\"", "")); } diff --git a/Examples/BlazorWebAssemblySupabaseTemplate/Providers/CustomSupabaseSessionHandler.cs b/Examples/BlazorWebAssemblySupabaseTemplate/Providers/CustomSupabaseSessionHandler.cs new file mode 100644 index 00000000..4b4d8667 --- /dev/null +++ b/Examples/BlazorWebAssemblySupabaseTemplate/Providers/CustomSupabaseSessionHandler.cs @@ -0,0 +1,42 @@ +using Blazored.LocalStorage; +using Supabase.Gotrue; +using Supabase.Interfaces; + +namespace BlazorWebAssemblySupabaseTemplate.Providers; + +public class CustomSupabaseSessionHandler : ISupabaseSessionHandler +{ + private readonly ILocalStorageService localStorage; + private readonly ILogger logger; + private static string SESSION_KEY = "SUPABASE_SESSION"; + + public CustomSupabaseSessionHandler( + ILocalStorageService localStorage, + ILogger logger + ) + { + logger.LogInformation("------------------- CONSTRUCTOR -------------------"); + this.localStorage = localStorage; + this.logger = logger; + } + + public async Task SessionDestroyer() + { + logger.LogInformation("------------------- SessionDestroyer -------------------"); + await localStorage.RemoveItemAsync(SESSION_KEY); + return true; + } + + public async Task SessionPersistor(TSession session) where TSession : Session + { + logger.LogInformation("------------------- SessionPersistor -------------------"); + await localStorage.SetItemAsync(SESSION_KEY, session); + return true; + } + + public async Task SessionRetriever() where TSession : Session + { + logger.LogInformation("------------------- SessionRetriever -------------------"); + return (TSession?) await localStorage.GetItemAsync(SESSION_KEY); + } +} \ No newline at end of file diff --git a/Examples/BlazorWebAssemblySupabaseTemplate/README.MD b/Examples/BlazorWebAssemblySupabaseTemplate/README.MD index 2259dc02..f95660b4 100644 --- a/Examples/BlazorWebAssemblySupabaseTemplate/README.MD +++ b/Examples/BlazorWebAssemblySupabaseTemplate/README.MD @@ -1,3 +1,17 @@ +#TODO + - CustomSupabaseSessionHandler not working. + - how to test: + - Logout + - Login + - Access Crud table private RLS page + - Insert some row + - you will see the row displayed below + - press F5 on the browser + - the rows will disapear + - Maybe i created CustomSupabaseSessionHandler wrong. But it seem like that the token is not being sent with the headers... + + - create policy to insert that check if the user_id is the same of the user logged in trying to insert. + # Credits https://github.com/supabase-community/supabase-csharp https://github.com/patrickgod/BlazorAuthenticationTutorial diff --git a/Examples/BlazorWebAssemblySupabaseTemplate/Services/AuthService.cs b/Examples/BlazorWebAssemblySupabaseTemplate/Services/AuthService.cs index 0f10c788..fffbd518 100644 --- a/Examples/BlazorWebAssemblySupabaseTemplate/Services/AuthService.cs +++ b/Examples/BlazorWebAssemblySupabaseTemplate/Services/AuthService.cs @@ -10,29 +10,24 @@ namespace BlazorWebAssemblySupabaseTemplate.Services; public class AuthService { - private readonly ISupabaseClient client; + private readonly Supabase.Client client; private readonly AuthenticationStateProvider customAuthStateProvider; private readonly ILocalStorageService localStorage; private readonly ILogger logger; public AuthService( - ISupabaseClient client, + Supabase.Client client, AuthenticationStateProvider CustomAuthStateProvider, ILocalStorageService localStorage, ILogger logger ) : base() { - logger.LogInformation("CONSTRUCTOR: AuthService"); + logger.LogInformation("------------------- CONSTRUCTOR -------------------"); this.client = client; customAuthStateProvider = CustomAuthStateProvider; this.localStorage = localStorage; this.logger = logger; - - if( client.Auth == null) - { - client.InitializeAsync(); - } } public async Task Login(string email, string password) @@ -42,16 +37,15 @@ public async Task Login(string email, string password) Session? session = await client.Auth.SignIn(email, password); logger.LogInformation("------------------- User logged in -------------------"); - logger.LogInformation($"instance.Auth.CurrentUser.Id {client?.Auth?.CurrentUser?.Id}"); + // logger.LogInformation($"instance.Auth.CurrentUser.Id {client?.Auth?.CurrentUser?.Id}"); logger.LogInformation($"client.Auth.CurrentUser.Email {client?.Auth?.CurrentUser?.Email}"); - - await localStorage.SetItemAsStringAsync("token", session?.AccessToken); + await customAuthStateProvider.GetAuthenticationStateAsync(); } public async Task Logout() { - await localStorage.RemoveItemAsync("token"); + await client.Auth.SignOut(); await customAuthStateProvider.GetAuthenticationStateAsync(); } diff --git a/Examples/BlazorWebAssemblySupabaseTemplate/Services/DatabaseService.cs b/Examples/BlazorWebAssemblySupabaseTemplate/Services/DatabaseService.cs index f2ba84b0..bcd52530 100644 --- a/Examples/BlazorWebAssemblySupabaseTemplate/Services/DatabaseService.cs +++ b/Examples/BlazorWebAssemblySupabaseTemplate/Services/DatabaseService.cs @@ -11,47 +11,42 @@ namespace BlazorWebAssemblySupabaseTemplate.Services; public class DatabaseService { - private readonly ISupabaseClient client; - private readonly AuthenticationStateProvider customAuthStateProvider; - private readonly ILocalStorageService localStorage; - private readonly ILogger logger; - - public DatabaseService( - ISupabaseClient client, - AuthenticationStateProvider CustomAuthStateProvider, - ILocalStorageService localStorage, - ILogger logger - ) : base() - { - logger.LogInformation("CONSTRUCTOR: DatabaseService"); - - this.client = client; - customAuthStateProvider = CustomAuthStateProvider; - this.localStorage = localStorage; - this.logger = logger; - - if( client.Postgrest == null) - { - client.InitializeAsync(); - } - } - - public async Task> From() where TModel : BaseModel, new() - { - Postgrest.Responses.ModeledResponse modeledResponse = await client.From().Get(); - return modeledResponse.Models; - } - - public async Task> Delete(TModel item) where TModel : BaseModel, new() - { - Postgrest.Responses.ModeledResponse modeledResponse = await client.From().Delete(item); - return modeledResponse.Models; - } - - public async Task> Insert(TModel item) where TModel : BaseModel, new() - { - Postgrest.Responses.ModeledResponse modeledResponse = await client.From().Insert(item); - return modeledResponse.Models; - } + private readonly Supabase.Client client; + private readonly AuthenticationStateProvider customAuthStateProvider; + private readonly ILocalStorageService localStorage; + private readonly ILogger logger; + + public DatabaseService( + Supabase.Client client, + AuthenticationStateProvider CustomAuthStateProvider, + ILocalStorageService localStorage, + ILogger logger + ) : base() + { + logger.LogInformation("------------------- CONSTRUCTOR -------------------"); + + this.client = client; + customAuthStateProvider = CustomAuthStateProvider; + this.localStorage = localStorage; + this.logger = logger; + } + + public async Task> From() where TModel : BaseModel, new() + { + Postgrest.Responses.ModeledResponse modeledResponse = await client.From().Get(); + return modeledResponse.Models; + } + + public async Task> Delete(TModel item) where TModel : BaseModel, new() + { + Postgrest.Responses.ModeledResponse modeledResponse = await client.From().Delete(item); + return modeledResponse.Models; + } + + public async Task> Insert(TModel item) where TModel : BaseModel, new() + { + Postgrest.Responses.ModeledResponse modeledResponse = await client.From().Insert(item); + return modeledResponse.Models; + } } diff --git a/Examples/BlazorWebAssemblySupabaseTemplate/Shared/MainLayout.razor b/Examples/BlazorWebAssemblySupabaseTemplate/Shared/MainLayout.razor index 8460af32..10744426 100644 --- a/Examples/BlazorWebAssemblySupabaseTemplate/Shared/MainLayout.razor +++ b/Examples/BlazorWebAssemblySupabaseTemplate/Shared/MainLayout.razor @@ -36,9 +36,12 @@ @* ********************************** MENU ********************************** *@ - + Crud table + + Crud table private RLS + diff --git a/Examples/BlazorWebAssemblySupabaseTemplate/Supabase/DatabasePolicies.sql b/Examples/BlazorWebAssemblySupabaseTemplate/Supabase/DatabasePolicies.sql new file mode 100644 index 00000000..e4287831 --- /dev/null +++ b/Examples/BlazorWebAssemblySupabaseTemplate/Supabase/DatabasePolicies.sql @@ -0,0 +1,37 @@ +-- CREATE POLICY "policy_name" +-- ON public.Lista FOR +-- DELETE USING ( auth.uid() = user_id ); + +-- CREATE POLICY "policy_name" +-- ON public.Lista FOR +-- SELECT USING ( auth.uid() = user_id ); + +-- #################### TABELA LISTA + +-- CREATE POLICY "Enable read access for all users" ON "public"."Lista" +-- AS PERMISSIVE FOR SELECT +-- TO authenticated +-- USING (true) + + +CREATE POLICY "Users can SELECT if own row" + ON "public"."TodoPrivate" AS PERMISSIVE FOR SELECT + TO authenticated + USING ( auth.uid() = user_id ); + +CREATE POLICY "Users can UPDATE if own row" + ON "public"."TodoPrivate" AS PERMISSIVE FOR UPDATE + TO authenticated + USING ( auth.uid() = user_id ); + +CREATE POLICY "Users can DELETE if own row" + ON "public"."TodoPrivate" AS PERMISSIVE FOR DELETE + TO authenticated + USING ( auth.uid() = user_id ); + +CREATE POLICY "Users can SELECT if authenticated" + ON "public"."TodoPrivate" + AS PERMISSIVE + FOR INSERT + TO authenticated + WITH CHECK (true); \ No newline at end of file