diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 37f752d9..dc07c26c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,23 +3,19 @@ Thank you for contributing to **UltimateAuth**! Please complete the following checklist to help us review your PR effectively. ---- ## 📘 Summary Describe what this PR does and why it’s needed. ---- ## 🔍 Details Explain any important implementation details, design decisions, or considerations. ---- ## 🧩 Related Issues Link any related issues: ---- ## 🛠 Changes - [ ] New feature diff --git a/.github/codecov.yml b/.github/codecov.yml index 70dbff6f..c52c4b6c 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -5,9 +5,9 @@ coverage: status: project: default: - target: 50% - threshold: 5% + target: 70% + threshold: 0% patch: default: - target: 20% + target: 50% threshold: 0% diff --git a/README.md b/README.md index 930e8a4f..af08c023 100644 --- a/README.md +++ b/README.md @@ -33,15 +33,15 @@ UltimateAuth is an open-source auth framework with platform-level capabilities t | Phase | Version | Scope | Status | Release Date | | ----------------------- | ------------- | ----------------------------------------- | -------------- | ------------ | | First Preview | 0.1.0-preview | "Stable" Preview Core | ✅ Completed | 07.04.2026 | -| First Release* | 0.1.0 | Fully Documented & Quality Tested | 🟡 In Progress | Q2 2026 | -| Product Expansion | 0.2.0 | Full Auth Modes | 🟡 In Progress | Q2 2026 | -| Security Expansion | 0.3.0 | MFA, Reauth, Rate Limiting | 🔜 Planned | Q2 2026 | -| Infrastructure Expansion| 0.4.0 | Redis, Distributed Cache, Password Hasher | 🔜 Planned | Q2 2026 | -| Multi-Tenant Expansion | 0.5.0 | Multi tenant management | 🔜 Planned | Q3 2026 | -| Extensibility Expansion | 0.6.0 | Audit, events, hooks | 🔜 Planned | Q3 2026 | -| Performance Expansion | 0.7.0 | Benchmarks, caching | 🔜 Planned | Q3 2026 | -| Ecosystem Expansion | 0.8.0 | Migration tools | 🔜 Planned | Q4 2026 | -| v1.0 | 1.0.0 | Locked API, align with .NET 11 | 🔜 Planned | Q4 2026 | +| First Release* | 0.1.0 | Fully Documented & Quality Tested | ✅ Completed | 04.10.2026 | +| Product Expansion | 0.2.0 | Full Auth Modes | 🟡 In Progress | Q4 2026 | +| Security Expansion | 0.3.0 | MFA, Reauth, Rate Limiting | 🟡 In Progress | Q4 2026 | +| Infrastructure Expansion| 0.4.0 | Redis, Distributed Cache, Password Hasher | 🔜 Planned | Q1 2027 | +| Multi-Tenant Expansion | 0.5.0 | Multi tenant management | 🔜 Planned | Q1 2027 | +| Extensibility Expansion | 0.6.0 | Audit, events, hooks | 🔜 Planned | Q1 2027 | +| Performance Expansion | 0.7.0 | Benchmarks, caching | 🔜 Planned | Q1 2027 | +| Ecosystem Expansion | 0.8.0 | Migration tools | 🔜 Planned | Q2 2027 | +| v1.0 | 1.0.0 | Locked API, align with .NET 11 | 🔜 Planned | Q2 2027 | *v 0.1.0 already provides a skeleton of multi tenancy, MFA, reauth etc. Expansion releases will enhance these areas. @@ -51,19 +51,6 @@ UltimateAuth is an open-source auth framework with platform-level capabilities t We keep it up-to-date with current priorities, planned features, and progress. Feel free to follow, comment, or contribute ideas. -
- -> UltimateAuth is currently in the final stage of the first preview release (v 0.1.0-preview). - -> Core architecture is complete and validated through working samples. - -> Ongoing work: -> - Final API surface review -> - Developer experience improvements -> - EF Core integration polishing -> - Documentation refinement -
- --- ## 🌟 Why UltimateAuth @@ -99,7 +86,7 @@ Modern security built-in by default: - Session reuse detection - Device tracking - Hardened auth flows -- Safe defaults with extensibility +- Safe defaults ### 5) Extensible & Lightweight @@ -115,7 +102,8 @@ Designed specifically for real-world .NET environments: - Blazor Server - Blazor WASM -- .NET MAUI +- Blazor Web App +- .NET MAUI & Hybrid Apps - Backend APIs Traditional auth solutions struggle here — UltimateAuth embraces it. @@ -124,56 +112,49 @@ Traditional auth solutions struggle here — UltimateAuth embraces it. # 🚀 Quick Start > ⏱ Takes ~2 minutes to get started +> +> **This Quick Start uses a Blazor Server application with in-memory persistence.** +It is intentionally designed as the simplest path to a working UltimateAuth application. -### 1) Install packages +> For Entity Framework Core, Blazor WebAssembly, Blazor Web App, Resource API, persistent storage, and other real-world configurations, see the [Real-World Setup guide](https://github.com/CodeBeamOrg/UltimateAuth/blob/dev/docs/content/getting-started/real-world-setup.md). + +### 1) Install UltimateAuth -1.1 Core Packages ```bash -dotnet add package CodeBeam.UltimateAuth.Server +dotnet add package CodeBeam.UltimateAuth.InMemory.Bundle dotnet add package CodeBeam.UltimateAuth.Client.Blazor ``` -1.2 Persistence & Reference Packages (Choose One) -```bash -dotnet add package CodeBeam.UltimateAuth.InMemory.Bundle (for debug & development) -dotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle (for production) -``` -### 2) Configure services (in program.cs) -Server registration: -```csharp -builder.Services - .AddUltimateAuthServer() - .AddUltimateAuthEntityFrameworkCore(db => - { - // use with your database provider - db.UseSqlite("Data Source=uauth.db"); - }); -// OR +### 2) Configure UltimateAuth + +Register UltimateAuth in `Program.cs`: +```csharp +// Server registration builder.Services .AddUltimateAuthServer() - .AddUltimateAuthInMemory(); // Development + .AddUltimateAuthInMemory(); +// Client registration +builder.Services.AddUltimateAuthClientBlazor(); ``` -Client registration: -```csharp -builder.Services.AddUltimateAuthClientBlazor(); -``` **Usage by application type:** - **Blazor Server App** → Use both Server and Client registrations - **Blazor WASM / MAUI** → Use Client only -- **Auth Server / Resource API** → Use Server only +- **UAuthHub (Auth Server) / Resource API** → Use Server only + +### 3) Configure the Application Pipeline +Add the UltimateAuth middleware and endpoints: -### 3) Configure pipeline ```csharp // app.UseHttpsRedirection(); // app.UseStaticFiles(); app.UseUltimateAuthWithAspNetCore(); // Includes UseAuthentication() and UseAuthorization() -// Place Antiforgery or something else needed +// Place Antiforgery or something else before endpoint registration if needed app.MapUltimateAuthEndpoints(); app.MapRazorComponents() @@ -181,54 +162,89 @@ app.MapRazorComponents() .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient()); ``` -### 4) Add UAuth Script -Place this in `App.razor` or `index.html` in your Blazor client application: -```csharp - +### 4) Add UAuthApp +UltimateAuth uses `UAuthApp` as the root integration point for its client authentication state and Blazor lifecycle. + +Replace the default router in your `App.razor` or `Routes.razor` with: + +```razor +@using CodeBeam.UltimateAuth.Client.Blazor + + + + @* Add application-wide UI providers or other root components here. *@ + + + +

Not authorized.

+
+
``` -### 5) 🗄️ Database Setup (EF Core) +`UAuthApp` can provide the built-in router, authentication state, and UltimateAuth client lifecycle integration for your component tree. -After configuring UltimateAuth with Entity Framework Core, you need to create and apply database migrations. +> Need full control over routing? -5.1) Install EF Core tools (if not installed) -```bash -dotnet tool install --global dotnet-ef +> UAuthApp also supports applications that provide their own Blazor Router. See the Blazor Routing guide for advanced routing configuration. + +### 5) Add the UltimateAuth Client Script +Place this in `App.razor` or `index.html` in your Blazor client application: +```html + ``` -5.2) Add migration -```bash -dotnet ef migrations add InitUAuth + +### 6) Optional: Blazor Usings +Add this in `_Imports.razor`: +```csharp +@using CodeBeam.UltimateAuth.Client.Blazor ``` -5.3) Update database +### 7) Optional: Add Sample Data +For the fastest way to try auth process, install the UltimateAuth sample seed package: + ```bash -dotnet ef database update +dotnet add package CodeBeam.UltimateAuth.Sample.Seed ``` -💡 Visual Studio (PMC alternative) -If you are using Visual Studio, you can run these commands in Package Manager Console: +Register the development seed: + ```bash -Add-Migration InitUAuth -Context UAuthDbContext -Update-Database -Context UAuthDbContext +builder.Services.AddUltimateAuthSampleSeed(); ``` -⚠️ Notes -- Migrations must be created in your application project, not in the UltimateAuth packages -- You are responsible for managing migrations in production -- Automatic database initialization is not enabled by default -### 6) Optional: Blazor Usings -Add this in `_Imports.razor` +Then seed the application during development: + ```csharp -@using CodeBeam.UltimateAuth.Client.Blazor +if (app.Environment.IsDevelopment()) +{ + await app.SeedUltimateAuthAsync(); +} ``` -### ✅ Done +The development seed includes ready-to-use accounts: + +| Identifier | Secret | +|------------|----------| +| `admin` | `admin` | +| `user` | `user` | + +You can use these credentials to test the auth flows immediately. + +> Development only: Sample users and credentials are intended for evaluation and local development. Do not use them in production. + +### ✅ You're Ready --- ## 💡 Usage -Inject IUAuthClient and simply call methods. +**One Client. Your Auth Application API.** + +For most application-level authentication and identity operations, start with `IUAuthClient`. + +`IUAuthClient` provides a single entry point to UltimateAuth capabilities such as authentication flows, users, sessions, tokens, profiles, credentials, and authorization — without requiring your application code to manage the underlying authentication transport. + +> UltimateAuth treats authentication and identity as application services. Your application works with explicit operations and structured results while UltimateAuth handles the underlying authentication flow. ### Examples Login @@ -239,8 +255,8 @@ private async Task Login() { var request = new LoginRequest { - Identifier = "UAuthUser", - Secret = "UAuthPassword", + Identifier = "admin", + Secret = "admin", }; await UAuthClient.Flows.LoginAsync(request); } @@ -254,9 +270,9 @@ private async Task Register() { var request = new CreateUserRequest { - UserName = _username, - Password = _password, - Email = _email, + UserName = "NewUser", + Password = "NewUserPassword", + Email = "newuser@example.com", }; var result = await UAuthClient.Users.CreateAsync(request); @@ -282,11 +298,14 @@ private async Task LogoutOthersAsync() } ``` -UltimateAuth turns Auth into a simple application service — not a separate system you fight against. -- No manual token handling -- No custom HTTP plumbing -- No fragile redirect logic -- All built-in with extensible options. +With `IUAuthClient`, common application code doesn't need to manually orchestrate: +- token handling +- authentication HTTP calls +- session operations +- redirect plumbing +- client-specific authentication flows + +Start with the simple API. Drop down to UltimateAuth's extensibility points when your application needs more control. --- diff --git a/docs/content/getting-started/real-world-setup.md b/docs/content/getting-started/real-world-setup.md index 92afaeef..27f9e35c 100644 --- a/docs/content/getting-started/real-world-setup.md +++ b/docs/content/getting-started/real-world-setup.md @@ -18,8 +18,9 @@ In real applications, you will typically configure: This guide shows how to set up UltimateAuth for real-world scenarios. ## 🗄️ Using Entity Framework Core +For production, you should use a persistent store. (In-memory provider is volatile and automatically resets on each restart.) -For production, you should use a persistent store. In this setup, you no longer need the `CodeBeam.UltimateAuth.InMemory.Bundle` package. +In this setup, you no longer need the `CodeBeam.UltimateAuth.InMemory.Bundle` package. ### Install Packages @@ -34,26 +35,74 @@ builder.Services .AddUltimateAuthEntityFrameworkCore(db => { db.UseSqlite("Data Source=uauth.db"); - // or UseSqlServer / UseNpgsql + // or UseSqlServer(...) / UseNpgsql(...) / UseMySql(...) etc. }); builder.Services .AddUltimateAuthClientBlazor(); ``` -### Create Database & Migrations +### Database Migrations + +UltimateAuth integrates with Entity Framework Core, but database migrations belong to your application. + +UltimateAuth does not automatically create or apply migrations on your behalf. This keeps your database schema lifecycle under your control and allows migrations to follow the same deployment and review process as the rest of your application. + +After configuring the Entity Framework Core provider, create the initial migration and update the database using either the .NET CLI or Visual Studio Package Manager Console. + +#### Option A — .NET CLI + +If you use the .NET CLI: + +```bash +dotnet ef migrations add InitUAuth --context UAuthDbContext +dotnet ef database update --context UAuthDbContext +``` + +If the dotnet ef command is not available, install the EF Core CLI tool: + ```bash -dotnet ef migrations add InitUAuth -dotnet ef database update +dotnet tool install --global dotnet-ef ``` -or -If you are using Visual Studio, you can run these commands in Package Manager Console*: +Your project also needs the Entity Framework Core design package: + +```bash +dotnet add package Microsoft.EntityFrameworkCore.Design +``` + +#### Option B — Visual Studio Package Manager Console + +If you use Visual Studio, you can perform the same operation from Tools → NuGet Package Manager → Package Manager Console: + ```bash Add-Migration InitUAuth -Context UAuthDbContext Update-Database -Context UAuthDbContext ``` -*Needs `Microsoft.EntityFrameworkCore.Design` and `Microsoft.EntityFrameworkCore.Tools` + +For Package Manager Console tooling, make sure the required EF Core tooling package is available: + +```bash +Install-Package Microsoft.EntityFrameworkCore.Tools +``` + +### Who Owns the Migrations? + +Your application does. + +This is intentional. UltimateAuth provides the authentication and identity model through its Entity Framework Core integration, while your application remains responsible for managing the resulting database schema. + +This means you can: + +- review migrations before applying them, +- include UltimateAuth schema changes in your normal deployment process, +- control when database changes are applied, +- maintain migration history alongside your application, +- use the database provider and deployment strategy appropriate for your environment. + +When upgrading UltimateAuth, review the release notes for persistence-related schema changes and create a new migration when required. + +> **Tip:** Treat UltimateAuth model changes like any other Entity Framework Core model change: upgrade the package, create a migration, review the generated migration, and apply it through your normal deployment process. ## Configure Services With Options UltimateAuth provides rich options for server and client service registration. diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/real-world-setup.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/real-world-setup.json index 867b1a17..f2964dbf 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/real-world-setup.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/real-world-setup.json @@ -1,7 +1,7 @@ { "Slug": "getting-started/real-world-setup", "Title": "Real World Setup", - "Html": "\n\u003Cp\u003EThe Quick Start uses an in-memory setup for simplicity.\nIn real-world applications, you should replace it with a persistent configuration as shown below.\u003C/p\u003E\n\u003Cp\u003EIn real applications, you will typically configure:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA persistent database\u003C/li\u003E\n\u003Cli\u003EAn appropriate client profile\u003C/li\u003E\n\u003Cli\u003EA suitable authentication mode\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis guide shows how to set up UltimateAuth for real-world scenarios.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-entity-framework-core\u0022\u003E\uD83D\uDDC4\uFE0F Using Entity Framework Core\u003C/h2\u003E\n\u003Cp\u003EFor production, you should use a persistent store. In this setup, you no longer need the \u003Ccode\u003ECodeBeam.UltimateAuth.InMemory.Bundle\u003C/code\u003E package.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-packages\u0022\u003EInstall Packages\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services\u0022\u003EConfigure Services\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services\n .AddUltimateAuthServer()\n .AddUltimateAuthEntityFrameworkCore(db =\u0026gt;\n {\n db.UseSqlite(\u0026quot;Data Source=uauth.db\u0026quot;);\n // or UseSqlServer / UseNpgsql\n });\n\nbuilder.Services\n .AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-database-migrations\u0022\u003ECreate Database \u0026amp; Migrations\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet ef migrations add InitUAuth\ndotnet ef database update\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003Eor\u003C/p\u003E\n\u003Cp\u003EIf you are using Visual Studio, you can run these commands in Package Manager Console*:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003EAdd-Migration InitUAuth -Context UAuthDbContext\nUpdate-Database -Context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E*Needs \u003Ccode\u003EMicrosoft.EntityFrameworkCore.Design\u003C/code\u003E and \u003Ccode\u003EMicrosoft.EntityFrameworkCore.Tools\u003C/code\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services-with-options\u0022\u003EConfigure Services With Options\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides rich options for server and client service registration.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(o =\u0026gt; {\n o.Diagnostics.EnableRefreshDetails = true;\n o.Login.MaxFailedAttempts = 4;\n o.Identifiers.AllowMultipleUsernames = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-standalone-wasm-setup\u0022\u003EBlazor Standalone WASM Setup\u003C/h2\u003E\n\u003Cp\u003EBlazor WASM applications run entirely on the client and cannot securely handle credentials.\nFor this reason, UltimateAuth uses a dedicated Auth server called \u003Cstrong\u003EUAuthHub\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EWASM \u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Endpoints.BasePath = \u0026quot;https://localhost:6110/auth\u0026quot;; // UAuthHub URL\n o.Pkce.ReturnUrl = \u0026quot;https://localhost:6130/home\u0026quot;; // Your (WASM) application domain \u002B return path\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EUAuthHub \u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthInMemory()\n .AddUAuthHub(o =\u0026gt; o.AllowedClientOrigins.Add(\u0026quot;https://localhost:6130\u0026quot;)); // WASM application\u0027s URL\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EUAuthHub Pipeline Configuration\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.MapUltimateAuthEndpoints();\napp.MapUAuthHub();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-web-app-setup\u0022\u003EBlazor Web App Setup\u003C/h2\u003E\n\u003Cp\u003EA blazor web app contains two projects that includes host and client. You need to arrange them both.\u003C/p\u003E\n\u003Cp\u003EIn the host project:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;; // UAuthHub URL\n o.Pkce.ReturnUrl = \u0026quot;https://localhost:6132/home\u0026quot;; // Current application domain \u002B path\n});\n\n// In pipeline configuration\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveWebAssemblyRenderMode()\n .AddAdditionalAssemblies(UAuthAssemblies.BlazorClient().First());\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EIn the client project:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;; // UAuthHub URL\n o.Pkce.ReturnUrl = \u0026quot;https://localhost:6132/home\u0026quot;; // Current application domain \u002B path\n});\n\nbuilder.Services.AddScoped(sp =\u0026gt; new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });\n\n// Optional if you use external API calls in your client project.\nbuilder.Services.AddHttpClient(\u0026quot;resourceApi\u0026quot;, client =\u0026gt;\n{\n client.BaseAddress = new Uri(\u0026quot;https://localhost:6122\u0026quot;);\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EIf you want to use embedded UAuthHub in host project, you can register server services as shown in quickstart.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u2139\uFE0F UltimateAuth automatically selects the appropriate authentication mode (PureOpaque, Hybrid, etc.) based on the client type.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022resourceapi-setup\u0022\u003EResourceApi Setup\u003C/h2\u003E\n\u003Cp\u003EYou may want to secure your custom API with UltimateAuth. UltimateAuth provides a lightweight option for this case. (ResourceApi doesn\u0027t have to be a blazor application, it can be any server-side project like MVC.)\u003C/p\u003E\n\u003Cp\u003EResourceApi\u0027s \u003Ccode\u003EProgram.cs\u003C/code\u003E\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthResourceApi(o =\u0026gt;\n {\n o.UAuthHubBaseUrl = \u0026quot;https://localhost:6110\u0026quot;;\n o.AllowedClientOrigins.Add(\u0026quot;https://localhost:6130\u0026quot;);\n });\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EConfigure pipeline:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.UseUltimateAuthResourceApiWithAspNetCore();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003ENotes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EResourceApi should connect with an UAuthHub, not a pure-server. Make sure \u003Ccode\u003E.AddUAuthHub()\u003C/code\u003E after calling \u003Ccode\u003Ebuilder.Services.AddUltimateAuthServer()\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EUltimateAuth automatically configures CORS based on the provided origins.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUse ResourceApi when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou have a separate backend API\u003C/li\u003E\n\u003Cli\u003EYou want to validate sessions or tokens externally\u003C/li\u003E\n\u003Cli\u003EYour API is not hosting UltimateAuth directly\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-to-think-about-setup\u0022\u003E\uD83E\uDDE0 How to Think About Setup\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EServer\u003C/strong\u003E manages authentication flows and sessions\u003C/li\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EClient\u003C/strong\u003E interacts through flows (not tokens directly)\u003C/li\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EStorage layer\u003C/strong\u003E (InMemory / EF Core) defines persistence\u003C/li\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EApplication type\u003C/strong\u003E determines runtime behavior\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You configure the system once, and UltimateAuth adapts automatically.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EThe Quick Start uses an in-memory setup for simplicity.\nIn real-world applications, you should replace it with a persistent configuration as shown below.\u003C/p\u003E\n\u003Cp\u003EIn real applications, you will typically configure:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA persistent database\u003C/li\u003E\n\u003Cli\u003EAn appropriate client profile\u003C/li\u003E\n\u003Cli\u003EA suitable authentication mode\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis guide shows how to set up UltimateAuth for real-world scenarios.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-entity-framework-core\u0022\u003E\uD83D\uDDC4\uFE0F Using Entity Framework Core\u003C/h2\u003E\n\u003Cp\u003EFor production, you should use a persistent store. (In-memory provider is volatile and automatically resets on each restart.)\u003C/p\u003E\n\u003Cp\u003EIn this setup, you no longer need the \u003Ccode\u003ECodeBeam.UltimateAuth.InMemory.Bundle\u003C/code\u003E package.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-packages\u0022\u003EInstall Packages\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services\u0022\u003EConfigure Services\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services\n .AddUltimateAuthServer()\n .AddUltimateAuthEntityFrameworkCore(db =\u0026gt;\n {\n db.UseSqlite(\u0026quot;Data Source=uauth.db\u0026quot;);\n // or UseSqlServer(...) / UseNpgsql(...) / UseMySql(...) etc.\n });\n\nbuilder.Services\n .AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022database-migrations\u0022\u003EDatabase Migrations\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth integrates with Entity Framework Core, but database migrations belong to your application.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth does not automatically create or apply migrations on your behalf. This keeps your database schema lifecycle under your control and allows migrations to follow the same deployment and review process as the rest of your application.\u003C/p\u003E\n\u003Cp\u003EAfter configuring the Entity Framework Core provider, create the initial migration and update the database using either the .NET CLI or Visual Studio Package Manager Console.\u003C/p\u003E\n\u003Ch4 id=\u0022option-a.net-cli\u0022\u003EOption A \u2014 .NET CLI\u003C/h4\u003E\n\u003Cp\u003EIf you use the .NET CLI:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet ef migrations add InitUAuth --context UAuthDbContext\ndotnet ef database update --context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EIf the dotnet ef command is not available, install the EF Core CLI tool:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet tool install --global dotnet-ef\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EYour project also needs the Entity Framework Core design package:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package Microsoft.EntityFrameworkCore.Design\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch4 id=\u0022option-b-visual-studio-package-manager-console\u0022\u003EOption B \u2014 Visual Studio Package Manager Console\u003C/h4\u003E\n\u003Cp\u003EIf you use Visual Studio, you can perform the same operation from Tools \u2192 NuGet Package Manager \u2192 Package Manager Console:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003EAdd-Migration InitUAuth -Context UAuthDbContext\nUpdate-Database -Context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EFor Package Manager Console tooling, make sure the required EF Core tooling package is available:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003EInstall-Package Microsoft.EntityFrameworkCore.Tools\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022who-owns-the-migrations\u0022\u003EWho Owns the Migrations?\u003C/h3\u003E\n\u003Cp\u003EYour application does.\u003C/p\u003E\n\u003Cp\u003EThis is intentional. UltimateAuth provides the authentication and identity model through its Entity Framework Core integration, while your application remains responsible for managing the resulting database schema.\u003C/p\u003E\n\u003Cp\u003EThis means you can:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ereview migrations before applying them,\u003C/li\u003E\n\u003Cli\u003Einclude UltimateAuth schema changes in your normal deployment process,\u003C/li\u003E\n\u003Cli\u003Econtrol when database changes are applied,\u003C/li\u003E\n\u003Cli\u003Emaintain migration history alongside your application,\u003C/li\u003E\n\u003Cli\u003Euse the database provider and deployment strategy appropriate for your environment.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EWhen upgrading UltimateAuth, review the release notes for persistence-related schema changes and create a new migration when required.\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003ETip:\u003C/strong\u003E Treat UltimateAuth model changes like any other Entity Framework Core model change: upgrade the package, create a migration, review the generated migration, and apply it through your normal deployment process.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services-with-options\u0022\u003EConfigure Services With Options\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides rich options for server and client service registration.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(o =\u0026gt; {\n o.Diagnostics.EnableRefreshDetails = true;\n o.Login.MaxFailedAttempts = 4;\n o.Identifiers.AllowMultipleUsernames = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-standalone-wasm-setup\u0022\u003EBlazor Standalone WASM Setup\u003C/h2\u003E\n\u003Cp\u003EBlazor WASM applications run entirely on the client and cannot securely handle credentials.\nFor this reason, UltimateAuth uses a dedicated Auth server called \u003Cstrong\u003EUAuthHub\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EWASM \u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Endpoints.BasePath = \u0026quot;https://localhost:6110/auth\u0026quot;; // UAuthHub URL\n o.Pkce.ReturnUrl = \u0026quot;https://localhost:6130/home\u0026quot;; // Your (WASM) application domain \u002B return path\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EUAuthHub \u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthInMemory()\n .AddUAuthHub(o =\u0026gt; o.AllowedClientOrigins.Add(\u0026quot;https://localhost:6130\u0026quot;)); // WASM application\u0027s URL\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EUAuthHub Pipeline Configuration\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.MapUltimateAuthEndpoints();\napp.MapUAuthHub();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-web-app-setup\u0022\u003EBlazor Web App Setup\u003C/h2\u003E\n\u003Cp\u003EA blazor web app contains two projects that includes host and client. You need to arrange them both.\u003C/p\u003E\n\u003Cp\u003EIn the host project:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;; // UAuthHub URL\n o.Pkce.ReturnUrl = \u0026quot;https://localhost:6132/home\u0026quot;; // Current application domain \u002B path\n});\n\n// In pipeline configuration\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveWebAssemblyRenderMode()\n .AddAdditionalAssemblies(UAuthAssemblies.BlazorClient().First());\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EIn the client project:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;; // UAuthHub URL\n o.Pkce.ReturnUrl = \u0026quot;https://localhost:6132/home\u0026quot;; // Current application domain \u002B path\n});\n\nbuilder.Services.AddScoped(sp =\u0026gt; new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });\n\n// Optional if you use external API calls in your client project.\nbuilder.Services.AddHttpClient(\u0026quot;resourceApi\u0026quot;, client =\u0026gt;\n{\n client.BaseAddress = new Uri(\u0026quot;https://localhost:6122\u0026quot;);\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EIf you want to use embedded UAuthHub in host project, you can register server services as shown in quickstart.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u2139\uFE0F UltimateAuth automatically selects the appropriate authentication mode (PureOpaque, Hybrid, etc.) based on the client type.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022resourceapi-setup\u0022\u003EResourceApi Setup\u003C/h2\u003E\n\u003Cp\u003EYou may want to secure your custom API with UltimateAuth. UltimateAuth provides a lightweight option for this case. (ResourceApi doesn\u0027t have to be a blazor application, it can be any server-side project like MVC.)\u003C/p\u003E\n\u003Cp\u003EResourceApi\u0027s \u003Ccode\u003EProgram.cs\u003C/code\u003E\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthResourceApi(o =\u0026gt;\n {\n o.UAuthHubBaseUrl = \u0026quot;https://localhost:6110\u0026quot;;\n o.AllowedClientOrigins.Add(\u0026quot;https://localhost:6130\u0026quot;);\n });\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EConfigure pipeline:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.UseUltimateAuthResourceApiWithAspNetCore();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003ENotes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EResourceApi should connect with an UAuthHub, not a pure-server. Make sure \u003Ccode\u003E.AddUAuthHub()\u003C/code\u003E after calling \u003Ccode\u003Ebuilder.Services.AddUltimateAuthServer()\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EUltimateAuth automatically configures CORS based on the provided origins.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUse ResourceApi when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou have a separate backend API\u003C/li\u003E\n\u003Cli\u003EYou want to validate sessions or tokens externally\u003C/li\u003E\n\u003Cli\u003EYour API is not hosting UltimateAuth directly\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-to-think-about-setup\u0022\u003E\uD83E\uDDE0 How to Think About Setup\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EServer\u003C/strong\u003E manages authentication flows and sessions\u003C/li\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EClient\u003C/strong\u003E interacts through flows (not tokens directly)\u003C/li\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EStorage layer\u003C/strong\u003E (InMemory / EF Core) defines persistence\u003C/li\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EApplication type\u003C/strong\u003E determines runtime behavior\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You configure the system once, and UltimateAuth adapts automatically.\u003C/p\u003E\n", "Headings": [ { "Id": "using-entity-framework-core", @@ -19,8 +19,13 @@ "Level": 1 }, { - "Id": "create-database-migrations", - "Text": "Create Database \u0026 Migrations", + "Id": "database-migrations", + "Text": "Database Migrations", + "Level": 1 + }, + { + "Id": "who-owns-the-migrations", + "Text": "Who Owns the Migrations?", "Level": 1 }, { diff --git a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub.EFCore/Components/Pages/Home.razor.cs b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub.EFCore/Components/Pages/Home.razor.cs index d8cac511..7897aca7 100644 --- a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub.EFCore/Components/Pages/Home.razor.cs +++ b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub.EFCore/Components/Pages/Home.razor.cs @@ -141,7 +141,7 @@ private async Task ResolveReturnUrlAsync() if (!string.IsNullOrWhiteSpace(fromContext)) return fromContext; - var uri = Nav.ToAbsoluteUri(Nav.Uri); + var uri = Navigation.ToAbsoluteUri(Navigation.Uri); var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query); if (query.TryGetValue(UAuthConstants.Query.ReturnUrl, out var ru) && !string.IsNullOrWhiteSpace(ru)) @@ -154,7 +154,7 @@ private async Task ResolveReturnUrlAsync() return flow.ReturnUrl!; } - return Nav.Uri; + return Navigation.Uri; } private async void StartCountdown() diff --git a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Pages/Home.razor.cs b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Pages/Home.razor.cs index d25ff1f1..a5f93cee 100644 --- a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Pages/Home.razor.cs +++ b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Pages/Home.razor.cs @@ -141,7 +141,7 @@ private async Task ResolveReturnUrlAsync() if (!string.IsNullOrWhiteSpace(fromContext)) return fromContext; - var uri = Nav.ToAbsoluteUri(Nav.Uri); + var uri = Navigation.ToAbsoluteUri(Navigation.Uri); var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query); if (query.TryGetValue(UAuthConstants.Query.ReturnUrl, out var ru) && !string.IsNullOrWhiteSpace(ru)) @@ -154,7 +154,7 @@ private async Task ResolveReturnUrlAsync() return flow.ReturnUrl!; } - return Nav.Uri; + return Navigation.Uri; } private async void StartCountdown() diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Login.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Login.razor index 385dfad8..5eec659a 100644 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Login.razor +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Login.razor @@ -34,7 +34,7 @@ - + Continue diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/ResetCredential.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/ResetCredential.razor.cs index 71a4d93e..2cd5e204 100644 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/ResetCredential.razor.cs +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/ResetCredential.razor.cs @@ -37,7 +37,7 @@ private async Task ResetPasswordAsync() if (result.IsSuccess) { Snackbar.Add("Credential reset successfully. Please log in with your new password.", Severity.Success); - Nav.NavigateTo("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/login"); + Navigation.NavigateTo("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/login"); } else { diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Pages/Login.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Pages/Login.razor index 385dfad8..5eec659a 100644 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Pages/Login.razor +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Pages/Login.razor @@ -34,7 +34,7 @@ - + Continue diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Pages/ResetCredential.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Pages/ResetCredential.razor.cs index 9bcaf5f7..681ff884 100644 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Pages/ResetCredential.razor.cs +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Pages/ResetCredential.razor.cs @@ -37,7 +37,7 @@ private async Task ResetPasswordAsync() if (result.IsSuccess) { Snackbar.Add("Credential reset successfully. Please log in with your new password.", Severity.Success); - Nav.NavigateTo("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/login"); + Navigation.NavigateTo("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/login"); } else { diff --git a/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/Login.razor b/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/Login.razor index 57dcf3ff..595addf8 100644 --- a/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/Login.razor +++ b/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/Login.razor @@ -34,7 +34,7 @@ - + Continue diff --git a/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/Login.razor.cs b/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/Login.razor.cs index ef45eee9..78129102 100644 --- a/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/Login.razor.cs +++ b/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/Login.razor.cs @@ -83,7 +83,7 @@ private async Task StartPkceLogin() { string? returnUrl = null; if (!string.IsNullOrEmpty(ReturnUrl)) - returnUrl = Nav.BaseUri + ReturnUrl.TrimStart('/'); + returnUrl = Navigation.BaseUri + ReturnUrl.TrimStart('/'); await UAuthClient.Flows.BeginPkceAsync(returnUrl); } diff --git a/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/ResetCredential.razor.cs b/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/ResetCredential.razor.cs index b76e12b1..43e7b541 100644 --- a/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/ResetCredential.razor.cs +++ b/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/ResetCredential.razor.cs @@ -37,7 +37,7 @@ private async Task ResetPasswordAsync() if (result.IsSuccess) { Snackbar.Add("Credential reset successfully. Please log in with your new password.", Severity.Success); - Nav.NavigateTo("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/login"); + Navigation.NavigateTo("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/login"); } else { diff --git a/samples/int-wasm/CodeBeam.UAuth.Sample.IntWasm/CodeBeam.UAuth.Sample.IntWasm.Client/Pages/Login.razor b/samples/int-wasm/CodeBeam.UAuth.Sample.IntWasm/CodeBeam.UAuth.Sample.IntWasm.Client/Pages/Login.razor index de5b1771..43e409a7 100644 --- a/samples/int-wasm/CodeBeam.UAuth.Sample.IntWasm/CodeBeam.UAuth.Sample.IntWasm.Client/Pages/Login.razor +++ b/samples/int-wasm/CodeBeam.UAuth.Sample.IntWasm/CodeBeam.UAuth.Sample.IntWasm.Client/Pages/Login.razor @@ -35,7 +35,7 @@ - + Continue diff --git a/samples/int-wasm/CodeBeam.UAuth.Sample.IntWasm/CodeBeam.UAuth.Sample.IntWasm.Client/Pages/Login.razor.cs b/samples/int-wasm/CodeBeam.UAuth.Sample.IntWasm/CodeBeam.UAuth.Sample.IntWasm.Client/Pages/Login.razor.cs index 0e0139e8..2cb3f032 100644 --- a/samples/int-wasm/CodeBeam.UAuth.Sample.IntWasm/CodeBeam.UAuth.Sample.IntWasm.Client/Pages/Login.razor.cs +++ b/samples/int-wasm/CodeBeam.UAuth.Sample.IntWasm/CodeBeam.UAuth.Sample.IntWasm.Client/Pages/Login.razor.cs @@ -83,7 +83,7 @@ private async Task StartPkceLogin() { string? returnUrl = null; if (!string.IsNullOrEmpty(ReturnUrl)) - returnUrl = Nav.BaseUri + ReturnUrl.TrimStart('/'); + returnUrl = Navigation.BaseUri + ReturnUrl.TrimStart('/'); await UAuthClient.Flows.BeginPkceAsync(returnUrl); } diff --git a/samples/int-wasm/CodeBeam.UAuth.Sample.IntWasm/CodeBeam.UAuth.Sample.IntWasm.Client/Pages/ResetCredential.razor.cs b/samples/int-wasm/CodeBeam.UAuth.Sample.IntWasm/CodeBeam.UAuth.Sample.IntWasm.Client/Pages/ResetCredential.razor.cs index fc9942f0..be79c63a 100644 --- a/samples/int-wasm/CodeBeam.UAuth.Sample.IntWasm/CodeBeam.UAuth.Sample.IntWasm.Client/Pages/ResetCredential.razor.cs +++ b/samples/int-wasm/CodeBeam.UAuth.Sample.IntWasm/CodeBeam.UAuth.Sample.IntWasm.Client/Pages/ResetCredential.razor.cs @@ -37,7 +37,7 @@ private async Task ResetPasswordAsync() if (result.IsSuccess) { Snackbar.Add("Credential reset successfully. Please log in with your new password.", Severity.Success); - Nav.NavigateTo("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/login"); + Navigation.NavigateTo("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/login"); } else { diff --git a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Attributes/UAuthAuthorizeAttribute.cs b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Attributes/UAuthAuthorizeAttribute.cs index 971364ac..ae253156 100644 --- a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Attributes/UAuthAuthorizeAttribute.cs +++ b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Attributes/UAuthAuthorizeAttribute.cs @@ -1,8 +1,37 @@ namespace CodeBeam.UltimateAuth.Client.Blazor; +/// +/// Declares UltimateAuth authorization requirements for a Blazor component or page. +/// +/// +/// +/// The attribute can be used to associate role and permission requirements with routable or authorization-aware Blazor components. +/// +/// +/// Role and permission values are expressed as comma-separated lists. The effective +/// authorization behavior is determined by the UltimateAuth authorization pipeline that consumes this metadata. +/// +/// +/// This attribute describes authorization requirements only. It does not perform authorization by itself. +/// +/// [AttributeUsage(AttributeTargets.Class)] public sealed class UAuthAuthorizeAttribute : Attribute { + /// + /// Gets or sets the comma-separated roles associated with the authorization requirement. + /// + /// + /// A , empty, or whitespace value indicates that no explicit role requirement is declared by this property. + /// public string? Roles { get; set; } + + /// + /// Gets or sets the comma-separated UltimateAuth permissions associated with the authorization requirement. + /// + /// + /// A , empty, or whitespace value indicates that no explicit + /// permission requirement is declared by this property. + /// public string? Permissions { get; set; } } diff --git a/src/client/CodeBeam.UltimateAuth.Client.Blazor/CodeBeam.UltimateAuth.Client.Blazor.csproj b/src/client/CodeBeam.UltimateAuth.Client.Blazor/CodeBeam.UltimateAuth.Client.Blazor.csproj index 02509da9..73d5c800 100644 --- a/src/client/CodeBeam.UltimateAuth.Client.Blazor/CodeBeam.UltimateAuth.Client.Blazor.csproj +++ b/src/client/CodeBeam.UltimateAuth.Client.Blazor/CodeBeam.UltimateAuth.Client.Blazor.csproj @@ -2,7 +2,6 @@ net8.0;net9.0;net10.0 - $(NoWarn);1591 CodeBeam.UltimateAuth.Client.Blazor diff --git a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/Base/UAuthComponentBase.cs b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/Base/UAuthComponentBase.cs index 300a3855..d2462d35 100644 --- a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/Base/UAuthComponentBase.cs +++ b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/Base/UAuthComponentBase.cs @@ -19,13 +19,18 @@ public abstract class UAuthComponentBase : ComponentBase, IDisposable /// /// Gets the Blazor navigation service. /// - [Inject] protected NavigationManager Nav { get; set; } = default!; + [Inject] protected NavigationManager Navigation { get; set; } = default!; /// /// Automatically re-render when UAuthState changes. Can be overridden to disable. /// protected virtual bool AutoRefreshOnAuthStateChanged => true; + /// + /// Called when the component's parameters have been set. This method ensures that the component is properly registered + /// with the current and evaluates authorization requirements. + /// + /// protected override void OnParametersSet() { base.OnParametersSet(); @@ -46,6 +51,11 @@ protected override void OnParametersSet() EvaluateAuthorization(); } + /// + /// Called after the component has been rendered. This method sets the _rendered flag to true on the first render. + /// + /// + /// protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); @@ -117,7 +127,7 @@ private void EvaluateAuthorization() /// protected virtual void OnUnauthorized() { - Nav.NavigateTo("/"); + Navigation.NavigateTo("/"); } /// @@ -125,9 +135,12 @@ protected virtual void OnUnauthorized() /// protected virtual void OnForbidden() { - Nav.NavigateTo("/forbidden"); + Navigation.NavigateTo("/forbidden"); } + /// + /// Disposes of the component and unsubscribes from the event to prevent memory leaks. + /// public virtual void Dispose() { if (_previousState is not null) diff --git a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/Base/UAuthHubLayoutBase.cs b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/Base/UAuthHubLayoutBase.cs index e8141391..086dd486 100644 --- a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/Base/UAuthHubLayoutBase.cs +++ b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/Base/UAuthHubLayoutBase.cs @@ -5,20 +5,67 @@ namespace CodeBeam.UltimateAuth.Client.Blazor; +/// +/// Base class for Blazor layouts that participate in an UltimateAuth Hub flow. +/// +/// +/// +/// The layout resolves the Hub session identifier from the current navigation URI +/// and exposes the corresponding to derived layouts. +/// +/// +/// Hub state is obtained through . An absent or invalid +/// Hub session identifier results in no current Hub state. +/// +/// +/// This type provides Hub flow state to the UI and does not itself authorize, +/// complete, or otherwise make security decisions for the authentication flow. +/// +/// public abstract class UAuthHubLayoutBase : LayoutComponentBase { + /// + /// Gets the navigation service used to inspect the current URI. + /// [Inject] protected NavigationManager Navigation { get; set; } = default!; + + /// + /// Gets the Hub flow reader used to retrieve the state of the current Hub session. + /// [Inject] protected IHubFlowReader HubFlowReader { get; set; } = default!; + /// + /// Gets the state associated with the current Hub session, when one can be resolved. + /// + /// + /// The value is when the current URI does not contain a Hub + /// session identifier, the identifier is invalid, or no state has been loaded. + /// protected HubFlowState? HubState { get; private set; } + /// + /// Gets a value indicating whether the resolved Hub flow exists. + /// protected bool HasHub => HubState?.Exists == true; + + /// + /// Gets a value indicating whether the resolved Hub flow exists and is active. + /// protected bool IsHubActive => HasHub && HubState?.IsActive == true; + + /// + /// Gets a value indicating whether the resolved Hub flow has expired. + /// protected bool IsExpired => HubState?.IsExpired == true; + + /// + /// Gets the error associated with the resolved Hub flow, if any. + /// protected HubErrorCode? Error => HubState?.Error; private string? _lastHubKey; + /// protected override async Task OnParametersSetAsync() { await base.OnParametersSetAsync(); @@ -46,6 +93,17 @@ protected override async Task OnParametersSetAsync() } } + /// + /// Resolves the Hub session identifier associated with the current navigation URI. + /// + /// + /// The raw Hub session identifier when present; otherwise, . + /// + /// + /// The default implementation reads from + /// the current query string. Derived layouts may override this method to provide + /// the Hub session identifier from another source. + /// protected virtual string? ResolveHubKey() { var uri = Navigation.ToAbsoluteUri(Navigation.Uri); diff --git a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/Base/UAuthHubPageBase.cs b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/Base/UAuthHubPageBase.cs index bccb68ab..f4063ab0 100644 --- a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/Base/UAuthHubPageBase.cs +++ b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/Base/UAuthHubPageBase.cs @@ -5,24 +5,77 @@ namespace CodeBeam.UltimateAuth.Client.Blazor; +/// +/// Base class for Blazor pages that participate in an UltimateAuth Hub flow. +/// +/// +/// +/// The page receives the Hub session identifier from the current query string +/// and exposes the corresponding to derived pages. +/// +/// +/// Hub state is obtained through and is automatically +/// loaded when component parameters are processed. Derived pages can explicitly +/// refresh the state by calling . +/// +/// +/// This type provides Hub flow state to the UI and does not itself authorize, +/// complete, or otherwise make security decisions for the authentication flow. +/// +/// public abstract class UAuthHubPageBase : UAuthComponentBase { + /// + /// Gets the Hub flow reader used to retrieve the state of the current Hub session. + /// [Inject] protected IHubFlowReader HubFlowReader { get; set; } = default!; + /// + /// Gets or sets the raw Hub session identifier supplied by the current query string. + /// + /// + /// The value is supplied from . + /// It is validated as a before Hub state is read. + /// [Parameter] [SupplyParameterFromQuery(Name = UAuthConstants.Query.Hub)] public string? HubKey { get; set; } + /// + /// Gets the state associated with the current Hub session, when one can be resolved. + /// + /// + /// The value is when no Hub session identifier is supplied + /// or when the supplied identifier is invalid. + /// protected HubFlowState? HubState { get; private set; } + /// + /// Gets a value indicating whether the resolved Hub flow exists and is active. + /// protected bool IsHubActive => HubState is { Exists: true, IsActive: true }; + /// protected override async Task OnParametersSetAsync() { await base.OnParametersSetAsync(); await ReloadStateAsync(); } + /// + /// Reloads the Hub flow state associated with the current . + /// + /// + /// + /// When is missing, empty, or invalid, is cleared. + /// + /// + /// A valid Hub session identifier is resolved through . + /// + /// + /// + /// A task that represents the asynchronous reload operation. + /// public async Task ReloadStateAsync() { if (string.IsNullOrWhiteSpace(HubKey)) diff --git a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/Base/UAuthPageBase.cs b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/Base/UAuthPageBase.cs index 83fa544f..a4af1c65 100644 --- a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/Base/UAuthPageBase.cs +++ b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/Base/UAuthPageBase.cs @@ -45,11 +45,12 @@ public abstract class UAuthPageBase : UAuthComponentBase private string? _lastParsedUri; private bool _payloadConsumed; + /// protected override void OnParametersSet() { base.OnParametersSet(); - var currentUri = Nav.Uri; + var currentUri = Navigation.Uri; if (string.Equals(_lastParsedUri, currentUri, StringComparison.Ordinal)) return; @@ -58,7 +59,7 @@ protected override void OnParametersSet() _payloadConsumed = false; - var uri = Nav.ToAbsoluteUri(currentUri); + var uri = Navigation.ToAbsoluteUri(currentUri); var query = QueryHelpers.ParseQuery(uri.Query); ShouldFocus = query.TryGetValue(UAuthConstants.Query.Focus, out var focus) && focus == "1"; @@ -84,6 +85,7 @@ protected override void OnParametersSet() _needsClear = ClearUAuthQueryAfterParse && HasUAuthPageQuery(query); } + /// protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); @@ -99,8 +101,8 @@ protected override async Task OnAfterRenderAsync(bool firstRender) _needsClear = false; var cleanUri = BuildUriWithoutConsumedUAuthQuery(); - if (!string.Equals(cleanUri, Nav.Uri, StringComparison.Ordinal)) - Nav.NavigateTo(cleanUri, replace: true); + if (!string.Equals(cleanUri, Navigation.Uri, StringComparison.Ordinal)) + Navigation.NavigateTo(cleanUri, replace: true); } } @@ -135,12 +137,24 @@ protected bool TryConsumePayload(out AuthFlowPayload? payload) return true; } + /// + /// Called when a new UltimateAuth flow payload is available for the current page. + /// This method is called only once per parsed URL, and only when a valid payload is present. + /// + /// + /// protected virtual Task OnUAuthPayloadAsync(AuthFlowPayload payload) => Task.CompletedTask; + + /// + /// Called when a focus request is present for the current page. + /// This method is called only once per parsed URL, and only when a focus request is present. + /// + /// protected virtual Task OnFocusRequestedAsync() => Task.CompletedTask; private string BuildUriWithoutConsumedUAuthQuery() { - var uri = Nav.ToAbsoluteUri(Nav.Uri); + var uri = Navigation.ToAbsoluteUri(Navigation.Uri); var query = QueryHelpers.ParseQuery(uri.Query); var remaining = query diff --git a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/UAuthApp.razor.cs b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/UAuthApp.razor.cs index d5132c0d..46ec4b79 100644 --- a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/UAuthApp.razor.cs +++ b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/UAuthApp.razor.cs @@ -111,11 +111,13 @@ public partial class UAuthApp [Parameter] public EventCallback OnReauthRequired { get; set; } + /// protected override async Task OnInitializedAsync() { Coordinator.ReauthRequired += HandleReauthRequired; } + /// protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) @@ -190,6 +192,10 @@ private IEnumerable GetAdditionalAssemblies() return Enumerable.Empty(); } + /// + /// Disposes the component and stops the session coordinator if it was started. + /// + /// public async ValueTask DisposeAsync() { StateManager.State.Changed -= OnStateChanged; diff --git a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/UAuthLoginForm.razor.cs b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/UAuthLoginForm.razor.cs index 5ea58eb4..7ff71a56 100644 --- a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/UAuthLoginForm.razor.cs +++ b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/UAuthLoginForm.razor.cs @@ -10,6 +10,9 @@ namespace CodeBeam.UltimateAuth.Client.Blazor; +/// +/// Represents a Blazor component that provides a login form for UltimateAuth authentication. +/// public partial class UAuthLoginForm { [Inject] @@ -98,6 +101,7 @@ public partial class UAuthLoginForm private HubFlowState? _flow; private DeviceId? _deviceId; + /// protected override async Task OnParametersSetAsync() { await base.OnParametersSetAsync(); @@ -113,6 +117,7 @@ protected override async Task OnParametersSetAsync() } } + /// protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) @@ -122,6 +127,10 @@ protected override async Task OnAfterRenderAsync(bool firstRender) StateHasChanged(); } + /// + /// Asynchronously reloads the credentials associated with the current hub session, if applicable. + /// + /// protected async Task ReloadCredentialsAsync() { if (LoginType != UAuthLoginType.Pkce) @@ -133,6 +142,10 @@ protected async Task ReloadCredentialsAsync() _credentials = await HubCredentialResolver.ResolveAsync(EffectiveHubSessionId.Value); } + /// + /// Asynchronously reloads the state associated with the current hub session, if applicable. + /// + /// protected async Task ReloadStateAsync() { if (LoginType != UAuthLoginType.Pkce || EffectiveHubSessionId is null || HubFlowReader is null) diff --git a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/UAuthScope.razor.cs b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/UAuthScope.razor.cs index 751b579b..9240307a 100644 --- a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/UAuthScope.razor.cs +++ b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/UAuthScope.razor.cs @@ -2,8 +2,15 @@ namespace CodeBeam.UltimateAuth.Client.Blazor; +/// +/// A Blazor component that defines a scope for UltimateAuth authentication and authorization. +/// It can be used to group child components that require specific authentication or authorization context. +/// public partial class UAuthScope : UAuthComponentBase { + /// + /// Gets or sets the child content to be rendered within this scope. + /// [Parameter] public RenderFragment? ChildContent { get; set; } } diff --git a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/UAuthStateView.razor.cs b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/UAuthStateView.razor.cs index e88ccfdd..bdce111b 100644 --- a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/UAuthStateView.razor.cs +++ b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Components/UAuthStateView.razor.cs @@ -5,6 +5,9 @@ namespace CodeBeam.UltimateAuth.Client.Blazor; +/// +/// A Blazor component that conditionally renders content based on the current UltimateAuth authentication state and authorization requirements. +/// public partial class UAuthStateView : UAuthComponentBase { private IReadOnlyList _rolesParsed = Array.Empty(); @@ -16,27 +19,51 @@ public partial class UAuthStateView : UAuthComponentBase private string? _rolesRaw; private string? _permissionsRaw; + /// + /// Gets or sets the content to render when the user is authorized. The content receives the current as a parameter. + /// [Parameter] public RenderFragment? Authorized { get; set; } + /// + /// Gets or sets the content to render when the user is not authorized. This content is displayed when the user does not meet the specified authorization requirements. + /// [Parameter] public RenderFragment? NotAuthorized { get; set; } + /// + /// Gets or sets the content to render when the user is inactive. This content is displayed when the user's session state is not active, and the parameter is set to true. + /// [Parameter] public RenderFragment? Inactive { get; set; } + /// + /// Gets or sets the content to render while the authorization evaluation is in progress. This content is displayed when the component is determining whether the user meets the specified authorization requirements. + /// [Parameter] public RenderFragment? Authorizing { get; set; } + /// + /// Gets or sets the content to render regardless of the user's authorization state. This content is always displayed, and it receives the current as a parameter. + /// [Parameter] public RenderFragment? ChildContent { get; set; } + /// + /// Gets or sets a comma-separated list of roles that the user must have to be considered authorized. The roles are evaluated based on the specified . + /// [Parameter] public string? Roles { get; set; } + /// + /// Gets or sets a comma-separated list of permissions that the user must have to be considered authorized. The permissions are evaluated based on the specified . + /// [Parameter] public string? Permissions { get; set; } + /// + /// Gets or sets the name of a policy that the user must satisfy to be considered authorized. The policy is evaluated based on the specified . + /// [Parameter] public string? Policy { get; set; } @@ -65,9 +92,14 @@ public partial class UAuthStateView : UAuthComponentBase [Parameter] public AuthorizationMatchMode MatchMode { get; set; } = AuthorizationMatchMode.Category; + /// + /// Gets or sets a value indicating whether the user's session state must be active for the user to be considered authorized. + /// If set to true, the component will evaluate the user's session state and render the content if the session is not active. + /// [Parameter] public bool RequireActive { get; set; } = true; + /// protected override async Task OnParametersSetAsync() { await base.OnParametersSetAsync(); @@ -97,6 +129,11 @@ protected override async Task OnParametersSetAsync() _authorizing = false; } + /// + /// Handles changes in the authentication state. + /// This method is called when the authentication state changes, and it evaluates the current session state and authorization requirements. + /// + /// protected override async void HandleAuthStateChanged(UAuthStateChangeReason reason) { EvaluateSessionState(); diff --git a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Device/BrowserDeviceIdStorage.cs b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Device/BrowserDeviceIdStorage.cs index b64b221b..1ef3de09 100644 --- a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Device/BrowserDeviceIdStorage.cs +++ b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Device/BrowserDeviceIdStorage.cs @@ -4,16 +4,25 @@ namespace CodeBeam.UltimateAuth.Client.Blazor.Device; +/// +/// Represents a device ID storage implementation that uses browser client storage to persist the device ID. +/// public sealed class BrowserDeviceIdStorage : IDeviceIdStorage { private const string Key = "udid"; private readonly IClientStorage _storage; + /// public BrowserDeviceIdStorage(IClientStorage storage) { _storage = storage; } + /// + /// Loads the device ID from the browser client storage. + /// + /// + /// public async ValueTask LoadAsync(CancellationToken ct = default) { try @@ -29,6 +38,12 @@ public BrowserDeviceIdStorage(IClientStorage storage) } } + /// + /// Saves the device ID to the browser client storage. + /// + /// + /// + /// public ValueTask SaveAsync(string deviceId, CancellationToken ct = default) { return _storage.SetAsync(StorageScope.Local, Key, deviceId); diff --git a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Extensions/AssemblyExtensions.cs b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Extensions/AssemblyExtensions.cs index 3c0cb7aa..2411bfc2 100644 --- a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Extensions/AssemblyExtensions.cs +++ b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Extensions/AssemblyExtensions.cs @@ -2,8 +2,17 @@ namespace CodeBeam.UltimateAuth.Client.Blazor; +/// +/// Provides extension methods for working with assemblies in the context of UltimateAuth Blazor client applications. +/// public static class UAuthAssemblies { + /// + /// Appends the assembly containing the UAuthBlazorClientMarker class to the provided collection of assemblies, + /// ensuring that it is included for UltimateAuth Blazor client applications. + /// + /// + /// public static Assembly[] WithUltimateAuth(this IEnumerable? assemblies) { var authAssembly = typeof(UAuthBlazorClientMarker).Assembly; @@ -14,6 +23,11 @@ public static Assembly[] WithUltimateAuth(this IEnumerable? assemblies return assemblies.Append(authAssembly).DistinctBy(a => a.FullName).ToArray(); } + /// + /// Returns an array containing the assembly of the UAuthBlazorClientMarker class, + /// which is used to identify the UltimateAuth Blazor client application assembly. + /// + /// public static Assembly[] BlazorClient() { return new[] { typeof(UAuthBlazorClientMarker).Assembly }; diff --git a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Infrastructure/BrowserClientStorage.cs b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Infrastructure/BrowserClientStorage.cs index 017c43a5..46436d41 100644 --- a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Infrastructure/BrowserClientStorage.cs +++ b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Infrastructure/BrowserClientStorage.cs @@ -4,27 +4,64 @@ namespace CodeBeam.UltimateAuth.Client.Blazor.Infrastructure; +/// +/// Represents a client storage implementation that uses the browser's localStorage and sessionStorage via JavaScript interop. +/// public sealed class BrowserClientStorage : IClientStorage { private readonly IJSRuntime _js; + /// + /// Initializes a new instance of the class with the specified JavaScript runtime. + /// + /// public BrowserClientStorage(IJSRuntime js) { _js = js; } + /// + /// Sets a value in the specified storage scope (localStorage or sessionStorage) with the given key. + /// + /// + /// + /// + /// public ValueTask SetAsync(StorageScope scope, string key, string value) => _js.InvokeVoidAsync("uauth.storage.set", Scope(scope), key, value); + /// + /// Gets a value from the specified storage scope (localStorage or sessionStorage) with the given key. + /// + /// + /// + /// public ValueTask GetAsync(StorageScope scope, string key) => _js.InvokeAsync("uauth.storage.get", Scope(scope), key); + /// + /// Removes a value from the specified storage scope (localStorage or sessionStorage) with the given key. + /// + /// + /// + /// public ValueTask RemoveAsync(StorageScope scope, string key) => _js.InvokeVoidAsync("uauth.storage.remove", Scope(scope), key); - + + /// + /// Checks if a value exists in the specified storage scope (localStorage or sessionStorage) with the given key. + /// + /// + /// + /// public async ValueTask ExistsAsync(StorageScope scope, string key) => await _js.InvokeAsync("uauth.storage.exists", Scope(scope), key); + /// + /// Gets the string representation of the storage scope for use in JavaScript interop. + /// + /// + /// private static string Scope(StorageScope scope) => scope == StorageScope.Local ? "local" : "session"; } diff --git a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Infrastructure/UAuthLoginPageDiscovery.cs b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Infrastructure/UAuthLoginPageDiscovery.cs index 7e1d5999..a4b93b3e 100644 --- a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Infrastructure/UAuthLoginPageDiscovery.cs +++ b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Infrastructure/UAuthLoginPageDiscovery.cs @@ -2,10 +2,20 @@ namespace CodeBeam.UltimateAuth.Client.Infrastructure; +/// +/// Discovers the login page route by scanning for a component decorated with the [UAuthLoginPage] attribute. If no such component is found, it defaults to "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/login". If multiple components are found, an exception is thrown. +/// The resolved route is cached for subsequent calls. +/// public static class UAuthLoginPageDiscovery { private static string? _cached; + /// + /// Resolves the login page route by scanning for a component decorated with the [UAuthLoginPage] attribute. If no such component is found, it defaults to "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/login". + /// If multiple components are found, an exception is thrown. + /// + /// + /// public static string Resolve() { if (_cached != null) diff --git a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Runtime/UAuthBlazorClientMarker.cs b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Runtime/UAuthBlazorClientMarker.cs index d7174ce1..affb92b8 100644 --- a/src/client/CodeBeam.UltimateAuth.Client.Blazor/Runtime/UAuthBlazorClientMarker.cs +++ b/src/client/CodeBeam.UltimateAuth.Client.Blazor/Runtime/UAuthBlazorClientMarker.cs @@ -1,5 +1,9 @@ namespace CodeBeam.UltimateAuth.Client.Blazor; +/// +/// Marker class for the UltimateAuth Blazor client library. +/// This class is used to identify the assembly and provide a reference point for dependency injection and other framework features. +/// public class UAuthBlazorClientMarker { } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Abstractions/IClientDeviceProvider.cs b/src/client/CodeBeam.UltimateAuth.Client/Abstractions/IClientDeviceProvider.cs index d307fbdc..a39a6075 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Abstractions/IClientDeviceProvider.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Abstractions/IClientDeviceProvider.cs @@ -2,7 +2,14 @@ namespace CodeBeam.UltimateAuth.Client.Abstractions; +/// +/// Provides a mechanism to retrieve the device context for the client application. +/// public interface IClientDeviceProvider { + /// + /// Retrieves the device context for the client application asynchronously. + /// + /// Task GetAsync(); } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Abstractions/IClientStorage.cs b/src/client/CodeBeam.UltimateAuth.Client/Abstractions/IClientStorage.cs index 9f605d26..2cbb2793 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Abstractions/IClientStorage.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Abstractions/IClientStorage.cs @@ -2,10 +2,28 @@ namespace CodeBeam.UltimateAuth.Client.Infrastructure; +/// +/// Represents a storage mechanism for client-side data, allowing for setting, retrieving, removing, and checking the existence of key-value pairs within specified storage scopes. +/// public interface IClientStorage { + /// + /// Sets a value in the specified storage scope with the given key. + /// ValueTask SetAsync(StorageScope scope, string key, string value); + + /// + /// Retrieves a value from the specified storage scope using the given key. Returns null if the key does not exist. + /// ValueTask GetAsync(StorageScope scope, string key); + + /// + /// Removes a value from the specified storage scope using the given key. + /// ValueTask RemoveAsync(StorageScope scope, string key); + + /// + /// Checks if a value exists in the specified storage scope for the given key. + /// ValueTask ExistsAsync(StorageScope scope, string key); } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Abstractions/IReturnUrlProvider.cs b/src/client/CodeBeam.UltimateAuth.Client/Abstractions/IReturnUrlProvider.cs index f5eddd8e..0567ef25 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Abstractions/IReturnUrlProvider.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Abstractions/IReturnUrlProvider.cs @@ -1,6 +1,12 @@ namespace CodeBeam.UltimateAuth.Client.Abstractions; +/// +/// Represents a provider that can retrieve the current return URL, typically used in authentication flows to redirect users back to their original destination after login or other actions. +/// public interface IReturnUrlProvider { + /// + /// Gets the current return URL, which is the URL to which the user should be redirected after completing an authentication flow or other relevant action. + /// string GetCurrentUrl(); } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Abstractions/ISessionCoordinator.cs b/src/client/CodeBeam.UltimateAuth.Client/Abstractions/ISessionCoordinator.cs index ce1781aa..cec583af 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Abstractions/ISessionCoordinator.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Abstractions/ISessionCoordinator.cs @@ -1,5 +1,9 @@ namespace CodeBeam.UltimateAuth.Client.Abstractions; +/// +/// Represents a coordinator for managing user sessions, providing methods to start and stop session coordination, +/// and an event to notify when reauthentication is required. +/// public interface ISessionCoordinator : IAsyncDisposable { /// @@ -13,5 +17,8 @@ public interface ISessionCoordinator : IAsyncDisposable /// Task StopAsync(); + /// + /// Event triggered when reauthentication is required. + /// event Action? ReauthRequired; } diff --git a/src/client/CodeBeam.UltimateAuth.Client/AuthState/UAuthState.cs b/src/client/CodeBeam.UltimateAuth.Client/AuthState/UAuthState.cs index cbc32fbe..ae41b93d 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/AuthState/UAuthState.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/AuthState/UAuthState.cs @@ -10,17 +10,28 @@ namespace CodeBeam.UltimateAuth.Client; /// /// Represents the client-side authentication snapshot for UltimateAuth. -/// +/// /// This is a lightweight, memory-only view of the current authentication state. /// It is not a security boundary and must always be validated server-side. +/// /// public sealed class UAuthState { private UAuthState() { } + /// + /// Gets the current authenticated identity snapshot, or null if the user is not authenticated. + /// public AuthIdentitySnapshot? Identity { get; private set; } + + /// + /// Gets the current claims snapshot for the authenticated user, or an empty snapshot if the user is not authenticated. + /// public ClaimsSnapshot Claims { get; private set; } = ClaimsSnapshot.Empty; + /// + /// Gets the timestamp of the last successful validation of the authentication state, or null if it has never been validated. + /// public DateTimeOffset? LastValidatedAt { get; private set; } /// @@ -28,13 +39,25 @@ private UAuthState() { } /// public bool IsStale { get; private set; } - + /// + /// Occurs when the authentication state has changed, such as after login, logout, or profile updates. + /// public event Action? Changed; internal Action? RequestRender; + /// + /// Gets a value indicating whether the user is currently authenticated (i.e., has a valid identity). + /// public bool IsAuthenticated => Identity is not null; + + /// + /// Gets a value indicating whether the authentication state needs to be validated (i.e., the user is authenticated but the snapshot is stale). + /// public bool NeedsValidation => IsAuthenticated && IsStale; + /// + /// Creates a new anonymous (unauthenticated) instance of . + /// public static UAuthState Anonymous() => new(); internal void ApplySnapshot(AuthStateSnapshot snapshot, DateTimeOffset validatedAt) @@ -96,6 +119,9 @@ internal void MarkStale() Changed?.Invoke(UAuthStateChangeReason.MarkedStale); } + /// + /// Marks the authentication state as stale and requests a re-render of the UI. + /// public void Touch(bool updateState = true) { if (updateState) @@ -116,9 +142,20 @@ internal void Clear() Changed?.Invoke(UAuthStateChangeReason.Cleared); } + /// + /// Determines whether the current authenticated user is in the specified role. + /// + /// + /// public bool IsInRole(string role) => IsAuthenticated && Claims.IsInRole(role); private CompiledPermissionSet? _compiledPermissions; + + /// + /// Determines whether the current authenticated user has the specified permission. + /// + /// + /// public bool HasPermission(string permission) { if (!IsAuthenticated) @@ -130,6 +167,11 @@ public bool HasPermission(string permission) return _compiledPermissions?.IsAllowed(permission) == true; } + /// + /// Determines whether the current authenticated user has any of the specified permissions. + /// + /// + /// public bool HasAnyPermission(params string[] permissions) { foreach (var perm in permissions) @@ -141,8 +183,16 @@ public bool HasAnyPermission(params string[] permissions) return false; } + /// + /// Determines whether the current authenticated user has the specified claim type and value. + /// public bool HasClaim(string type, string value) => IsAuthenticated && Claims.HasValue(type, value); + /// + /// Gets the value of the specified claim type for the current authenticated user, or null if the claim does not exist or the user is not authenticated. + /// + /// + /// public string? GetClaim(string type) => IsAuthenticated ? Claims.Get(type) : null; /// diff --git a/src/client/CodeBeam.UltimateAuth.Client/AuthState/UAuthStateChangeReason.cs b/src/client/CodeBeam.UltimateAuth.Client/AuthState/UAuthStateChangeReason.cs index 881df3f2..64af51cb 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/AuthState/UAuthStateChangeReason.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/AuthState/UAuthStateChangeReason.cs @@ -1,11 +1,37 @@ namespace CodeBeam.UltimateAuth.Client; +/// +/// Describes why the UltimateAuth client authentication state changed. +/// public enum UAuthStateChangeReason { + /// + /// The state was updated with an authenticated identity snapshot. + /// Authenticated, + + /// + /// The current authentication state was successfully validated. + /// Validated, + + /// + /// The current authentication state was marked as requiring validation. + /// MarkedStale, + + /// + /// The authentication state was cleared. + /// Cleared, + + /// + /// The state was explicitly touched to request an update or render. + /// Touched, + + /// + /// The current authenticated state was updated without replacing the full snapshot. + /// Patched } diff --git a/src/client/CodeBeam.UltimateAuth.Client/AuthState/UAuthStateEvent.cs b/src/client/CodeBeam.UltimateAuth.Client/AuthState/UAuthStateEvent.cs index 6367d6a1..80a995e3 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/AuthState/UAuthStateEvent.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/AuthState/UAuthStateEvent.cs @@ -1,15 +1,57 @@ namespace CodeBeam.UltimateAuth.Client; +/// +/// Identifies events that may affect the UltimateAuth client authentication state. +/// public enum UAuthStateEvent { + /// + /// Indicates that authentication state validation was requested. + /// ValidationCalled, + + /// + /// Indicates that one or more identifiers associated with the user changed. + /// IdentifiersChanged, + + /// + /// Indicates that the user's status changed. + /// UserStatusChanged, + + /// + /// Indicates that the user's profile information changed. + /// ProfileChanged, + + /// + /// Indicates that credentials associated with a user changed. + /// CredentialsChanged, + + /// + /// Indicates that the current user's own credentials changed. + /// CredentialsChangedSelf, + + /// + /// Indicates that authorization information associated with the user changed. + /// AuthorizationChanged, + + /// + /// Indicates that a session associated with the user was revoked. + /// SessionRevoked, + + /// + /// Indicates that the user was deleted. + /// UserDeleted, + + /// + /// Indicates that a logout operation affecting the authentication state occurred. + /// LogoutVariant } diff --git a/src/client/CodeBeam.UltimateAuth.Client/AuthState/UAuthStateEventHandlingMode.cs b/src/client/CodeBeam.UltimateAuth.Client/AuthState/UAuthStateEventHandlingMode.cs index 173f6526..00ed1d9f 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/AuthState/UAuthStateEventHandlingMode.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/AuthState/UAuthStateEventHandlingMode.cs @@ -1,8 +1,23 @@ namespace CodeBeam.UltimateAuth.Client; +/// +/// Specifies how the UltimateAuth client authentication state handles a state event. +/// public enum UAuthStateEventHandlingMode { + /// + /// Applies the event as a local update to the current authentication state + /// without performing full state validation. + /// Patch, + + /// + /// Revalidates the authentication state in response to the event. + /// Validate, + + /// + /// Performs no authentication state update in response to the event. + /// None } diff --git a/src/client/CodeBeam.UltimateAuth.Client/CodeBeam.UltimateAuth.Client.csproj b/src/client/CodeBeam.UltimateAuth.Client/CodeBeam.UltimateAuth.Client.csproj index fa4eaed0..f7f329c1 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/CodeBeam.UltimateAuth.Client.csproj +++ b/src/client/CodeBeam.UltimateAuth.Client/CodeBeam.UltimateAuth.Client.csproj @@ -2,7 +2,6 @@ net8.0;net9.0;net10.0 - $(NoWarn);1591 CodeBeam.UltimateAuth.Client diff --git a/src/client/CodeBeam.UltimateAuth.Client/Contracts/CoordinatorTerminationReason.cs b/src/client/CodeBeam.UltimateAuth.Client/Contracts/CoordinatorTerminationReason.cs index 3b079766..d3b69728 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Contracts/CoordinatorTerminationReason.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Contracts/CoordinatorTerminationReason.cs @@ -1,7 +1,17 @@ namespace CodeBeam.UltimateAuth.Client.Contracts; +/// +/// Specifies why an UltimateAuth session coordinator stopped its active coordination cycle. +/// public enum CoordinatorTerminationReason { + /// + /// Indicates that no specific termination reason was reported. + /// None = 0, - ReauthRequired = 1 + + /// + /// Indicates that the current authentication context requires the user to reauthenticate. + /// + ReauthRequired = 10 } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Contracts/RefreshResult.cs b/src/client/CodeBeam.UltimateAuth.Client/Contracts/RefreshResult.cs index f04f078b..82206fe2 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Contracts/RefreshResult.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Contracts/RefreshResult.cs @@ -2,9 +2,23 @@ namespace CodeBeam.UltimateAuth.Client.Contracts; +/// +/// Represents the result of an UltimateAuth session or token refresh operation. +/// public sealed record RefreshResult { + /// + /// Gets a value indicating whether the refresh operation completed successfully. + /// public bool IsSuccess { get; init; } + + /// + /// Gets the status code associated with the refresh operation. + /// public int Status { get; init; } + + /// + /// Gets the semantic outcome of the refresh operation. + /// public RefreshOutcome Outcome { get; init; } } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Contracts/StorageScope.cs b/src/client/CodeBeam.UltimateAuth.Client/Contracts/StorageScope.cs index 322f397c..3a7120cb 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Contracts/StorageScope.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Contracts/StorageScope.cs @@ -1,7 +1,17 @@ namespace CodeBeam.UltimateAuth.Client.Contracts; +/// +/// Specifies the browser storage scope used for UltimateAuth client data. +/// public enum StorageScope { - Session, - Local + /// + /// Stores data in storage scoped to the current browser session. + /// + Session = 0, + + /// + /// Stores data in persistent browser-local storage. + /// + Local = 10 } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Contracts/TenantTransport.cs b/src/client/CodeBeam.UltimateAuth.Client/Contracts/TenantTransport.cs index 6a13be2f..d470db9d 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Contracts/TenantTransport.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Contracts/TenantTransport.cs @@ -1,8 +1,22 @@ namespace CodeBeam.UltimateAuth.Client.Contracts; +/// +/// Specifies how tenant context is transported with UltimateAuth client requests. +/// public enum TenantTransport { - None, - Header, - Route + /// + /// Does not explicitly include tenant context in the request transport. + /// + None = 0, + + /// + /// Includes tenant context in an HTTP request header. + /// + Header = 10, + + /// + /// Includes tenant context as part of the request route. + /// + Route = 20 } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Contracts/UAuthRenderMode.cs b/src/client/CodeBeam.UltimateAuth.Client/Contracts/UAuthRenderMode.cs index f7f1cbba..ae3dd867 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Contracts/UAuthRenderMode.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Contracts/UAuthRenderMode.cs @@ -1,7 +1,18 @@ namespace CodeBeam.UltimateAuth.Client; +/// +/// Specifies how UltimateAuth authentication state changes affect UI rendering. +/// public enum UAuthRenderMode { + /// + /// Does not automatically request a UI re-render in response to authentication + /// state change notifications. + /// Manual = 0, - Reactive = 1 + + /// + /// Automatically requests a UI re-render in response to authentication state change notifications. + /// + Reactive = 10 } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Contracts/UAuthSubmitMode.cs b/src/client/CodeBeam.UltimateAuth.Client/Contracts/UAuthSubmitMode.cs index 4875462b..bf0f175a 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Contracts/UAuthSubmitMode.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Contracts/UAuthSubmitMode.cs @@ -1,8 +1,22 @@ namespace CodeBeam.UltimateAuth.Client; +/// +/// Specifies how an UltimateAuth authentication submission is executed. +/// public enum UAuthSubmitMode { + /// + /// Commits the authentication operation directly without first returning a structured try result to the caller. + /// DirectCommit = 0, + + /// + /// Attempts the authentication operation and returns its result without committing a successful authentication. + /// TryOnly = 10, + + /// + /// Attempts the authentication operation and, when successful, commits the authentication flow. + /// TryAndCommit = 20, } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Contracts/UAuthTransportResult.cs b/src/client/CodeBeam.UltimateAuth.Client/Contracts/UAuthTransportResult.cs index 1fd9fc47..0e87ba67 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Contracts/UAuthTransportResult.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Contracts/UAuthTransportResult.cs @@ -3,17 +3,36 @@ namespace CodeBeam.UltimateAuth.Client.Contracts; +/// +/// Represents the transport-level result of an UltimateAuth client request. +/// +/// +/// This type describes the response received by the client transport layer. +/// Application-level authentication results may be represented separately by more specific UltimateAuth result types. +/// public sealed class UAuthTransportResult { + /// + /// Gets a value indicating whether the transport response represents a successful operation. + /// [JsonPropertyName("ok")] public bool Ok { get; init; } + /// + /// Gets the HTTP status code returned by the request. + /// [JsonPropertyName("status")] public int Status { get; init; } + /// + /// Gets the refresh outcome reported by the transport response, when available. + /// [JsonPropertyName("refreshOutcome")] public string? RefreshOutcome { get; init; } + /// + /// Gets the response body as JSON, when a body is available. + /// [JsonPropertyName("body")] public JsonElement? Body { get; init; } } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Device/IDeviceIdGenerator.cs b/src/client/CodeBeam.UltimateAuth.Client/Device/IDeviceIdGenerator.cs index 033d82f0..bf73fae4 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Device/IDeviceIdGenerator.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Device/IDeviceIdGenerator.cs @@ -2,7 +2,13 @@ namespace CodeBeam.UltimateAuth.Client.Device; +/// +/// Represents a generator for device identifiers. +/// public interface IDeviceIdGenerator { + /// + /// Generates a new device identifier. + /// DeviceId Generate(); } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Device/IDeviceIdProvider.cs b/src/client/CodeBeam.UltimateAuth.Client/Device/IDeviceIdProvider.cs index a9be9fdd..1e60ab73 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Device/IDeviceIdProvider.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Device/IDeviceIdProvider.cs @@ -2,7 +2,13 @@ namespace CodeBeam.UltimateAuth.Client; +/// +/// Provides a mechanism to retrieve or create a unique device identifier for the client application. +/// public interface IDeviceIdProvider { + /// + /// Retrieves the existing device identifier or creates a new one if it doesn't exist. + /// ValueTask GetOrCreateAsync(CancellationToken ct = default); } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Device/IDeviceIdStorage.cs b/src/client/CodeBeam.UltimateAuth.Client/Device/IDeviceIdStorage.cs index c91457d3..e6bbd169 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Device/IDeviceIdStorage.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Device/IDeviceIdStorage.cs @@ -1,7 +1,22 @@ namespace CodeBeam.UltimateAuth.Client.Device; +/// +/// Represents a storage mechanism for device identifiers. +/// public interface IDeviceIdStorage { + /// + /// Loads the device identifier asynchronously. + /// + /// + /// ValueTask LoadAsync(CancellationToken ct = default); + + /// + /// Saves the device identifier asynchronously. + /// + /// + /// + /// ValueTask SaveAsync(string deviceId, CancellationToken ct = default); } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Diagnostics/UAuthClientDiagnostics.cs b/src/client/CodeBeam.UltimateAuth.Client/Diagnostics/UAuthClientDiagnostics.cs index eb21d03a..03008f9c 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Diagnostics/UAuthClientDiagnostics.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Diagnostics/UAuthClientDiagnostics.cs @@ -2,36 +2,117 @@ namespace CodeBeam.UltimateAuth.Client.Diagnostics; +/// +/// Represents diagnostic information for the UAuth client, tracking its lifecycle events and refresh attempts. +/// public sealed class UAuthClientDiagnostics { private int _terminatedCount; + /// + /// Occurs when any diagnostic information changes, allowing subscribers to react to updates in the client's state. + /// public event Action? Changed; + /// + /// Gets the timestamp when the client was started, or null if it has not been started yet. + /// public DateTimeOffset? StartedAt { get; private set; } + + /// + /// Gets the timestamp when the client was stopped, or null if it has not been stopped yet. + /// public DateTimeOffset? StoppedAt { get; private set; } + + /// + /// Gets the timestamp when the client was terminated, or null if it has not been terminated yet. + /// public DateTimeOffset? TerminatedAt { get; private set; } + + /// + /// Gets a value indicating whether the client is currently running, which is true if it has been started and has not been stopped or terminated. + /// public bool IsRunning => StartedAt is not null && !IsStopped && !IsTerminated; + + /// + /// Gets a value indicating whether the client has been stopped, which is true if it has a non-null StoppedAt timestamp. + /// public bool IsStopped => StoppedAt is not null; + + /// + /// Gets a value indicating whether the client has been terminated, which is true if it has a non-null TerminatedAt timestamp. + /// public bool IsTerminated { get; private set; } + + /// + /// Gets the reason for the client's termination, or null if it has not been terminated. + /// This provides context for why the client was terminated, such as due to an error or a manual stop request. + /// public CoordinatorTerminationReason? TerminationReason { get; private set; } + + /// + /// Gets the total number of times the client has been terminated, which is incremented each time the MarkTerminated method is called. + /// public int TerminatedCount => _terminatedCount; + + /// + /// Gets the total number of times the client has been started and stopped, which are incremented each time the MarkStarted and MarkStopped methods are called, respectively. + /// public int StartCount { get; private set; } + + /// + /// Gets the total number of times the client has been stopped, which is incremented each time the MarkStopped method is called. + /// public int StopCount { get; private set; } + + /// + /// Gets the total number of refresh attempts made by the client, which is incremented each time either the MarkManualRefresh or MarkAutomaticRefresh methods are called. + /// public int RefreshAttemptCount { get; private set; } + + /// + /// Gets the total number of manual refresh attempts made by the client, which is incremented each time the MarkManualRefresh method is called. + /// public int ManualRefreshCount { get; private set; } + + /// + /// Gets the total number of automatic refresh attempts made by the client, which is incremented each time the MarkAutomaticRefresh method is called. + /// public int AutomaticRefreshCount { get; private set; } - + + + /// + /// Gets the total number of refresh attempts that resulted in a "touched" state, which is incremented each time the MarkRefreshTouched method is called. + /// public int RefreshTouchedCount { get; private set; } + + /// + /// Gets the total number of refresh attempts that resulted in a "rotated" state, which is incremented each time the MarkRefreshRotated method is called. + /// public int RefreshRotatedCount { get; private set; } + + /// + /// Gets the total number of refresh attempts that resulted in a "no operation" state, which is incremented each time the MarkRefreshNoOp method is called. + /// public int RefreshNoOpCount { get; private set; } + + /// + /// Gets the total number of refresh attempts that required reauthentication, which is incremented each time the MarkRefreshReauthRequired method is called. + /// public int RefreshReauthRequiredCount { get; private set; } + + /// + /// Gets the total number of successful refresh attempts, which is incremented each time the MarkRefreshSuccess method is called. + /// public int RefreshSuccessCount { get; private set; } + /// + /// Gets the total duration for which the client has been running, calculated as the difference between the StartedAt timestamp and either the StoppedAt or TerminatedAt timestamp, or the current time if the client is still running. Returns null if the client has not been started yet. + /// public TimeSpan? RunningDuration => StartedAt is null ? null diff --git a/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/IBrowserUAuthBridge.cs b/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/IBrowserUAuthBridge.cs index 2098052a..54798cf1 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/IBrowserUAuthBridge.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/IBrowserUAuthBridge.cs @@ -1,6 +1,12 @@ namespace CodeBeam.UltimateAuth.Client.Infrastructure; +/// +/// Represents a bridge for browser-specific operations in the UltimateAuth client. +/// public interface IBrowserUAuthBridge { + /// + /// Sets the device ID in the browser's local storage or cookies. + /// ValueTask SetDeviceIdAsync(string deviceId); } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/IUAuthClientBootstrapper.cs b/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/IUAuthClientBootstrapper.cs index 13b5b154..79c72386 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/IUAuthClientBootstrapper.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/IUAuthClientBootstrapper.cs @@ -1,6 +1,12 @@ namespace CodeBeam.UltimateAuth.Client.Infrastructure; +/// +/// Represents a bootstrapper for the UltimateAuth client, responsible for ensuring that the client is properly initialized and started before use. +/// public interface IUAuthClientBootstrapper { + /// + /// Ensures that the UltimateAuth client is started and ready for use. This method should be called before any operations that require the client to be initialized. + /// Task EnsureStartedAsync(CancellationToken ct = default); } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/IUAuthRequestClient.cs b/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/IUAuthRequestClient.cs index 98618306..b1c7d47b 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/IUAuthRequestClient.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/IUAuthRequestClient.cs @@ -3,13 +3,49 @@ namespace CodeBeam.UltimateAuth.Client.Infrastructure; +/// +/// Defines a client for sending requests to the UltimateAuth server, handling navigation, form submissions, JSON payloads, and transactional operations. +/// public interface IUAuthRequestClient { + /// + /// Navigates to the specified endpoint, optionally submitting form data, and handles the response. + /// + /// + /// + /// + /// Task NavigateAsync(string endpoint, IDictionary? form = null, CancellationToken ct = default); + + /// + /// Sends a form submission to the specified endpoint and returns the result of the operation. + /// + /// + /// + /// + /// Task SendFormAsync(string endpoint, IDictionary? form = null, CancellationToken ct = default); + + /// + /// Sends a JSON payload to the specified endpoint and returns the result of the operation. + /// + /// + /// + /// + /// Task SendJsonAsync(string endpoint, object? payload = null, CancellationToken ct = default); + + /// + /// Attempts to perform a transactional operation by first trying the specified endpoint and, if successful, committing the operation to another endpoint. Returns the result of the try operation. + /// + /// + /// + /// + /// + /// + /// Task TryAndCommitAsync(string tryEndpoint, string commitEndpoint, object request, CancellationToken ct = default); } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/UAuthLoginPageAttribute.cs b/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/UAuthLoginPageAttribute.cs index d42e1275..ab7db38c 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/UAuthLoginPageAttribute.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/UAuthLoginPageAttribute.cs @@ -1,5 +1,8 @@ namespace CodeBeam.UltimateAuth.Client; +/// +/// Indicates that the decorated class is a login page component for the UltimateAuth client. +/// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public sealed class UAuthLoginPageAttribute : Attribute { diff --git a/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/UAuthUrlBuilder.cs b/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/UAuthUrlBuilder.cs index 717c9378..41ff2612 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/UAuthUrlBuilder.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Infrastructure/UAuthUrlBuilder.cs @@ -3,8 +3,19 @@ namespace CodeBeam.UltimateAuth.Client.Infrastructure; +/// +/// Provides utility methods for constructing URLs for UltimateAuth endpoints, taking into account multi-tenancy and transport options. +/// public static class UAuthUrlBuilder { + /// + /// Builds a complete URL for an UltimateAuth endpoint based on the provided authority, relative path, and multi-tenant options. + /// + /// + /// + /// + /// + /// public static string Build(string authority, string relativePath, UAuthClientMultiTenantOptions tenant) { var baseAuthority = authority.TrimEnd('/'); diff --git a/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientEndpointOptions.cs b/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientEndpointOptions.cs index 0b709c79..6a8ab4e2 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientEndpointOptions.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientEndpointOptions.cs @@ -1,5 +1,8 @@ namespace CodeBeam.UltimateAuth.Client.Options; +/// +/// Options for configuring the endpoints of the UAuth client. +/// public sealed class UAuthClientEndpointOptions { /// @@ -7,14 +10,53 @@ public sealed class UAuthClientEndpointOptions /// public string BasePath { get; set; } = "/auth"; + /// + /// Path for the login endpoint (e.g. /login) + /// public string Login { get; set; } = "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/login"; + + /// + /// Path for the try login endpoint (e.g. /try-login) + /// public string TryLogin { get; set; } = "/try-login"; + + /// + /// Path for the logout endpoint (e.g. /logout) + /// public string Logout { get; set; } = "/logout"; + + /// + /// Path for the refresh endpoint (e.g. /refresh) + /// public string Refresh { get; set; } = "/refresh"; + + /// + /// Path for the reauth endpoint (e.g. /reauth) + /// public string Reauth { get; set; } = "/reauth"; + + /// + /// Path for the validate endpoint (e.g. /validate) + /// public string Validate { get; set; } = "/validate"; + + /// + /// Path for the PKCE authorize endpoint (e.g. /pkce/authorize) + /// public string PkceAuthorize { get; set; } = "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/pkce/authorize"; + + /// + /// Path for the PKCE try complete endpoint (e.g. /pkce/try-complete) + /// public string PkceTryComplete { get; set; } = "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/pkce/try-complete"; + + /// + /// Path for the PKCE complete endpoint (e.g. /pkce/complete) + /// public string PkceComplete { get; set; } = "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/pkce/complete"; + + /// + /// Path for the UAuthHub login endpoint (e.g. /uauthhub/entry) + /// public string HubLoginPath { get; set; } = "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/uauthhub/entry"; } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientLoginFlowOptions.cs b/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientLoginFlowOptions.cs index 9cb1d376..db4dc0f2 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientLoginFlowOptions.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientLoginFlowOptions.cs @@ -1,5 +1,8 @@ namespace CodeBeam.UltimateAuth.Client.Options; +/// +/// Options for configuring the login flow behavior of the UAuth client. +/// public sealed class UAuthClientLoginFlowOptions { /// diff --git a/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientMultiTenantOptions.cs b/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientMultiTenantOptions.cs index c5d9caf6..ee273691 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientMultiTenantOptions.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientMultiTenantOptions.cs @@ -2,6 +2,9 @@ namespace CodeBeam.UltimateAuth.Client.Options; +/// +/// Options for multi-tenant support in the UAuth client. +/// public sealed class UAuthClientMultiTenantOptions { /// diff --git a/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientPkceLoginFlowOptions.cs b/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientPkceLoginFlowOptions.cs index 16fa248c..12c4ced0 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientPkceLoginFlowOptions.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientPkceLoginFlowOptions.cs @@ -2,6 +2,9 @@ namespace CodeBeam.UltimateAuth.Client.Options; +/// +/// Options for configuring the PKCE login flow in the UAuth client. +/// public sealed class UAuthClientPkceLoginFlowOptions { /// @@ -9,6 +12,9 @@ public sealed class UAuthClientPkceLoginFlowOptions /// public bool Enabled { get; set; } = true; + /// + /// The URL to redirect to after successful login. + /// public string? ReturnUrl { get; set; } /// diff --git a/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientReauthOptions.cs b/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientReauthOptions.cs index 3cb6796f..ca523092 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientReauthOptions.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthClientReauthOptions.cs @@ -3,8 +3,18 @@ namespace CodeBeam.UltimateAuth.Client.Options; // TODO: Add ClearCookieOnReauth +/// +/// Options for reauthentication behavior in the UAuth client. +/// public sealed class UAuthClientReauthOptions { + /// + /// Specifies the behavior to follow when reauthentication is required. + /// public ReauthBehavior Behavior { get; set; } = ReauthBehavior.Redirect; + + /// + /// Specifies the path to redirect to when reauthentication is required and the behavior is set to Redirect. + /// public string? RedirectPath { get; set; } } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthStateEventOptions.cs b/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthStateEventOptions.cs index 51bf9632..9b795104 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthStateEventOptions.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Options/UAuthStateEventOptions.cs @@ -1,6 +1,12 @@ namespace CodeBeam.UltimateAuth.Client.Options; +/// +/// Options for handling UAuth state events in the client. +/// public class UAuthStateEventOptions { + /// + /// Gets or sets the handling mode for UAuth state events. + /// public UAuthStateEventHandlingMode HandlingMode { get; set; } = UAuthStateEventHandlingMode.Patch; } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Options/Validators/UAuthClientEndpointOptionsValidator.cs b/src/client/CodeBeam.UltimateAuth.Client/Options/Validators/UAuthClientEndpointOptionsValidator.cs index d00b4f2e..2a18d4b8 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Options/Validators/UAuthClientEndpointOptionsValidator.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Options/Validators/UAuthClientEndpointOptionsValidator.cs @@ -2,8 +2,17 @@ namespace CodeBeam.UltimateAuth.Client.Options; +/// +/// Validates the to ensure that all required endpoint paths are specified and not empty. +/// public sealed class UAuthClientEndpointOptionsValidator : IValidateOptions { + /// + /// Validates the specified instance. + /// + /// + /// + /// public ValidateOptionsResult Validate(string? name, UAuthClientOptions options) { var e = options.Endpoints; diff --git a/src/client/CodeBeam.UltimateAuth.Client/Options/Validators/UAuthClientOptionsValidator.cs b/src/client/CodeBeam.UltimateAuth.Client/Options/Validators/UAuthClientOptionsValidator.cs index 98e2ec3c..f0352dd9 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Options/Validators/UAuthClientOptionsValidator.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Options/Validators/UAuthClientOptionsValidator.cs @@ -3,8 +3,17 @@ namespace CodeBeam.UltimateAuth.Client.Options; +/// +/// Validates the to ensure that the configuration is consistent and valid. +/// public sealed class UAuthClientOptionsValidator : IValidateOptions { + /// + /// Validates the provided instance. + /// + /// + /// + /// public ValidateOptionsResult Validate(string? name, UAuthClientOptions options) { if (options.ClientProfile == UAuthClientProfile.NotSpecified && options.AutoDetectClientProfile == false) diff --git a/src/client/CodeBeam.UltimateAuth.Client/Runtime/IUAuthClientProductInfoProvider.cs b/src/client/CodeBeam.UltimateAuth.Client/Runtime/IUAuthClientProductInfoProvider.cs index d240224a..2fabb239 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Runtime/IUAuthClientProductInfoProvider.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Runtime/IUAuthClientProductInfoProvider.cs @@ -1,6 +1,12 @@ namespace CodeBeam.UltimateAuth.Client.Runtime; +/// +/// Provides information about the product using the UltimateAuth client. +/// public interface IUAuthClientProductInfoProvider { + /// + /// Gets the product information for the UltimateAuth client. + /// UAuthClientProductInfo Get(); } diff --git a/src/client/CodeBeam.UltimateAuth.Client/Runtime/UAuthClientProductInfo.cs b/src/client/CodeBeam.UltimateAuth.Client/Runtime/UAuthClientProductInfo.cs index 771c92c5..ffc919af 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Runtime/UAuthClientProductInfo.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Runtime/UAuthClientProductInfo.cs @@ -3,20 +3,61 @@ namespace CodeBeam.UltimateAuth.Client.Runtime; +/// +/// Represents product information for the UltimateAuth client, including versioning, client profile, and runtime details. +/// public sealed class UAuthClientProductInfo { + /// + /// Gets the name of the product. This is a read-only property initialized to "UltimateAuth Client". + /// public string ProductName { get; init; } = "UltimateAuth Client"; + + /// + /// Gets the version of the product. This is a required property that must be initialized with a valid version string. + /// public string Version { get; init; } = default!; + + /// + /// Gets the informational version of the product. This is an optional property that can be initialized with a version string for informational purposes. + /// public string? InformationalVersion { get; init; } + + /// + /// Gets the client profile associated with the UltimateAuth client. This is a required property that must be initialized with a valid UAuthClientProfile value. + /// public UAuthClientProfile ClientProfile { get; init; } = default!; + + /// + /// Gets the timestamp indicating when the UltimateAuth client started. This is a required property that must be initialized with a valid DateTimeOffset value. + /// public DateTimeOffset StartedAt { get; init; } + + /// + /// Gets the runtime identifier for the UltimateAuth client. This is a read-only property initialized with a new GUID in string format, which uniquely identifies the runtime instance. + /// public string RuntimeId { get; init; } = Guid.NewGuid().ToString("n"); + + /// + /// Gets a value indicating whether auto-refresh is enabled for the UltimateAuth client. This is a required property that must be initialized with a boolean value. + /// public bool AutoRefreshEnabled { get; init; } + + /// + /// Gets the refresh interval for the UltimateAuth client. This is an optional property that can be initialized with a TimeSpan value indicating how often the client should refresh its state. If not set, the client may use a default refresh interval. + /// public TimeSpan? RefreshInterval { get; init; } + + /// + /// Gets the reauthentication behavior for the UltimateAuth client. This is a required property that must be initialized with a valid ReauthBehavior value, which determines how the client handles reauthentication scenarios. + /// public ReauthBehavior ReauthBehavior { get; init; } + /// + /// Gets the framework description for the UltimateAuth client. This is a required property that must be initialized with a string value describing the framework in which the client is running (e.g., ".NET 6.0", ".NET 7.0"). + /// public string FrameworkDescription { get; init; } = default!; } diff --git a/tests/.gitkeep b/tests/.gitkeep deleted file mode 100644 index 5f282702..00000000 --- a/tests/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Helpers/TestDevice.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Helpers/TestDevice.cs index 5e4c9238..b5a46a06 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Helpers/TestDevice.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Helpers/TestDevice.cs @@ -1,4 +1,5 @@ -using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Contracts; +using CodeBeam.UltimateAuth.Core.Domain; namespace CodeBeam.UltimateAuth.Tests.Unit.Helpers; @@ -6,4 +7,6 @@ internal static class TestDevice { public static DeviceContext Default() => DeviceContext.Create(DeviceId.Create("test-device-000-000-000-000-01"), null, null, null, null, null); public static DeviceContext Alternative() => DeviceContext.Create(DeviceId.Create("test-device-000-000-000-000-alternative"), null, null, null, null, null); + public static DeviceInfo DefaultDeviceInfo() => new DeviceInfo() { DeviceId = DeviceId.Create("test-device-info-000-000-000-000-01") }; + } diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ValidateEndpointHandlerTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ValidateEndpointHandlerTests.cs index 2975e191..0784187f 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ValidateEndpointHandlerTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ValidateEndpointHandlerTests.cs @@ -68,7 +68,7 @@ public async Task ValidateAsync_WhenCredentialKindIsAccessToken_ReturnsUnauthori Kind = PrimaryTokenKind.AccessToken, Value = "access-token", Tenant = fixture.Flow.Tenant, - Device = null + Device = TestDevice.DefaultDeviceInfo() }; fixture.CredentialResolver @@ -112,7 +112,7 @@ public async Task ValidateAsync_WhenSessionCredentialCannotBeParsed_ReturnsUnaut Kind = PrimaryTokenKind.Session, Value = "invalid", Tenant = fixture.Flow.Tenant, - Device = null + Device = TestDevice.DefaultDeviceInfo() }; fixture.CredentialResolver @@ -492,7 +492,7 @@ private static void SetupSessionCredential( Kind = PrimaryTokenKind.Session, Value = sessionId.Value, Tenant = fixture.Flow.Tenant, - Device = null + Device = TestDevice.DefaultDeviceInfo() }); } diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserApplicationServiceTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserApplicationServiceTests.cs index 5079de2a..6ff3b1f4 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserApplicationServiceTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserApplicationServiceTests.cs @@ -1427,6 +1427,109 @@ public async Task DeleteUserAsync_DeletesProfilesUsingRequestedDeleteMode(Delete context.ResourceTenant, target, Now.AddDays(-1)); + + var defaultProfile = UserProfile.Create( + Guid.NewGuid(), + context.ResourceTenant, + target, + ProfileKey.Default, + Now.AddDays(-1)); + + var secondaryProfileKey = ProfileKey.Parse("secondary", null); + + var secondaryProfile = UserProfile.Create( + Guid.NewGuid(), + context.ResourceTenant, + target, + secondaryProfileKey, + Now.AddDays(-1)); + + f.LifecycleStore + .Setup(x => x.GetAsync( + It.IsAny(), + It.IsAny())) + .ReturnsAsync(lifecycle); + + f.LifecycleStore + .Setup(x => x.DeleteAsync( + It.IsAny(), + lifecycle.Version, + mode, + Now, + It.IsAny())) + .Returns(Task.CompletedTask); + + f.IdentifierStore + .Setup(x => x.DeleteByUserAsync( + target, + mode, + Now, + It.IsAny())) + .Returns(Task.CompletedTask); + + f.ProfileStore + .Setup(x => x.GetAllProfilesByUserAsync( + target, + It.IsAny())) + .ReturnsAsync(new[] + { + defaultProfile, + secondaryProfile + }); + + f.ProfileStore + .Setup(x => x.DeleteAsync( + It.IsAny(), + It.IsAny(), + mode, + Now, + It.IsAny())) + .Returns(Task.CompletedTask); + + f.SessionStore + .Setup(x => x.RevokeAllChainsAsync( + target, + Now, + It.IsAny())) + .Returns(Task.CompletedTask); + + await f.Sut.DeleteUserAsync( + context, + new DeleteUserRequest + { + Mode = mode + }); + + f.ProfileStore.Verify(x => x.DeleteAsync( + new UserProfileKey( + context.ResourceTenant, + target, + ProfileKey.Default), + defaultProfile.Version, + mode, + Now, + It.IsAny()), + Times.Once); + + f.ProfileStore.Verify(x => x.DeleteAsync( + new UserProfileKey( + context.ResourceTenant, + target, + secondaryProfileKey), + secondaryProfile.Version, + mode, + Now, + It.IsAny()), + Times.Once); + + f.ProfileStore.Verify( + x => x.DeleteAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), + Times.Exactly(2)); } // ============================================================