From 3b5a1f19c11cea4f9fa8a416052e82d08c304d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Thu, 26 Mar 2026 13:29:10 +0300 Subject: [PATCH 1/6] Added Some Client Tests --- .../Services/UAuthFlowClient.cs | 19 +- .../Client/AuthStateSnapshotFactoryTests.cs | 5 +- .../Client/UAuthFlowClientTests.cs | 295 ++++++++++++++++++ .../Client/UAuthResultMapperTests.cs | 136 ++++++++ .../Helpers/TestDto.cs | 6 + 5 files changed, 454 insertions(+), 7 deletions(-) create mode 100644 tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthFlowClientTests.cs create mode 100644 tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthResultMapperTests.cs create mode 100644 tests/CodeBeam.UltimateAuth.Tests.Unit/Helpers/TestDto.cs diff --git a/src/client/CodeBeam.UltimateAuth.Client/Services/UAuthFlowClient.cs b/src/client/CodeBeam.UltimateAuth.Client/Services/UAuthFlowClient.cs index 062de526..849e6fe0 100644 --- a/src/client/CodeBeam.UltimateAuth.Client/Services/UAuthFlowClient.cs +++ b/src/client/CodeBeam.UltimateAuth.Client/Services/UAuthFlowClient.cs @@ -23,17 +23,15 @@ internal class UAuthFlowClient : IFlowClient private readonly IUAuthRequestClient _post; private readonly IUAuthClientEvents _events; private readonly IClientDeviceProvider _clientDeviceProvider; - private readonly IDeviceIdProvider _deviceIdProvider; private readonly IReturnUrlProvider _returnUrlProvider; private readonly UAuthClientOptions _options; private readonly UAuthClientDiagnostics _diagnostics; - public UAuthFlowClient(IUAuthRequestClient post, IUAuthClientEvents events, IClientDeviceProvider clientDeviceProvider, IDeviceIdProvider deviceIdProvider, IReturnUrlProvider returnUrlProvider, IOptions options, UAuthClientDiagnostics diagnostics) + public UAuthFlowClient(IUAuthRequestClient post, IUAuthClientEvents events, IClientDeviceProvider clientDeviceProvider, IReturnUrlProvider returnUrlProvider, IOptions options, UAuthClientDiagnostics diagnostics) { _post = post; _events = events; _clientDeviceProvider = clientDeviceProvider; - _deviceIdProvider = deviceIdProvider; _returnUrlProvider = returnUrlProvider; _options = options.Value; _diagnostics = diagnostics; @@ -64,13 +62,22 @@ public async Task TryLoginAsync(LoginRequest request, UAuthSubmi { case UAuthSubmitMode.TryOnly: { - var result = await _post.SendJsonAsync(tryUrl, request); + var result = await _post.SendJsonAsync(tryUrl, payload); if (result.Body is null) throw new UAuthProtocolException("Empty response body."); - var parsed = result.Body.Value.Deserialize( - new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + TryLoginResult parsed; + + try + { + parsed = result.Body.Value.Deserialize( + new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!; + } + catch (JsonException ex) + { + throw new UAuthProtocolException("Invalid try-login result.", ex); + } if (parsed is null) throw new UAuthProtocolException("Invalid try-login result."); diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/AuthStateSnapshotFactoryTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/AuthStateSnapshotFactoryTests.cs index aca9fa48..e2acf3aa 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/AuthStateSnapshotFactoryTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/AuthStateSnapshotFactoryTests.cs @@ -1,4 +1,6 @@ -using CodeBeam.UltimateAuth.Core.Contracts; +using CodeBeam.UltimateAuth.Client; +using CodeBeam.UltimateAuth.Client.Infrastructure; +using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Server.Auth; @@ -6,6 +8,7 @@ using CodeBeam.UltimateAuth.Users.Contracts; using FluentAssertions; using Moq; +using System.Text.Json; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthFlowClientTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthFlowClientTests.cs new file mode 100644 index 00000000..21f57769 --- /dev/null +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthFlowClientTests.cs @@ -0,0 +1,295 @@ +using CodeBeam.UltimateAuth.Client; +using CodeBeam.UltimateAuth.Client.Abstractions; +using CodeBeam.UltimateAuth.Client.Contracts; +using CodeBeam.UltimateAuth.Client.Diagnostics; +using CodeBeam.UltimateAuth.Client.Errors; +using CodeBeam.UltimateAuth.Client.Events; +using CodeBeam.UltimateAuth.Client.Infrastructure; +using CodeBeam.UltimateAuth.Client.Options; +using CodeBeam.UltimateAuth.Client.Services; +using CodeBeam.UltimateAuth.Core.Contracts; +using CodeBeam.UltimateAuth.Core.Domain; +using FluentAssertions; +using Microsoft.Extensions.Options; +using Moq; +using System.Text.Json; + +namespace CodeBeam.UltimateAuth.Tests.Unit; + +public class UAuthFlowClientTests +{ + private readonly Mock _mockRequest = new(); + + private UAuthFlowClient CreateClient(Mock? requestMock = null) + { + var request = requestMock ?? new Mock(); + + var events = new Mock(); + var deviceProvider = new Mock(); + var deviceIdProvider = new Mock(); + var returnUrlProvider = new Mock(); + + deviceIdProvider + .Setup(x => x.GetOrCreateAsync(It.IsAny())) + .Returns(new ValueTask( + DeviceId.Create("device-1234567890123456"))); + + returnUrlProvider + .Setup(x => x.GetCurrentUrl()) + .Returns("/home"); + + var options = Options.Create(new UAuthClientOptions + { + Endpoints = new UAuthClientEndpointOptions + { + BasePath = "/auth", + Login = "/login", + TryLogin = "/try-login" + }, + Login = new UAuthClientLoginFlowOptions + { + AllowCredentialPost = true + } + }); + + var diagnostics = new UAuthClientDiagnostics(); + + return new UAuthFlowClient( + request.Object, + events.Object, + deviceProvider.Object, + returnUrlProvider.Object, + options, + diagnostics); + } + + private static UAuthTransportResult TryLoginResponse(bool success, AuthFailureReason? reason = null) + { + return new UAuthTransportResult + { + Status = 200, + Body = JsonSerializer.SerializeToElement(new TryLoginResult + { + Success = success, + Reason = reason + }) + }; + } + + [Fact] + public async Task TryLogin_Should_Call_TryLogin_Endpoint() + { + var mock = new Mock(); + + mock.Setup(x => x.SendJsonAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(TryLoginResponse(true)); + + var client = CreateClient(mock); + await client.TryLoginAsync(new LoginRequest { Identifier = "admin", Secret = "admin" }, UAuthSubmitMode.TryOnly); + + mock.Verify(x => x.SendJsonAsync("/auth/try-login", It.IsAny()), Times.Once); + } + + [Fact] + public async Task TryLogin_Should_Return_Success() + { + var mock = new Mock(); + + mock.Setup(x => x.SendJsonAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(TryLoginResponse(true)); + + var client = CreateClient(mock); + var result = await client.TryLoginAsync(new LoginRequest { Identifier = "admin", Secret = "admin" }, UAuthSubmitMode.TryOnly); + + result.Success.Should().BeTrue(); + } + + [Fact] + public async Task TryLogin_Should_Return_Failure() + { + var mock = new Mock(); + + mock.Setup(x => x.SendJsonAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(TryLoginResponse(false, AuthFailureReason.InvalidCredentials)); + + var client = CreateClient(mock); + var result = await client.TryLoginAsync(new LoginRequest { Identifier = "admin", Secret = "wrong" }, UAuthSubmitMode.TryOnly); + + result.Success.Should().BeFalse(); + result.Reason.Should().Be(AuthFailureReason.InvalidCredentials); + } + + [Fact] + public async Task TryLogin_Should_Throw_When_Body_Null() + { + var mock = new Mock(); + + mock.Setup(x => x.SendJsonAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new UAuthTransportResult + { + Status = 200, + Body = null + }); + + var client = CreateClient(mock); + Func act = async () => await client.TryLoginAsync(new LoginRequest(), UAuthSubmitMode.TryOnly); + + await act.Should().ThrowAsync(); + } + + [Fact] + public async Task TryLogin_Should_Throw_When_Invalid_Json() + { + var mock = new Mock(); + + mock.Setup(x => x.SendJsonAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new UAuthTransportResult + { + Status = 200, + Body = JsonDocument.Parse("\"invalid\"").RootElement + }); + + var client = CreateClient(mock); + Func act = async () => await client.TryLoginAsync(new LoginRequest(), UAuthSubmitMode.TryOnly); + + await act.Should().ThrowAsync(); + } + + [Fact] + public async Task TryLogin_DirectCommit_Should_Navigate() + { + var mock = new Mock(); + + mock.Setup(x => x.NavigateAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Returns(Task.CompletedTask); + + var client = CreateClient(mock); + + var request = new LoginRequest + { + Identifier = "admin", + Secret = "admin" + }; + + var result = await client.TryLoginAsync(request, UAuthSubmitMode.DirectCommit); + result.Success.Should().BeTrue(); + + mock.Verify(x => x.NavigateAsync("/auth/login", + It.Is>(d => d["Identifier"] == "admin" && d["Secret"] == "admin"), It.IsAny()), + Times.Once); + } + + [Fact] + public async Task TryLogin_TryAndCommit_Should_Call_TryAndCommit() + { + var mock = new Mock(); + + mock.Setup(x => x.TryAndCommitAsync( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(new TryLoginResult { Success = true }); + + var client = CreateClient(mock); + var result = await client.TryLoginAsync(new LoginRequest(), UAuthSubmitMode.TryAndCommit); + + result.Success.Should().BeTrue(); + } + + [Fact] + public async Task TryLogin_Should_Throw_When_CredentialPost_Disabled() + { + var options = Options.Create(new UAuthClientOptions + { + Login = new UAuthClientLoginFlowOptions + { + AllowCredentialPost = false + } + }); + + var mock = new Mock(); + + var client = new UAuthFlowClient( + mock.Object, + Mock.Of(), + Mock.Of(), + Mock.Of(), + options, + new UAuthClientDiagnostics()); + + Func act = async () => await client.TryLoginAsync(new LoginRequest(), UAuthSubmitMode.TryOnly); + await act.Should().ThrowAsync(); + } + + [Fact] + public async Task Refresh_Should_Return_ReauthRequired_On_401() + { + var mock = new Mock(); + + mock.Setup(x => x.SendFormAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .ReturnsAsync(new UAuthTransportResult + { + Status = 401 + }); + + var client = CreateClient(mock); + var result = await client.RefreshAsync(); + + result.IsSuccess.Should().BeFalse(); + result.Outcome.Should().Be(RefreshOutcome.ReauthRequired); + } + + [Fact] + public async Task Refresh_Should_Return_Success() + { + var mock = new Mock(); + + mock.Setup(x => x.SendFormAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .ReturnsAsync(new UAuthTransportResult + { + Ok = true, + Status = 200, + RefreshOutcome = "success" + }); + + var client = CreateClient(mock); + + var result = await client.RefreshAsync(); + + result.IsSuccess.Should().BeTrue(); + result.Outcome.Should().Be(RefreshOutcome.Success); + } + + [Fact] + public async Task Validate_Should_Return_Result() + { + var mock = new Mock(); + + mock.Setup(x => x.SendFormAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .ReturnsAsync(new UAuthTransportResult + { + Status = 200, + Body = JsonSerializer.SerializeToElement(new AuthValidationResult + { + State = SessionState.Active + }) + }); + + var client = CreateClient(mock); + var result = await client.ValidateAsync(); + + result.IsValid.Should().BeTrue(); + } +} \ No newline at end of file diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthResultMapperTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthResultMapperTests.cs new file mode 100644 index 00000000..301f457d --- /dev/null +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthResultMapperTests.cs @@ -0,0 +1,136 @@ +using CodeBeam.UltimateAuth.Client.Contracts; +using CodeBeam.UltimateAuth.Client.Errors; +using CodeBeam.UltimateAuth.Client.Infrastructure; +using CodeBeam.UltimateAuth.Tests.Unit.Helpers; +using FluentAssertions; +using System.Text.Json; + +namespace CodeBeam.UltimateAuth.Tests.Unit; + +public class UAuthResultMapperTests +{ + [Fact] + public void FromJson_Should_Map_Success_Response() + { + var raw = new UAuthTransportResult + { + Status = 200, + Body = JsonDocument.Parse("{\"name\":\"test\"}").RootElement + }; + + var result = UAuthResultMapper.FromJson(raw); + + result.IsSuccess.Should().BeTrue(); + result.Value!.Name.Should().Be("test"); + } + + [Fact] + public void FromJson_Should_Handle_Empty_Body() + { + var raw = new UAuthTransportResult + { + Status = 204, + Body = null + }; + + var result = UAuthResultMapper.FromJson(raw); + + result.IsSuccess.Should().BeTrue(); + result.Value.Should().BeNull(); + } + + [Fact] + public void FromJson_Should_Map_Problem_On_4xx() + { + var raw = new UAuthTransportResult + { + Status = 401, + Body = JsonDocument.Parse("{\"title\":\"Unauthorized\"}").RootElement + }; + + var result = UAuthResultMapper.FromJson(raw); + + result.IsSuccess.Should().BeFalse(); + result.Problem.Should().NotBeNull(); + } + + [Fact] + public void FromJson_Should_Throw_On_500() + { + var raw = new UAuthTransportResult + { + Status = 500 + }; + + Action act = () => UAuthResultMapper.FromJson(raw); + act.Should().Throw(); + } + + [Fact] + public void FromJson_Should_Throw_On_Invalid_Json() + { + var raw = new UAuthTransportResult + { + Status = 200, + Body = JsonDocument.Parse("\"invalid\"").RootElement + }; + + Action act = () => UAuthResultMapper.FromJson(raw); + act.Should().Throw(); + } + + [Fact] + public void FromJson_Should_Be_Case_Insensitive() + { + var raw = new UAuthTransportResult + { + Status = 200, + Body = JsonDocument.Parse("{\"NAME\":\"test\"}").RootElement + }; + + var result = UAuthResultMapper.FromJson(raw); + result.Value!.Name.Should().Be("test"); + } + + [Fact] + public void FromJson_Should_Handle_NullBody_With_Generic_Type() + { + var raw = new UAuthTransportResult + { + Status = 200, + Body = null + }; + + var result = UAuthResultMapper.FromJson(raw); + + result.IsSuccess.Should().BeTrue(); + result.Value.Should().BeNull(); + } + + [Fact] + public void FromJson_Should_Not_Throw_When_Problem_Invalid() + { + var raw = new UAuthTransportResult + { + Status = 400, + Body = JsonDocument.Parse("\"invalid\"").RootElement + }; + + var result = UAuthResultMapper.FromJson(raw); + + result.IsSuccess.Should().BeFalse(); + result.Problem.Should().BeNull(); + } + + [Fact] + public void FromJson_Should_Throw_On_Status_Zero() + { + var raw = new UAuthTransportResult + { + Status = 0 + }; + + Action act = () => UAuthResultMapper.FromJson(raw); + act.Should().Throw(); + } +} diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Helpers/TestDto.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Helpers/TestDto.cs new file mode 100644 index 00000000..ab94f520 --- /dev/null +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Helpers/TestDto.cs @@ -0,0 +1,6 @@ +namespace CodeBeam.UltimateAuth.Tests.Unit.Helpers; + +public sealed class TestDto +{ + public string? Name { get; set; } +} From 5fbb98737d6bad57a618b12f95570a9aadcf6436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Thu, 26 Mar 2026 16:44:23 +0300 Subject: [PATCH 2/6] Created Blacor Server EFCore Sample --- UltimateAuth.slnx | 4 +- ...ateAuth.EntityFrameworkCore.Bundle.csproj} | 0 .../AuthorizationSeedContributor.cs | 14 +- .../CodeBeam.UltimateAuth.Sample.Seed.csproj | 13 + .../CredentialSeedContributor.cs | 15 +- .../Extensions/ServiceCollectionExtensions.cs | 32 ++ .../IUserIdProvider.cs | 7 + .../UserIdProvider.cs | 5 +- .../UserSeedContributor.cs | 98 ++++ ...deBeam.UltimateAuth.Sample.UAuthHub.csproj | 1 + .../Program.cs | 3 + .../Brand/UAuthLogo.razor | 19 + .../Brand/UAuthLogo.razor.cs | 54 +++ .../Brand/UAuthLogoVariant.cs | 7 + ...mateAuth.Sample.BlazorServer.EFCore.csproj | 33 ++ .../Common/UAuthDialog.cs | 29 ++ .../Components/App.razor | 29 ++ .../Custom/UAuthPageComponent.razor | 10 + .../Dialogs/AccountStatusDialog.razor | 23 + .../Dialogs/AccountStatusDialog.razor.cs | 77 +++ .../Components/Dialogs/CreateUserDialog.razor | 27 ++ .../Dialogs/CreateUserDialog.razor.cs | 55 +++ .../Components/Dialogs/CredentialDialog.razor | 51 ++ .../Dialogs/CredentialDialog.razor.cs | 92 ++++ .../Components/Dialogs/IdentifierDialog.razor | 106 +++++ .../Dialogs/IdentifierDialog.razor.cs | 309 ++++++++++++ .../Components/Dialogs/PermissionDialog.razor | 46 ++ .../Dialogs/PermissionDialog.razor.cs | 119 +++++ .../Components/Dialogs/ProfileDialog.razor | 94 ++++ .../Components/Dialogs/ProfileDialog.razor.cs | 114 +++++ .../Components/Dialogs/ResetDialog.razor | 38 ++ .../Components/Dialogs/ResetDialog.razor.cs | 42 ++ .../Components/Dialogs/RoleDialog.razor | 81 ++++ .../Components/Dialogs/RoleDialog.razor.cs | 163 +++++++ .../Components/Dialogs/SessionDialog.razor | 217 +++++++++ .../Components/Dialogs/SessionDialog.razor.cs | 284 +++++++++++ .../Components/Dialogs/UserDetailDialog.razor | 75 +++ .../Dialogs/UserDetailDialog.razor.cs | 100 ++++ .../Components/Dialogs/UserRoleDialog.razor | 49 ++ .../Dialogs/UserRoleDialog.razor.cs | 112 +++++ .../Components/Dialogs/UsersDialog.razor | 85 ++++ .../Components/Dialogs/UsersDialog.razor.cs | 176 +++++++ .../Components/Layout/MainLayout.razor | 65 +++ .../Components/Layout/MainLayout.razor.cs | 130 +++++ .../Components/Layout/MainLayout.razor.css | 18 + .../Components/Layout/ReconnectModal.razor | 31 ++ .../Layout/ReconnectModal.razor.css | 157 +++++++ .../Components/Layout/ReconnectModal.razor.js | 63 +++ .../Components/Pages/Error.razor | 36 ++ .../Components/Pages/Home.razor | 444 ++++++++++++++++++ .../Components/Pages/Home.razor.cs | 222 +++++++++ .../Components/Pages/LandingPage.razor | 4 + .../Components/Pages/LandingPage.razor.cs | 17 + .../Components/Pages/Login.razor | 126 +++++ .../Components/Pages/Login.razor.cs | 211 +++++++++ .../Components/Pages/NotAuthorized.razor | 27 ++ .../Components/Pages/NotAuthorized.razor.cs | 15 + .../Components/Routes.razor | 73 +++ .../Components/_Imports.razor | 22 + .../Infrastructure/DarkModeManager.cs | 45 ++ .../20260326115139_InitSessions.Designer.cs | 238 ++++++++++ .../Migrations/20260326115139_InitSessions.cs | 178 +++++++ ...60326121436_InitAuthentication.Designer.cs | 95 ++++ .../20260326121436_InitAuthentication.cs | 73 +++ ...uthAuthenticationDbContextModelSnapshot.cs | 92 ++++ ...260326121602_InitAuthorization.Designer.cs | 116 +++++ .../20260326121602_InitAuthorization.cs | 105 +++++ ...AuthAuthorizationDbContextModelSnapshot.cs | 113 +++++ ...20260326121243_InitCredentials.Designer.cs | 91 ++++ .../20260326121243_InitCredentials.cs | 71 +++ .../UAuthCredentialDbContextModelSnapshot.cs | 88 ++++ .../UAuthSessionDbContextModelSnapshot.cs | 235 +++++++++ .../20260326120856_InitTokens.Designer.cs | 96 ++++ .../UAuthTokenDb/20260326120856_InitTokens.cs | 91 ++++ .../UAuthTokenDbContextModelSnapshot.cs | 93 ++++ .../20260326121123_InitUsers.Designer.cs | 198 ++++++++ .../UAuthUserDb/20260326121123_InitUsers.cs | 133 ++++++ .../UAuthUserDbContextModelSnapshot.cs | 195 ++++++++ .../Program.cs | 107 +++++ .../Properties/launchSettings.json | 23 + .../appsettings.Development.json | 8 + .../appsettings.json | 9 + .../uauth.db | Bin 0 -> 311296 bytes .../wwwroot/UltimateAuth-Logo.png | Bin 0 -> 14776 bytes .../wwwroot/app.css | 143 ++++++ ...Beam.UltimateAuth.Sample.BlazorServer.slnx | 3 - ...am.UltimateAuth.Sample.BlazorServer.csproj | 2 +- .../Program.cs | 8 +- .../Seed/UserSeedContributor.cs | 6 +- .../Extensions/ServiceCollectionExtensions.cs | 69 ++- .../Data/UAuthAuthenticationDbContext.cs | 2 +- .../AuthenticationSecutiryStateProjection.cs | 2 +- .../Data/UAuthAuthorizationDbContext.cs | 2 +- .../Projections/RolePermissionProjection.cs | 2 +- .../Projections/RoleProjection.cs | 2 +- .../Projections/UserRoleProjection.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 3 - .../IAuthorizationSeeder.cs | 6 - .../Data/UAuthCredentialDbContext.cs | 2 +- .../PasswordCredentialProjection.cs | 2 +- .../ServiceCollectionExtensions.cs | 3 - .../Data/UAuthSessionDbContext.cs | 4 +- .../Projections/SessionChainProjection.cs | 2 +- .../Projections/SessionProjection.cs | 2 +- .../Projections/SessionRootProjection.cs | 2 +- .../Data/UAuthTokenDbContext.cs | 2 +- .../Projections/RefreshTokenProjection.cs | 2 +- .../Data/UAuthUserDbContext.cs | 2 +- .../Projections/UserIdentifierProjections.cs | 2 +- .../Projections/UserLifecycleProjection.cs | 2 +- .../Projections/UserProfileProjection.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 4 - .../CodeBeam.UltimateAuth.Tests.Unit.csproj | 3 +- .../Helpers/TestAuthRuntime.cs | 3 + 114 files changed, 7267 insertions(+), 86 deletions(-) rename nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/{CodeBeam.UltimateAuth.EntityFrameworkCoreReference.csproj => CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle.csproj} (100%) rename src/authorization/CodeBeam.UltimateAuth.Authorization.InMemory/InMemoryAuthorizationSeedContributor.cs => samples/CodeBeam.UltimateAuth.Sample.Seed/AuthorizationSeedContributor.cs (86%) create mode 100644 samples/CodeBeam.UltimateAuth.Sample.Seed/CodeBeam.UltimateAuth.Sample.Seed.csproj rename src/credentials/CodeBeam.UltimateAuth.Credentials.InMemory/InMemoryCredentialSeedContributor.cs => samples/CodeBeam.UltimateAuth.Sample.Seed/CredentialSeedContributor.cs (78%) create mode 100644 samples/CodeBeam.UltimateAuth.Sample.Seed/Extensions/ServiceCollectionExtensions.cs create mode 100644 samples/CodeBeam.UltimateAuth.Sample.Seed/IUserIdProvider.cs rename src/users/CodeBeam.UltimateAuth.Users.InMemory/Infrastructure/InMemoryUserIdProvider.cs => samples/CodeBeam.UltimateAuth.Sample.Seed/UserIdProvider.cs (69%) create mode 100644 samples/CodeBeam.UltimateAuth.Sample.Seed/UserSeedContributor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Brand/UAuthLogo.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Brand/UAuthLogo.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Brand/UAuthLogoVariant.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.csproj create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Common/UAuthDialog.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/App.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Custom/UAuthPageComponent.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/AccountStatusDialog.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/AccountStatusDialog.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/CreateUserDialog.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/CreateUserDialog.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/CredentialDialog.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/CredentialDialog.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/IdentifierDialog.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/IdentifierDialog.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/PermissionDialog.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/PermissionDialog.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/ProfileDialog.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/ProfileDialog.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/ResetDialog.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/ResetDialog.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/RoleDialog.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/RoleDialog.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/SessionDialog.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/SessionDialog.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UserDetailDialog.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UserDetailDialog.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UserRoleDialog.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UserRoleDialog.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UsersDialog.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UsersDialog.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/MainLayout.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/MainLayout.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/MainLayout.razor.css create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/ReconnectModal.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/ReconnectModal.razor.css create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/ReconnectModal.razor.js create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Error.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Home.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Home.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/LandingPage.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/LandingPage.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Login.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Login.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/NotAuthorized.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/NotAuthorized.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Routes.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/_Imports.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Infrastructure/DarkModeManager.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260326115139_InitSessions.Designer.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260326115139_InitSessions.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/20260326121436_InitAuthentication.Designer.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/20260326121436_InitAuthentication.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/UAuthAuthenticationDbContextModelSnapshot.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/20260326121602_InitAuthorization.Designer.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/20260326121602_InitAuthorization.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/UAuthAuthorizationDbContextModelSnapshot.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/20260326121243_InitCredentials.Designer.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/20260326121243_InitCredentials.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/UAuthCredentialDbContextModelSnapshot.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthSessionDbContextModelSnapshot.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/20260326120856_InitTokens.Designer.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/20260326120856_InitTokens.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/UAuthTokenDbContextModelSnapshot.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/20260326121123_InitUsers.Designer.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/20260326121123_InitUsers.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/UAuthUserDbContextModelSnapshot.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Program.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Properties/launchSettings.json create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/appsettings.Development.json create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/appsettings.json create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/uauth.db create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/wwwroot/UltimateAuth-Logo.png create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/wwwroot/app.css delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.slnx rename src/users/CodeBeam.UltimateAuth.Users.InMemory/Infrastructure/InMemoryUserSeedContributor.cs => samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Seed/UserSeedContributor.cs (95%) delete mode 100644 src/authorization/CodeBeam.UltimateAuth.Authorization.InMemory/IAuthorizationSeeder.cs diff --git a/UltimateAuth.slnx b/UltimateAuth.slnx index be71aa02..cba5a257 100644 --- a/UltimateAuth.slnx +++ b/UltimateAuth.slnx @@ -1,12 +1,14 @@ - + + + diff --git a/nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/CodeBeam.UltimateAuth.EntityFrameworkCoreReference.csproj b/nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle.csproj similarity index 100% rename from nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/CodeBeam.UltimateAuth.EntityFrameworkCoreReference.csproj rename to nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle.csproj diff --git a/src/authorization/CodeBeam.UltimateAuth.Authorization.InMemory/InMemoryAuthorizationSeedContributor.cs b/samples/CodeBeam.UltimateAuth.Sample.Seed/AuthorizationSeedContributor.cs similarity index 86% rename from src/authorization/CodeBeam.UltimateAuth.Authorization.InMemory/InMemoryAuthorizationSeedContributor.cs rename to samples/CodeBeam.UltimateAuth.Sample.Seed/AuthorizationSeedContributor.cs index e9e22abf..da5c0ece 100644 --- a/src/authorization/CodeBeam.UltimateAuth.Authorization.InMemory/InMemoryAuthorizationSeedContributor.cs +++ b/samples/CodeBeam.UltimateAuth.Sample.Seed/AuthorizationSeedContributor.cs @@ -1,24 +1,24 @@ -using CodeBeam.UltimateAuth.Authorization.Contracts; +using CodeBeam.UltimateAuth.Authorization; +using CodeBeam.UltimateAuth.Authorization.Contracts; using CodeBeam.UltimateAuth.Core.Abstractions; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; -using CodeBeam.UltimateAuth.InMemory; -namespace CodeBeam.UltimateAuth.Authorization.InMemory; +namespace CodeBeam.UltimateAuth.Sample.Seed; -internal sealed class InMemoryAuthorizationSeedContributor : ISeedContributor +internal sealed class AuthorizationSeedContributor : ISeedContributor { public int Order => 20; private readonly IRoleStoreFactory _roleStoreFactory; private readonly IUserRoleStoreFactory _userRoleStoreFactory; - private readonly IInMemoryUserIdProvider _ids; + private readonly IUserIdProvider _ids; private readonly IClock _clock; - public InMemoryAuthorizationSeedContributor( + public AuthorizationSeedContributor( IRoleStoreFactory roleStoreFactory, IUserRoleStoreFactory userRoleStoreFactory, - IInMemoryUserIdProvider ids, + IUserIdProvider ids, IClock clock) { _roleStoreFactory = roleStoreFactory; diff --git a/samples/CodeBeam.UltimateAuth.Sample.Seed/CodeBeam.UltimateAuth.Sample.Seed.csproj b/samples/CodeBeam.UltimateAuth.Sample.Seed/CodeBeam.UltimateAuth.Sample.Seed.csproj new file mode 100644 index 00000000..96d9adaa --- /dev/null +++ b/samples/CodeBeam.UltimateAuth.Sample.Seed/CodeBeam.UltimateAuth.Sample.Seed.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/src/credentials/CodeBeam.UltimateAuth.Credentials.InMemory/InMemoryCredentialSeedContributor.cs b/samples/CodeBeam.UltimateAuth.Sample.Seed/CredentialSeedContributor.cs similarity index 78% rename from src/credentials/CodeBeam.UltimateAuth.Credentials.InMemory/InMemoryCredentialSeedContributor.cs rename to samples/CodeBeam.UltimateAuth.Sample.Seed/CredentialSeedContributor.cs index 84b01dbc..b49f8753 100644 --- a/src/credentials/CodeBeam.UltimateAuth.Credentials.InMemory/InMemoryCredentialSeedContributor.cs +++ b/samples/CodeBeam.UltimateAuth.Sample.Seed/CredentialSeedContributor.cs @@ -4,22 +4,21 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Credentials.Contracts; using CodeBeam.UltimateAuth.Credentials.Reference; -using CodeBeam.UltimateAuth.InMemory; -namespace CodeBeam.UltimateAuth.Credentials.InMemory; +namespace CodeBeam.UltimateAuth.Sample.Seed; -internal sealed class InMemoryCredentialSeedContributor : ISeedContributor +internal sealed class CredentialSeedContributor : ISeedContributor { private static readonly Guid _adminPasswordId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"); private static readonly Guid _userPasswordId = Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"); public int Order => 10; private readonly IPasswordCredentialStoreFactory _credentialFactory; - private readonly IInMemoryUserIdProvider _ids; + private readonly IUserIdProvider _ids; private readonly IUAuthPasswordHasher _hasher; private readonly IClock _clock; - public InMemoryCredentialSeedContributor(IPasswordCredentialStoreFactory credentialFactory, IInMemoryUserIdProvider ids, IUAuthPasswordHasher hasher, IClock clock) + public CredentialSeedContributor(IPasswordCredentialStoreFactory credentialFactory, IUserIdProvider ids, IUAuthPasswordHasher hasher, IClock clock) { _credentialFactory = credentialFactory; _ids = ids; @@ -38,6 +37,12 @@ private async Task SeedCredentialAsync(UserKey userKey, Guid credentialId, strin try { var credentialStore = _credentialFactory.Create(tenant); + + var existing = await credentialStore.GetByUserAsync(userKey, ct); + + if (existing.Any(x => x.Id == credentialId)) + return; + await credentialStore.AddAsync( PasswordCredential.Create( credentialId, diff --git a/samples/CodeBeam.UltimateAuth.Sample.Seed/Extensions/ServiceCollectionExtensions.cs b/samples/CodeBeam.UltimateAuth.Sample.Seed/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..25a28910 --- /dev/null +++ b/samples/CodeBeam.UltimateAuth.Sample.Seed/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,32 @@ +using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Infrastructure; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace CodeBeam.UltimateAuth.Sample.Seed.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddUltimateAuthSampleSeed(this IServiceCollection services) + { + services.TryAddSingleton(); + services.AddSingleton, UserIdProvider>(); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + + return services; + } + + public static IServiceCollection AddScopedUltimateAuthSampleSeed(this IServiceCollection services) + { + services.TryAddScoped(); + services.AddSingleton, UserIdProvider>(); + services.TryAddEnumerable(ServiceDescriptor.Scoped()); + services.TryAddEnumerable(ServiceDescriptor.Scoped()); + services.TryAddEnumerable(ServiceDescriptor.Scoped()); + + return services; + } +} diff --git a/samples/CodeBeam.UltimateAuth.Sample.Seed/IUserIdProvider.cs b/samples/CodeBeam.UltimateAuth.Sample.Seed/IUserIdProvider.cs new file mode 100644 index 00000000..6d4e04be --- /dev/null +++ b/samples/CodeBeam.UltimateAuth.Sample.Seed/IUserIdProvider.cs @@ -0,0 +1,7 @@ +namespace CodeBeam.UltimateAuth.Sample.Seed; + +public interface IUserIdProvider +{ + TUserId GetAdminUserId(); + TUserId GetUserUserId(); +} diff --git a/src/users/CodeBeam.UltimateAuth.Users.InMemory/Infrastructure/InMemoryUserIdProvider.cs b/samples/CodeBeam.UltimateAuth.Sample.Seed/UserIdProvider.cs similarity index 69% rename from src/users/CodeBeam.UltimateAuth.Users.InMemory/Infrastructure/InMemoryUserIdProvider.cs rename to samples/CodeBeam.UltimateAuth.Sample.Seed/UserIdProvider.cs index 0d6f4ec8..e2a167bb 100644 --- a/src/users/CodeBeam.UltimateAuth.Users.InMemory/Infrastructure/InMemoryUserIdProvider.cs +++ b/samples/CodeBeam.UltimateAuth.Sample.Seed/UserIdProvider.cs @@ -1,9 +1,8 @@ using CodeBeam.UltimateAuth.Core.Domain; -using CodeBeam.UltimateAuth.InMemory; -namespace CodeBeam.UltimateAuth.Users.InMemory; +namespace CodeBeam.UltimateAuth.Sample.Seed; -public sealed class InMemoryUserIdProvider : IInMemoryUserIdProvider +public sealed class UserIdProvider : IUserIdProvider { private static readonly UserKey Admin = UserKey.FromGuid(Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")); private static readonly UserKey User = UserKey.FromGuid(Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb")); diff --git a/samples/CodeBeam.UltimateAuth.Sample.Seed/UserSeedContributor.cs b/samples/CodeBeam.UltimateAuth.Sample.Seed/UserSeedContributor.cs new file mode 100644 index 00000000..5d285880 --- /dev/null +++ b/samples/CodeBeam.UltimateAuth.Sample.Seed/UserSeedContributor.cs @@ -0,0 +1,98 @@ +using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.MultiTenancy; +using CodeBeam.UltimateAuth.Server.Infrastructure; +using CodeBeam.UltimateAuth.Users.Contracts; +using CodeBeam.UltimateAuth.Users.Reference; + +namespace CodeBeam.UltimateAuth.Sample.Seed; + +public sealed class UserSeedContributor : ISeedContributor +{ + public int Order => 0; + + private readonly IUserLifecycleStoreFactory _lifecycleFactory; + private readonly IUserIdentifierStoreFactory _identifierFactory; + private readonly IUserProfileStoreFactory _profileFactory; + private readonly IUserIdProvider _ids; + private readonly IIdentifierNormalizer _identifierNormalizer; + private readonly IClock _clock; + + public UserSeedContributor( + IUserLifecycleStoreFactory lifecycleFactory, + IUserProfileStoreFactory profileFactory, + IUserIdentifierStoreFactory identifierFactory, + IUserIdProvider ids, + IIdentifierNormalizer identifierNormalizer, + IClock clock) + { + _lifecycleFactory = lifecycleFactory; + _identifierFactory = identifierFactory; + _profileFactory = profileFactory; + _ids = ids; + _identifierNormalizer = identifierNormalizer; + _clock = clock; + } + + public async Task SeedAsync(TenantKey tenant, CancellationToken ct = default) + { + await SeedUserAsync(tenant, _ids.GetAdminUserId(), "Administrator", "admin", "admin@ultimateauth.com", "1234567890", ct); + await SeedUserAsync(tenant, _ids.GetUserUserId(), "Standard User", "user", "user@ultimateauth.com", "9876543210", ct); + } + + private async Task SeedUserAsync(TenantKey tenant, UserKey userKey, string displayName, string username, string email, string phone, CancellationToken ct) + { + var now = _clock.UtcNow; + + var lifecycleStore = _lifecycleFactory.Create(tenant); + var profileStore = _profileFactory.Create(tenant); + var identifierStore = _identifierFactory.Create(tenant); + + var lifecycleKey = new UserLifecycleKey(tenant, userKey); + + var exists = await lifecycleStore.ExistsAsync(lifecycleKey, ct); + + if (!exists) + { + await lifecycleStore.AddAsync( + UserLifecycle.Create(tenant, userKey, now), + ct); + } + + var profileKey = new UserProfileKey(tenant, userKey); + if (!await profileStore.ExistsAsync(profileKey, ct)) + { + await profileStore.AddAsync( + UserProfile.Create(Guid.NewGuid(), tenant, userKey, now, displayName: displayName), + ct); + } + + async Task EnsureIdentifier(UserIdentifierType type, string value, bool isPrimary) + { + var normalized = _identifierNormalizer + .Normalize(type, value).Normalized; + + var existing = await identifierStore.GetAsync(type, normalized, ct); + + if (existing is not null) + return; + + await identifierStore.AddAsync( + UserIdentifier.Create( + Guid.NewGuid(), + tenant, + userKey, + type, + value, + normalized, + now, + isPrimary, + now), + ct); + } + + await EnsureIdentifier(UserIdentifierType.Username, username, true); + await EnsureIdentifier(UserIdentifierType.Email, email, true); + await EnsureIdentifier(UserIdentifierType.Phone, phone, true); + } +} diff --git a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub.csproj b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub.csproj index fbf939d6..840af929 100644 --- a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub.csproj +++ b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub.csproj @@ -21,6 +21,7 @@ + diff --git a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Program.cs b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Program.cs index 47a813c8..9108573e 100644 --- a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Program.cs +++ b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Program.cs @@ -3,6 +3,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.Infrastructure; using CodeBeam.UltimateAuth.InMemory; +using CodeBeam.UltimateAuth.Sample.Seed.Extensions; using CodeBeam.UltimateAuth.Sample.UAuthHub.Components; using CodeBeam.UltimateAuth.Sample.UAuthHub.Infrastructure; using CodeBeam.UltimateAuth.Server.Extensions; @@ -41,6 +42,8 @@ .AddUltimateAuthInMemory() .AddUAuthHub(o => o.AllowedClientOrigins.Add("https://localhost:6130")); // Client sample's URL +builder.Services.AddUltimateAuthSampleSeed(); + builder.Services.AddUltimateAuthClientBlazor(o => { //o.Refresh.Interval = TimeSpan.FromSeconds(5); diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Brand/UAuthLogo.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Brand/UAuthLogo.razor new file mode 100644 index 00000000..2806b7d3 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Brand/UAuthLogo.razor @@ -0,0 +1,19 @@ +@namespace CodeBeam.UltimateAuth.Sample +@inherits ComponentBase + + + + @if (Variant == UAuthLogoVariant.Brand) + { + + + + } + else + { + + + + } + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Brand/UAuthLogo.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Brand/UAuthLogo.razor.cs new file mode 100644 index 00000000..030d9b66 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Brand/UAuthLogo.razor.cs @@ -0,0 +1,54 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; + +namespace CodeBeam.UltimateAuth.Sample; + +public partial class UAuthLogo : ComponentBase +{ + [Parameter] public UAuthLogoVariant Variant { get; set; } = UAuthLogoVariant.Brand; + + [Parameter] public int Size { get; set; } = 32; + + [Parameter] public string? ShieldColor { get; set; } = "#00072d"; + [Parameter] public string? KeyColor { get; set; } = "#f6f5ae"; + + [Parameter] public string? Class { get; set; } + [Parameter] public string? Style { get; set; } + + private string BuildStyle() + { + if (Variant == UAuthLogoVariant.Mono) + return $"color: {KeyColor}; {Style}"; + + return Style ?? ""; + } + + protected string KeyPath => @" +M120.43,39.44H79.57A11.67,11.67,0,0,0,67.9,51.11V77.37 +A11.67,11.67,0,0,0,79.57,89H90.51l3.89,3.9v5.32l-3.8,3.81v81.41H99 +v-5.33h13.69V169H108.1v-3.8H99C99,150.76,111.9,153,111.9,153 +V99.79h-8V93.32L108.19,89h12.24 +A11.67,11.67,0,0,0,132.1,77.37V51.11 +A11.67,11.67,0,0,0,120.43,39.44Z + +M79.57,48.19h5.84a2.92,2.92 0 0 1 2.92,2.92 +v5.84a2.92,2.92 0 0 1 -2.92,2.92 +h-5.84a2.91,2.91 0 0 1 -2.91,-2.92 +v-5.84a2.91,2.91 0 0 1 2.91,-2.92Z + +M79.57,68.62h5.84a2.92,2.92 0 0 1 2.92,2.92 +v5.83a2.92,2.92 0 0 1 -2.92,2.92 +h-5.84a2.91,2.91 0 0 1 -2.91,-2.92 +v-5.83a2.91,2.91 0 0 1 2.91,-2.92Z + +M114.59,48.19h5.84a2.92,2.92 0 0 1 2.91,2.92 +v5.84a2.91,2.91 0 0 1 -2.91,2.91 +h-5.84a2.92,2.92 0 0 1 -2.92,-2.91 +v-5.84a2.92,2.92 0 0 1 2.92,-2.92Z + +M114.59,68.62h5.84a2.92,2.92 0 0 1 2.91,2.92 +v5.83a2.91,2.91 0 0 1 -2.91,2.92 +h-5.84a2.92,2.92 0 0 1 -2.92,-2.92 +v-5.83a2.92,2.92 0 0 1 2.92,-2.92Z +"; +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Brand/UAuthLogoVariant.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Brand/UAuthLogoVariant.cs new file mode 100644 index 00000000..fe3be220 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Brand/UAuthLogoVariant.cs @@ -0,0 +1,7 @@ +namespace CodeBeam.UltimateAuth.Sample; + +public enum UAuthLogoVariant +{ + Brand, + Mono +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.csproj b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.csproj new file mode 100644 index 00000000..f49ac077 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.csproj @@ -0,0 +1,33 @@ + + + + net10.0 + enable + enable + false + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Common/UAuthDialog.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Common/UAuthDialog.cs new file mode 100644 index 00000000..2183c80d --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Common/UAuthDialog.cs @@ -0,0 +1,29 @@ +using CodeBeam.UltimateAuth.Client; +using CodeBeam.UltimateAuth.Core.Domain; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Common; + +public static class UAuthDialog +{ + public static DialogParameters GetDialogParameters(UAuthState state, UserKey? userKey = null) + { + DialogParameters parameters = new DialogParameters(); + parameters.Add("AuthState", state); + if (userKey != null ) + { + parameters.Add("UserKey", userKey); + } + return parameters; + } + + public static DialogOptions GetDialogOptions(MaxWidth maxWidth = MaxWidth.Medium) + { + return new DialogOptions + { + MaxWidth = maxWidth, + FullWidth = true, + CloseButton = true + }; + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/App.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/App.razor new file mode 100644 index 00000000..6acc099e --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/App.razor @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Custom/UAuthPageComponent.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Custom/UAuthPageComponent.razor new file mode 100644 index 00000000..5af543e4 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Custom/UAuthPageComponent.razor @@ -0,0 +1,10 @@ + + + @ChildContent + + + +@code { + [Parameter] + public RenderFragment? ChildContent { get; set; } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/AccountStatusDialog.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/AccountStatusDialog.razor new file mode 100644 index 00000000..0c91e45c --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/AccountStatusDialog.razor @@ -0,0 +1,23 @@ +@using CodeBeam.UltimateAuth.Core.Contracts +@using CodeBeam.UltimateAuth.Users.Contracts +@inject IUAuthClient UAuthClient +@inject ISnackbar Snackbar +@inject IDialogService DialogService + + + + Identifier Management + User: @AuthState?.Identity?.DisplayName + + + + + Suspend Account + + + + Delete Account + + + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/AccountStatusDialog.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/AccountStatusDialog.razor.cs new file mode 100644 index 00000000..edeedaa0 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/AccountStatusDialog.razor.cs @@ -0,0 +1,77 @@ +using CodeBeam.UltimateAuth.Client; +using CodeBeam.UltimateAuth.Users.Contracts; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Dialogs; + +public partial class AccountStatusDialog +{ + [CascadingParameter] + private IMudDialogInstance MudDialog { get; set; } = default!; + + [Parameter] + public UAuthState AuthState { get; set; } = default!; + + private async Task SuspendAccountAsync() + { + var info = await DialogService.ShowMessageBoxAsync( + title: "Are You Sure", + markupMessage: (MarkupString) + """ + You are going to suspend your account.

+ You can still active your account later. + """, + yesText: "Suspend", noText: "Cancel", + options: new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, BackgroundClass = "uauth-blur-slight" }); + + if (info != true) + { + Snackbar.Add("Suspend process cancelled.", Severity.Info); + return; + } + + ChangeUserStatusSelfRequest request = new() { NewStatus = SelfUserStatus.SelfSuspended }; + var result = await UAuthClient.Users.ChangeStatusSelfAsync(request); + if (result.IsSuccess) + { + Snackbar.Add("Your account suspended successfully.", Severity.Success); + MudDialog.Close(); + } + else + { + Snackbar.Add(result?.ErrorText ?? "Delete failed.", Severity.Error); + } + } + + private async Task DeleteAccountAsync() + { + var info = await DialogService.ShowMessageBoxAsync( + title: "Are You Sure", + markupMessage: (MarkupString) + """ + You are going to delete your account.

+ This action can't be undone.

+ (Actually it is, admin can handle soft deleted accounts.) + """, + yesText: "Delete", noText: "Cancel", + options: new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, BackgroundClass = "uauth-blur-slight" }); + + if (info != true) + { + Snackbar.Add("Deletion cancelled.", Severity.Info); + return; + } + + var result = await UAuthClient.Users.DeleteMeAsync(); + if (result.IsSuccess) + { + Snackbar.Add("Your account deleted successfully.", Severity.Success); + MudDialog.Close(); + } + else + { + Snackbar.Add(result?.ErrorText ?? "Delete failed.", Severity.Error); + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/CreateUserDialog.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/CreateUserDialog.razor new file mode 100644 index 00000000..9a514935 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/CreateUserDialog.razor @@ -0,0 +1,27 @@ +@using CodeBeam.UltimateAuth.Credentials.Contracts +@using CodeBeam.UltimateAuth.Users.Contracts +@inject IUAuthClient UAuthClient +@inject ISnackbar Snackbar + + + + Create User + + + + + + + + + + + + + + + + Cancel + Create + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/CreateUserDialog.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/CreateUserDialog.razor.cs new file mode 100644 index 00000000..778ad577 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/CreateUserDialog.razor.cs @@ -0,0 +1,55 @@ +using CodeBeam.UltimateAuth.Users.Contracts; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Dialogs; + +public partial class CreateUserDialog +{ + private MudForm _form = null!; + private string? _username; + private string? _email; + private string? _password; + private string? _passwordCheck; + private string? _displayName; + + [CascadingParameter] + private IMudDialogInstance MudDialog { get; set; } = default!; + + private async Task CreateUserAsync() + { + await _form.Validate(); + + if (!_form.IsValid) + return; + + if (_password != _passwordCheck) + { + Snackbar.Add("Passwords don't match.", Severity.Error); + return; + } + + var request = new CreateUserRequest + { + UserName = _username, + Email = _email, + DisplayName = _displayName, + Password = _password + }; + + var result = await UAuthClient.Users.CreateAdminAsync(request); + + if (!result.IsSuccess) + { + Snackbar.Add(result.ErrorText ?? "User creation failed.", Severity.Error); + return; + } + + Snackbar.Add("User created successfully", Severity.Success); + MudDialog.Close(DialogResult.Ok(true)); + } + + private string PasswordMatch(string? arg) => _password != arg ? "Passwords don't match." : string.Empty; + + private void Cancel() => MudDialog.Cancel(); +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/CredentialDialog.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/CredentialDialog.razor new file mode 100644 index 00000000..660b7c3a --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/CredentialDialog.razor @@ -0,0 +1,51 @@ +@using CodeBeam.UltimateAuth.Core.Contracts +@using CodeBeam.UltimateAuth.Credentials.Contracts +@using CodeBeam.UltimateAuth.Users.Contracts +@inject IUAuthClient UAuthClient +@inject ISnackbar Snackbar +@inject IDialogService DialogService +@inject IUAuthStateManager StateManager +@inject NavigationManager Nav + + + + Credential Management + User: @AuthState?.Identity?.DisplayName + + + + + @if (UserKey == null) + { + + + + } + else + { + + + Administrators can directly assign passwords to users. + However, using the credential reset flow is generally recommended for better security and auditability. + + + } + + + + + + + + + + + @(UserKey is null ? "Change Password" : "Set Password") + + + + + + Cancel + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/CredentialDialog.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/CredentialDialog.razor.cs new file mode 100644 index 00000000..f9829141 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/CredentialDialog.razor.cs @@ -0,0 +1,92 @@ +using CodeBeam.UltimateAuth.Client; +using CodeBeam.UltimateAuth.Core.Contracts; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Credentials.Contracts; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Dialogs; + +public partial class CredentialDialog +{ + private MudForm _form = null!; + private string? _oldPassword; + private string? _newPassword; + private string? _newPasswordCheck; + private bool _passwordMode1 = false; + private bool _passwordMode2 = false; + private bool _passwordMode3 = true; + + [CascadingParameter] + private IMudDialogInstance MudDialog { get; set; } = default!; + + [Parameter] + public UAuthState AuthState { get; set; } = default!; + + [Parameter] + public UserKey? UserKey { get; set; } + + private async Task ChangePasswordAsync() + { + if (_form is null) + return; + + await _form.Validate(); + if (!_form.IsValid) + { + Snackbar.Add("Form is not valid.", Severity.Error); + return; + } + + if (_newPassword != _newPasswordCheck) + { + Snackbar.Add("New password and check do not match", Severity.Error); + return; + } + + ChangeCredentialRequest request; + + if (UserKey is null) + { + request = new ChangeCredentialRequest + { + CurrentSecret = _oldPassword!, + NewSecret = _newPassword! + }; + } + else + { + request = new ChangeCredentialRequest + { + NewSecret = _newPassword! + }; + } + + UAuthResult result; + if (UserKey is null) + { + result = await UAuthClient.Credentials.ChangeMyAsync(request); + } + else + { + result = await UAuthClient.Credentials.ChangeCredentialAsync(UserKey.Value, request); + } + + if (result.IsSuccess) + { + Snackbar.Add("Password changed successfully", Severity.Success); + _oldPassword = null; + _newPassword = null; + _newPasswordCheck = null; + MudDialog.Close(DialogResult.Ok(true)); + } + else + { + Snackbar.Add(result.ErrorText ?? "An error occurred while changing password", Severity.Error); + } + } + + private string PasswordMatch(string arg) => _newPassword != arg ? "Passwords don't match" : string.Empty; + + private void Cancel() => MudDialog.Cancel(); +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/IdentifierDialog.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/IdentifierDialog.razor new file mode 100644 index 00000000..0d631533 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/IdentifierDialog.razor @@ -0,0 +1,106 @@ +@using CodeBeam.UltimateAuth.Core.Contracts +@using CodeBeam.UltimateAuth.Users.Contracts +@inject IUAuthClient UAuthClient +@inject ISnackbar Snackbar +@inject IDialogService DialogService + + + + Identifier Management + + @if (UserKey is null) + { + User: @AuthState?.Identity?.DisplayName + } + else + { + UserKey: @UserKey.Value + } + + + + + + + Identifiers + + + + + + + + + + + + + + + + + + + + + + + + + @if (context.Item.IsPrimary) + { + + + + } + else + { + + + + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Add + + + + + + + + Cancel + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/IdentifierDialog.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/IdentifierDialog.razor.cs new file mode 100644 index 00000000..c07885b3 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/IdentifierDialog.razor.cs @@ -0,0 +1,309 @@ +using CodeBeam.UltimateAuth.Client; +using CodeBeam.UltimateAuth.Core.Contracts; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Users.Contracts; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Dialogs; + +public partial class IdentifierDialog +{ + private MudDataGrid? _grid; + private UserIdentifierType _newIdentifierType; + private string? _newIdentifierValue; + private bool _newIdentifierPrimary; + private bool _loading = false; + private bool _reloadQueued; + + [CascadingParameter] + private IMudDialogInstance MudDialog { get; set; } = default!; + + [Parameter] + public UAuthState AuthState { get; set; } = default!; + + [Parameter] + public UserKey? UserKey { get; set; } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + + if (firstRender) + { + var result = await UAuthClient.Identifiers.GetMyIdentifiersAsync(); + if (result != null && result.IsSuccess && result.Value != null) + { + await ReloadAsync(); + StateHasChanged(); + } + } + } + + private async Task> LoadServerData(GridState state, CancellationToken ct) + { + var sort = state.SortDefinitions?.FirstOrDefault(); + + var req = new PageRequest + { + PageNumber = state.Page + 1, + PageSize = state.PageSize, + SortBy = sort?.SortBy, + Descending = sort?.Descending ?? false + }; + + UAuthResult> res; + + if (UserKey is null) + { + res = await UAuthClient.Identifiers.GetMyIdentifiersAsync(req); + } + else + { + res = await UAuthClient.Identifiers.GetUserIdentifiersAsync(UserKey.Value, req); + } + + if (!res.IsSuccess || res.Value is null) + { + Snackbar.Add(res.Problem?.Title ?? "Failed", Severity.Error); + + return new GridData + { + Items = Array.Empty(), + TotalItems = 0 + }; + } + + return new GridData + { + Items = res.Value.Items, + TotalItems = res.Value.TotalCount + }; + } + + private async Task ReloadAsync() + { + if (_loading) + { + _reloadQueued = true; + return; + } + + if (_grid is null) + return; + + _loading = true; + await InvokeAsync(StateHasChanged); + await Task.Delay(300); + + try + { + await _grid.ReloadServerData(); + } + finally + { + _loading = false; + + if (_reloadQueued) + { + _reloadQueued = false; + await ReloadAsync(); + } + + await InvokeAsync(StateHasChanged); + } + } + + private async Task CommittedItemChanges(UserIdentifierInfo item) + { + UpdateUserIdentifierRequest updateRequest = new() + { + Id = item.Id, + NewValue = item.Value + }; + + UAuthResult result; + + if (UserKey is null) + { + result = await UAuthClient.Identifiers.UpdateSelfAsync(updateRequest); + } + else + { + result = await UAuthClient.Identifiers.UpdateAdminAsync(UserKey.Value, updateRequest); + } + + if (result.IsSuccess) + { + Snackbar.Add("Identifier updated successfully", Severity.Success); + } + else + { + Snackbar.Add(result?.ErrorText ?? "Failed to update identifier", Severity.Error); + } + + await ReloadAsync(); + return DataGridEditFormAction.Close; + } + + private async Task AddNewIdentifier() + { + if (string.IsNullOrEmpty(_newIdentifierValue)) + { + Snackbar.Add("Value cannot be empty", Severity.Warning); + return; + } + + AddUserIdentifierRequest request = new() + { + Type = _newIdentifierType, + Value = _newIdentifierValue, + IsPrimary = _newIdentifierPrimary + }; + + UAuthResult result; + + if (UserKey is null) + { + result = await UAuthClient.Identifiers.AddSelfAsync(request); + } + else + { + result = await UAuthClient.Identifiers.AddAdminAsync(UserKey.Value, request); + } + + if (result.IsSuccess) + { + Snackbar.Add("Identifier added successfully", Severity.Success); + await ReloadAsync(); + StateHasChanged(); + } + else + { + Snackbar.Add(result?.ErrorText ?? "Failed to add identifier", Severity.Error); + } + } + + private async Task VerifyAsync(Guid id) + { + var demoInfo = await DialogService.ShowMessageBoxAsync( + title: "Demo verification", + markupMessage: (MarkupString) + """ + This is a demo action.

+ In a real app, you should verify identifiers via Email, SMS, or an Authenticator flow. + This will only mark the identifier as verified in UltimateAuth. + """, + yesText: "Verify", noText: "Cancel", + options: new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, BackgroundClass = "uauth-blur-slight" }); + + if (demoInfo != true) + { + Snackbar.Add("Verification cancelled", Severity.Info); + return; + } + + VerifyUserIdentifierRequest request = new() { IdentifierId = id }; + UAuthResult result; + + if (UserKey is null) + { + result = await UAuthClient.Identifiers.VerifySelfAsync(request); + } + else + { + result = await UAuthClient.Identifiers.VerifyAdminAsync(UserKey.Value, request); + } + + if (result.IsSuccess) + { + Snackbar.Add("Identifier verified successfully", Severity.Success); + await ReloadAsync(); + StateHasChanged(); + } + else + { + Snackbar.Add(result?.ErrorText ?? "Failed to verify primary identifier", Severity.Error); + } + } + + private async Task SetPrimaryAsync(Guid id) + { + SetPrimaryUserIdentifierRequest request = new() { IdentifierId = id }; + UAuthResult result; + + if (UserKey is null) + { + result = await UAuthClient.Identifiers.SetPrimarySelfAsync(request); + } + else + { + result = await UAuthClient.Identifiers.SetPrimaryAdminAsync(UserKey.Value, request); + } + + if (result.IsSuccess) + { + Snackbar.Add("Primary identifier set successfully", Severity.Success); + await ReloadAsync(); + StateHasChanged(); + } + else + { + Snackbar.Add(result?.ErrorText ?? "Failed to set primary identifier", Severity.Error); + } + } + + private async Task UnsetPrimaryAsync(Guid id) + { + UnsetPrimaryUserIdentifierRequest request = new() { IdentifierId = id }; + UAuthResult result; + + if (UserKey is null) + { + result = await UAuthClient.Identifiers.UnsetPrimarySelfAsync(request); + } + else + { + result = await UAuthClient.Identifiers.UnsetPrimaryAdminAsync(UserKey.Value, request); + } + + if (result.IsSuccess) + { + Snackbar.Add("Primary identifier unset successfully", Severity.Success); + await ReloadAsync(); + StateHasChanged(); + } + else + { + Snackbar.Add(result?.ErrorText ?? "Failed to unset primary identifier", Severity.Error); + } + } + + private async Task DeleteIdentifier(Guid id) + { + DeleteUserIdentifierRequest request = new() { IdentifierId = id }; + UAuthResult result; + + if (UserKey is null) + { + result = await UAuthClient.Identifiers.DeleteSelfAsync(request); + } + else + { + result = await UAuthClient.Identifiers.DeleteAdminAsync(UserKey.Value, request); + } + + if (result.IsSuccess) + { + Snackbar.Add("Identifier deleted successfully", Severity.Success); + await ReloadAsync(); + StateHasChanged(); + } + else + { + Snackbar.Add(result?.ErrorText ?? "Failed to delete identifier", Severity.Error); + } + } + + private void Cancel() => MudDialog.Cancel(); +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/PermissionDialog.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/PermissionDialog.razor new file mode 100644 index 00000000..8e0df863 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/PermissionDialog.razor @@ -0,0 +1,46 @@ +@using CodeBeam.UltimateAuth.Authorization.Contracts +@using CodeBeam.UltimateAuth.Core.Defaults +@using System.Reflection + +@inject IUAuthClient UAuthClient +@inject ISnackbar Snackbar + + + + Role Permissions + @Role.Name + + + + @* For Debug *@ + @* Current Permissions: @string.Join(", ", Role.Permissions) *@ + + @foreach (var group in _groups) + { + + + + + @group.Name (@group.Items.Count(x => x.Selected)/@group.Items.Count) + + + + + @foreach (var perm in group.Items) + { + + + + } + + + + } + + + + + Cancel + Save + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/PermissionDialog.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/PermissionDialog.razor.cs new file mode 100644 index 00000000..99c3b75b --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/PermissionDialog.razor.cs @@ -0,0 +1,119 @@ +using CodeBeam.UltimateAuth.Authorization.Contracts; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Dialogs; + +public partial class PermissionDialog +{ + [CascadingParameter] + private IMudDialogInstance MudDialog { get; set; } = default!; + + [Parameter] + public RoleInfo Role { get; set; } = default!; + + private List _groups = new(); + + protected override void OnInitialized() + { + var catalog = UAuthPermissionCatalog.GetAdminPermissions(); + var expanded = PermissionExpander.Expand(Role.Permissions, catalog); + var selected = expanded.Select(x => x.Value).ToHashSet(); + + _groups = catalog + .GroupBy(p => p.Split('.')[0]) + .Select(g => new PermissionGroup + { + Name = g.Key, + Items = g.Select(p => new PermissionItem + { + Value = p, + Selected = selected.Contains(p) + }).ToList() + }) + .OrderBy(x => x.Name) + .ToList(); + } + + private void ToggleGroup(PermissionGroup group, bool value) + { + foreach (var item in group.Items) + item.Selected = value; + } + + private void TogglePermission(PermissionItem item, bool value) + { + item.Selected = value; + } + + private bool? GetGroupState(PermissionGroup group) + { + var selected = group.Items.Count(x => x.Selected); + + if (selected == 0) + return false; + + if (selected == group.Items.Count) + return true; + + return null; + } + + private async Task Save() + { + var permissions = _groups.SelectMany(g => g.Items).Where(x => x.Selected).Select(x => Permission.From(x.Value)).ToList(); + + var req = new SetPermissionsRequest + { + Permissions = permissions + }; + + var result = await UAuthClient.Authorization.SetPermissionsAsync(Role.Id, req); + + if (!result.IsSuccess) + { + Snackbar.Add(result.ErrorText ?? "Failed to update permissions", Severity.Error); + return; + } + + var result2 = await UAuthClient.Authorization.QueryRolesAsync(new RoleQuery() { Search = Role.Name }); + if (result2.Value?.Items is not null) + { + Role = result2.Value.Items.First(); + } + + Snackbar.Add("Permissions updated", Severity.Success); + RefreshUI(); + } + + private void RefreshUI() + { + var catalog = UAuthPermissionCatalog.GetAdminPermissions(); + var expanded = PermissionExpander.Expand(Role.Permissions, catalog); + var selected = expanded.Select(x => x.Value).ToHashSet(); + + foreach (var group in _groups) + { + foreach (var item in group.Items) + { + item.Selected = selected.Contains(item.Value); + } + } + + StateHasChanged(); + } + + private void Cancel() => MudDialog.Cancel(); + + private class PermissionGroup + { + public string Name { get; set; } = ""; + public List Items { get; set; } = new(); + } + + private class PermissionItem + { + public string Value { get; set; } = ""; + public bool Selected { get; set; } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/ProfileDialog.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/ProfileDialog.razor new file mode 100644 index 00000000..d09fcfa0 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/ProfileDialog.razor @@ -0,0 +1,94 @@ +@using CodeBeam.UltimateAuth.Core.Contracts +@using CodeBeam.UltimateAuth.Users.Contracts +@inject IUAuthClient UAuthClient +@inject ISnackbar Snackbar +@inject IDialogService DialogService + + + + Identifier Management + + @if (UserKey is null) + { + User: @AuthState?.Identity?.DisplayName + } + else + { + UserKey: @UserKey.Value + } + + + + + + + + + Name + + + + + + + + + + + + + + + + + + + Personal + + + + + + + + + + + + + + + + + + + Localization + + + + + + + + + + + @foreach (var tz in TimeZoneInfo.GetSystemTimeZones()) + { + @tz.Id - @tz.DisplayName + } + + + + + + + + + + + + Cancel + Save + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/ProfileDialog.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/ProfileDialog.razor.cs new file mode 100644 index 00000000..868c9c05 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/ProfileDialog.razor.cs @@ -0,0 +1,114 @@ +using CodeBeam.UltimateAuth.Client; +using CodeBeam.UltimateAuth.Core.Contracts; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Users.Contracts; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Dialogs; + +public partial class ProfileDialog +{ + private MudForm? _form; + private string? _firstName; + private string? _lastName; + private string? _displayName; + private DateTime? _birthDate; + private string? _gender; + private string? _bio; + private string? _language; + private string? _timeZone; + private string? _culture; + + [CascadingParameter] + private IMudDialogInstance MudDialog { get; set; } = default!; + + [Parameter] + public UAuthState AuthState { get; set; } = default!; + + [Parameter] + public UserKey? UserKey { get; set; } + + protected override async Task OnInitializedAsync() + { + UAuthResult result; + + if (UserKey is null) + { + result = await UAuthClient.Users.GetMeAsync(); + } + else + { + result = await UAuthClient.Users.GetProfileAsync(UserKey.Value); + } + + if (result.IsSuccess && result.Value is not null) + { + var p = result.Value; + + _firstName = p.FirstName; + _lastName = p.LastName; + _displayName = p.DisplayName; + + _gender = p.Gender; + _birthDate = p.BirthDate?.ToDateTime(TimeOnly.MinValue); + _bio = p.Bio; + + _language = p.Language; + _timeZone = p.TimeZone; + _culture = p.Culture; + } + } + + private async Task SaveAsync() + { + if (AuthState is null || AuthState.Identity is null) + { + Snackbar.Add("No AuthState found.", Severity.Error); + return; + } + + if (_form is not null) + { + await _form.Validate(); + if (!_form.IsValid) + return; + } + + var request = new UpdateProfileRequest + { + FirstName = _firstName, + LastName = _lastName, + DisplayName = _displayName, + BirthDate = _birthDate.HasValue ? DateOnly.FromDateTime(_birthDate.Value) : null, + Gender = _gender, + Bio = _bio, + Language = _language, + TimeZone = _timeZone, + Culture = _culture + }; + + UAuthResult result; + + if (UserKey is null) + { + result = await UAuthClient.Users.UpdateMeAsync(request); + } + else + { + result = await UAuthClient.Users.UpdateProfileAsync(UserKey.Value, request); + } + + if (result.IsSuccess) + { + Snackbar.Add("Profile updated", Severity.Success); + MudDialog.Close(DialogResult.Ok(true)); + } + else + { + Snackbar.Add(result.ErrorText ?? "Failed to update profile", Severity.Error); + } + } + + private void Cancel() => MudDialog.Cancel(); +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/ResetDialog.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/ResetDialog.razor new file mode 100644 index 00000000..06a515aa --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/ResetDialog.razor @@ -0,0 +1,38 @@ +@using CodeBeam.UltimateAuth.Core.Contracts +@using CodeBeam.UltimateAuth.Credentials.Contracts +@using CodeBeam.UltimateAuth.Users.Contracts +@inject IUAuthClient UAuthClient +@inject ISnackbar Snackbar +@inject IDialogService DialogService +@inject IUAuthStateManager StateManager +@inject NavigationManager Nav + + + + Reset Credential + + + + + + This is a demonstration of how to implement a credential reset flow. + In a production application, you should use reset token or code in email, SMS etc. verification steps. + + + Reset request always returns ok even with not found users due to security reasons. + + + Request Reset + @if (_resetRequested) + { + Your reset code is: (Copy it before next step) + @_resetCode + Use Reset Code + } + + + + + Close + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/ResetDialog.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/ResetDialog.razor.cs new file mode 100644 index 00000000..55f5195c --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/ResetDialog.razor.cs @@ -0,0 +1,42 @@ +using CodeBeam.UltimateAuth.Client; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Credentials.Contracts; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Dialogs; + +public partial class ResetDialog +{ + private bool _resetRequested = false; + private string? _resetCode; + private string? _identifier; + + [CascadingParameter] + private IMudDialogInstance MudDialog { get; set; } = default!; + + [Parameter] + public UAuthState AuthState { get; set; } = default!; + + private async Task RequestResetAsync() + { + var request = new BeginCredentialResetRequest + { + CredentialType = CredentialType.Password, + ResetCodeType = ResetCodeType.Code, + Identifier = _identifier ?? string.Empty + }; + + var result = await UAuthClient.Credentials.BeginResetMyAsync(request); + if (!result.IsSuccess || result.Value is null) + { + Snackbar.Add(result.ErrorText ?? "Failed to request credential reset.", Severity.Error); + return; + } + + _resetCode = result.Value.Token; + _resetRequested = true; + } + + private void Cancel() => MudDialog.Cancel(); +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/RoleDialog.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/RoleDialog.razor new file mode 100644 index 00000000..b78db16f --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/RoleDialog.razor @@ -0,0 +1,81 @@ +@using CodeBeam.UltimateAuth.Authorization.Contracts +@using CodeBeam.UltimateAuth.Core.Contracts +@inject IUAuthClient UAuthClient +@inject ISnackbar Snackbar +@inject IDialogService DialogService + + + + + Role Management + Manage system roles + + + + + + + + Roles + + + + + + + + + + + @GetPermissionCount(context.Item) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + + + + + Close + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/RoleDialog.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/RoleDialog.razor.cs new file mode 100644 index 00000000..349ef670 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/RoleDialog.razor.cs @@ -0,0 +1,163 @@ +using CodeBeam.UltimateAuth.Authorization.Contracts; +using CodeBeam.UltimateAuth.Client; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Dialogs; + +public partial class RoleDialog +{ + private MudDataGrid? _grid; + private bool _loading; + private string? _newRoleName; + + [CascadingParameter] + private IMudDialogInstance MudDialog { get; set; } = default!; + + [Parameter] + public UAuthState AuthState { get; set; } = default!; + + private async Task> LoadServerData(GridState state, CancellationToken ct) + { + var sort = state.SortDefinitions?.FirstOrDefault(); + + var req = new RoleQuery + { + PageNumber = state.Page + 1, + PageSize = state.PageSize, + SortBy = sort?.SortBy, + Descending = sort?.Descending ?? false + }; + + var res = await UAuthClient.Authorization.QueryRolesAsync(req); + + if (!res.IsSuccess || res.Value == null) + { + Snackbar.Add(res.ErrorText ?? "Failed", Severity.Error); + + return new GridData + { + Items = Array.Empty(), + TotalItems = 0 + }; + } + + return new GridData + { + Items = res.Value.Items, + TotalItems = res.Value.TotalCount + }; + } + + private async Task CommittedItemChanges(RoleInfo role) + { + var req = new RenameRoleRequest + { + Name = role.Name + }; + + var result = await UAuthClient.Authorization.RenameRoleAsync(role.Id, req); + + if (result.IsSuccess) + { + Snackbar.Add("Role renamed", Severity.Success); + } + else + { + Snackbar.Add(result.ErrorText ?? "Rename failed", Severity.Error); + } + + await ReloadAsync(); + return DataGridEditFormAction.Close; + } + + private async Task CreateRole() + { + if (string.IsNullOrWhiteSpace(_newRoleName)) + { + Snackbar.Add("Role name required.", Severity.Warning); + return; + } + + var req = new CreateRoleRequest + { + Name = _newRoleName + }; + + var res = await UAuthClient.Authorization.CreateRoleAsync(req); + + if (res.IsSuccess) + { + Snackbar.Add("Role created.", Severity.Success); + await ReloadAsync(); + } + else + { + Snackbar.Add(res.ErrorText ?? "Creation failed.", Severity.Error); + } + } + + private async Task DeleteRole(RoleId roleId) + { + var confirm = await DialogService.ShowMessageBoxAsync( + "Delete role", + "Are you sure?", + yesText: "Delete", + cancelText: "Cancel", + options: new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, BackgroundClass = "uauth-blur-slight" }); + + if (confirm != true) + return; + + var req = new DeleteRoleRequest(); + var result = await UAuthClient.Authorization.DeleteRoleAsync(roleId, req); + + if (result.IsSuccess) + { + Snackbar.Add($"Role deleted, assignments removed from {result.Value?.RemovedAssignments.ToString() ?? "unknown"} users.", Severity.Success); + await ReloadAsync(); + } + else + { + Snackbar.Add(result.ErrorText ?? "Deletion failed.", Severity.Error); + } + } + + private async Task EditPermissions(RoleInfo role) + { + var dialog = await DialogService.ShowAsync( + "Edit Permissions", + new DialogParameters + { + { nameof(PermissionDialog.Role), role } + }, + new DialogOptions + { + CloseButton = true, + MaxWidth = MaxWidth.Large, + FullWidth = true + }); + + var result = await dialog.Result; + await ReloadAsync(); + } + + private async Task ReloadAsync() + { + _loading = true; + await Task.Delay(300); + if (_grid is null) + return; + + await _grid.ReloadServerData(); + _loading = false; + } + + private int GetPermissionCount(RoleInfo role) + { + var expanded = PermissionExpander.Expand(role.Permissions, UAuthPermissionCatalog.GetAdminPermissions()); + return expanded.Count; + } + + private void Cancel() => MudDialog.Cancel(); +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/SessionDialog.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/SessionDialog.razor new file mode 100644 index 00000000..8ecf2a15 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/SessionDialog.razor @@ -0,0 +1,217 @@ +@using CodeBeam.UltimateAuth.Core.Contracts +@using CodeBeam.UltimateAuth.Users.Contracts +@inject IUAuthClient UAuthClient +@inject ISnackbar Snackbar +@inject IDialogService DialogService +@inject IUAuthStateManager StateManager +@inject NavigationManager Nav + + + + Session Management + + @if (UserKey is null) + { + User: @AuthState?.Identity?.DisplayName + } + else + { + UserKey: @UserKey.Value + } + + + + @if (_chainDetail is not null) + { + + + + Device Details + + + + @if (!_chainDetail.IsRevoked) + { + + Revoke Device + + } + + + + + + + Device Type + @_chainDetail.DeviceType + + + + Platform + @_chainDetail.Platform + + + + Operating System + @_chainDetail.OperatingSystem + + + + Browser + @_chainDetail.Browser + + + + Created + @_chainDetail.CreatedAt.ToLocalTime() + + + + Last Seen + @_chainDetail.LastSeenAt.ToLocalTime() + + + + State + + @_chainDetail.State + + + + + Active Session + @_chainDetail.ActiveSessionId + + + + Rotation Count + @_chainDetail.RotationCount + + + + Touch Count + @_chainDetail.TouchCount + + + + + + Session History + + + + Session Id + Created + Expires + Status + + + + @context.SessionId + @context.CreatedAt.ToLocalTime() + @context.ExpiresAt.ToLocalTime() + + @if (context.IsRevoked) + { + Revoked + } + else + { + Active + } + + + + + } + else + { + + Logout All Devices + @if (UserKey == null) + { + Logout Other Devices + } + Revoke All Devices + @if (UserKey == null) + { + Revoke Other Devices + } + + + + Sessions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Id + @context.Item.ChainId + + + + Created At + @context.Item.CreatedAt + + + + Touch Count + @context.Item.TouchCount + + + + Rotation Count + @context.Item.RotationCount + + + + + + + + + } + + + Cancel + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/SessionDialog.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/SessionDialog.razor.cs new file mode 100644 index 00000000..5ec8d396 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/SessionDialog.razor.cs @@ -0,0 +1,284 @@ +using CodeBeam.UltimateAuth.Client; +using CodeBeam.UltimateAuth.Core.Contracts; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Users.Contracts; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Dialogs; + +public partial class SessionDialog +{ + private MudDataGrid? _grid; + private bool _loading = false; + private bool _reloadQueued; + private SessionChainDetail? _chainDetail; + + [CascadingParameter] + private IMudDialogInstance MudDialog { get; set; } = default!; + + [Parameter] + public UAuthState AuthState { get; set; } = default!; + + [Parameter] + public UserKey? UserKey { get; set; } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + + if (firstRender) + { + var result = await UAuthClient.Sessions.GetMyChainsAsync(); + if (result != null && result.IsSuccess && result.Value != null) + { + await ReloadAsync(); + StateHasChanged(); + } + } + } + + private async Task> LoadServerData(GridState state, CancellationToken ct) + { + var sort = state.SortDefinitions?.FirstOrDefault(); + + var req = new PageRequest + { + PageNumber = state.Page + 1, + PageSize = state.PageSize, + SortBy = sort?.SortBy, + Descending = sort?.Descending ?? false + }; + + UAuthResult> res; + + if (UserKey is null) + { + res = await UAuthClient.Sessions.GetMyChainsAsync(req); + } + else + { + res = await UAuthClient.Sessions.GetUserChainsAsync(UserKey.Value, req); + } + + if (!res.IsSuccess || res.Value is null) + { + Snackbar.Add(res.Problem?.Title ?? "Failed", Severity.Error); + + return new GridData + { + Items = Array.Empty(), + TotalItems = 0 + }; + } + + return new GridData + { + Items = res.Value.Items, + TotalItems = res.Value.TotalCount + }; + } + + private async Task ReloadAsync() + { + if (_loading) + { + _reloadQueued = true; + return; + } + + if (_grid is null) + return; + + _loading = true; + await InvokeAsync(StateHasChanged); + await Task.Delay(300); + + try + { + await _grid.ReloadServerData(); + } + finally + { + _loading = false; + + if (_reloadQueued) + { + _reloadQueued = false; + await ReloadAsync(); + } + + await InvokeAsync(StateHasChanged); + } + } + + private async Task LogoutAllAsync() + { + UAuthResult result; + + if (UserKey is null) + { + result = await UAuthClient.Flows.LogoutAllDevicesSelfAsync(); + } + else + { + result = await UAuthClient.Flows.LogoutAllDevicesAdminAsync(UserKey.Value); + } + + if (result.IsSuccess) + { + Snackbar.Add("Logged out of all devices.", Severity.Success); + if (UserKey is null) + Nav.NavigateTo("/login"); + } + else + { + Snackbar.Add(result.ErrorText ?? "Failed to logout.", Severity.Error); + } + } + + private async Task LogoutOthersAsync() + { + var result = await UAuthClient.Flows.LogoutOtherDevicesSelfAsync(); + + if (result.IsSuccess) + { + Snackbar.Add("Logged out of other devices.", Severity.Success); + } + else + { + Snackbar.Add(result?.ErrorText ?? "Failed to logout", Severity.Error); + } + } + + private async Task LogoutDeviceAsync(SessionChainId chainId) + { + LogoutDeviceRequest request = new() { ChainId = chainId }; + UAuthResult result; + + if (UserKey is null) + { + result = await UAuthClient.Flows.LogoutDeviceSelfAsync(request); + } + else + { + result = await UAuthClient.Flows.LogoutDeviceAdminAsync(UserKey.Value, request); + } + + if (result.IsSuccess) + { + Snackbar.Add("Logged out of device.", Severity.Success); + if (result?.Value?.CurrentChain == true) + { + Nav.NavigateTo("/login"); + return; + } + await ReloadAsync(); + } + else + { + Snackbar.Add(result.ErrorText ?? "Failed to logout.", Severity.Error); + } + } + + private async Task RevokeAllAsync() + { + UAuthResult result; + + if (UserKey is null) + { + result = await UAuthClient.Sessions.RevokeAllMyChainsAsync(); + } + else + { + result = await UAuthClient.Sessions.RevokeAllUserChainsAsync(UserKey.Value); + } + + if (result.IsSuccess) + { + Snackbar.Add("Logged out of all devices.", Severity.Success); + + if (UserKey is null) + Nav.NavigateTo("/login"); + } + else + { + Snackbar.Add(result?.ErrorText ?? "Failed to logout.", Severity.Error); + } + } + + private async Task RevokeOthersAsync() + { + var result = await UAuthClient.Sessions.RevokeMyOtherChainsAsync(); + if (result.IsSuccess) + { + Snackbar.Add("Revoked all other devices.", Severity.Success); + await ReloadAsync(); + } + else + { + Snackbar.Add(result?.ErrorText ?? "Failed to logout.", Severity.Error); + } + } + + private async Task RevokeChainAsync(SessionChainId chainId) + { + UAuthResult result; + + if (UserKey is null) + { + result = await UAuthClient.Sessions.RevokeMyChainAsync(chainId); + } + else + { + result = await UAuthClient.Sessions.RevokeUserChainAsync(UserKey.Value, chainId); + } + + if (result.IsSuccess) + { + Snackbar.Add("Device revoked successfully.", Severity.Success); + + if (result?.Value?.CurrentChain == true) + { + Nav.NavigateTo("/login"); + return; + } + await ReloadAsync(); + } + else + { + Snackbar.Add(result?.ErrorText ?? "Failed to logout.", Severity.Error); + } + } + + private async Task ShowChainDetailsAsync(SessionChainId chainId) + { + UAuthResult result; + + if (UserKey is null) + { + result = await UAuthClient.Sessions.GetMyChainDetailAsync(chainId); + } + else + { + result = await UAuthClient.Sessions.GetUserChainDetailAsync(UserKey.Value, chainId); + } + + if (result.IsSuccess) + { + _chainDetail = result.Value; + } + else + { + Snackbar.Add(result?.ErrorText ?? "Failed to fetch chain details.", Severity.Error); + _chainDetail = null; + } + } + + private void ClearDetail() + { + _chainDetail = null; + } + + private void Cancel() => MudDialog.Cancel(); +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UserDetailDialog.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UserDetailDialog.razor new file mode 100644 index 00000000..28bb738d --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UserDetailDialog.razor @@ -0,0 +1,75 @@ +@using CodeBeam.UltimateAuth.Core.Contracts +@using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Common +@using CodeBeam.UltimateAuth.Users.Contracts +@inject IUAuthClient UAuthClient +@inject IDialogService DialogService +@inject ISnackbar Snackbar + + + + User Management + @_user?.UserKey.Value + + + + + + + + Display Name + @_user?.DisplayName + + + + Username + @_user?.UserName + + + + Email + @_user?.PrimaryEmail + + + + Phone + @_user?.PrimaryPhone + + + + Created + @_user?.CreatedAt?.ToLocalTime() + + + + Status + @_user?.Status + + + @foreach (var s in Enum.GetValues()) + { + @s + } + + Change + + + + + + + + Management + + Sessions + Profile + Identifiers + Credentials + Roles + + + + + + Close + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UserDetailDialog.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UserDetailDialog.razor.cs new file mode 100644 index 00000000..e5bd3a08 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UserDetailDialog.razor.cs @@ -0,0 +1,100 @@ +using CodeBeam.UltimateAuth.Client; +using CodeBeam.UltimateAuth.Core.Contracts; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Common; +using CodeBeam.UltimateAuth.Users.Contracts; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Dialogs; + +public partial class UserDetailDialog +{ + private UserView? _user; + private UserStatus _status; + + [CascadingParameter] + IMudDialogInstance MudDialog { get; set; } = default!; + + [Parameter] + public UAuthState AuthState { get; set; } = default!; + + [Parameter] + public UserKey UserKey { get; set; } + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + var result = await UAuthClient.Users.GetProfileAsync(UserKey); + + if (result.IsSuccess) + { + _user = result.Value; + _status = _user?.Status ?? UserStatus.Unknown; + } + } + + private async Task OpenSessions() + { + await DialogService.ShowAsync("Session Management", UAuthDialog.GetDialogParameters(AuthState, _user?.UserKey), UAuthDialog.GetDialogOptions()); + } + + private async Task OpenProfile() + { + await DialogService.ShowAsync("Profile Management", UAuthDialog.GetDialogParameters(AuthState, _user?.UserKey), UAuthDialog.GetDialogOptions()); + } + + private async Task OpenIdentifiers() + { + await DialogService.ShowAsync("Identifier Management", UAuthDialog.GetDialogParameters(AuthState, _user?.UserKey), UAuthDialog.GetDialogOptions()); + } + + private async Task OpenCredentials() + { + await DialogService.ShowAsync("Credentials", UAuthDialog.GetDialogParameters(AuthState, _user?.UserKey), UAuthDialog.GetDialogOptions()); + } + + private async Task OpenRoles() + { + await DialogService.ShowAsync("Roles", UAuthDialog.GetDialogParameters(AuthState, _user?.UserKey), UAuthDialog.GetDialogOptions()); + } + + private async Task ChangeStatusAsync() + { + if (_user is null) + return; + + ChangeUserStatusAdminRequest request = new() + { + NewStatus = _status + }; + + var result = await UAuthClient.Users.ChangeStatusAdminAsync(_user.UserKey, request); + + if (result.IsSuccess) + { + Snackbar.Add("User status updated", Severity.Success); + _user = _user with { Status = _status }; + } + else + { + Snackbar.Add(result.ErrorText ?? "Failed", Severity.Error); + } + } + + private Color GetStatusColor(UserStatus? status) + { + return status switch + { + UserStatus.Active => Color.Success, + UserStatus.Suspended => Color.Warning, + UserStatus.Disabled => Color.Error, + _ => Color.Default + }; + } + + private void Close() + { + MudDialog.Close(); + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UserRoleDialog.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UserRoleDialog.razor new file mode 100644 index 00000000..6e754848 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UserRoleDialog.razor @@ -0,0 +1,49 @@ +@using CodeBeam.UltimateAuth.Authorization.Contracts +@inject IUAuthClient UAuthClient +@inject IDialogService DialogService +@inject ISnackbar Snackbar + + + + + User Roles + UserKey: @UserKey.Value + + + + + Assigned Roles + + @if (_roles.Count == 0) + { + No roles assigned + } + + + @foreach (var role in _roles) + { + @role + } + + + + + Add Role + + + + @foreach (var role in _allRoles) + { + @role.Name + } + + + Add + + + + + + Close + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UserRoleDialog.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UserRoleDialog.razor.cs new file mode 100644 index 00000000..94ec688a --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UserRoleDialog.razor.cs @@ -0,0 +1,112 @@ +using CodeBeam.UltimateAuth.Authorization.Contracts; +using CodeBeam.UltimateAuth.Client; +using CodeBeam.UltimateAuth.Core.Domain; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Dialogs; + +public partial class UserRoleDialog +{ + [CascadingParameter] + private IMudDialogInstance MudDialog { get; set; } = default!; + + [Parameter] + public UAuthState AuthState { get; set; } = default!; + + [Parameter] + public UserKey UserKey { get; set; } = default!; + + private List _roles = new(); + private List _allRoles = new(); + + private string? _selectedRole; + + protected override async Task OnInitializedAsync() + { + await LoadRoles(); + } + + private async Task LoadRoles() + { + var userRoles = await UAuthClient.Authorization.GetUserRolesAsync(UserKey); + + if (userRoles.IsSuccess && userRoles.Value != null) + _roles = userRoles.Value.Roles.Items.Select(x => x.Name).ToList(); + + var roles = await UAuthClient.Authorization.QueryRolesAsync(new RoleQuery + { + PageNumber = 1, + PageSize = 200 + }); + + if (roles.IsSuccess && roles.Value != null) + _allRoles = roles.Value.Items.ToList(); + } + + private async Task AddRole() + { + if (string.IsNullOrWhiteSpace(_selectedRole)) + return; + + var result = await UAuthClient.Authorization.AssignRoleToUserAsync(UserKey, _selectedRole); + + if (result.IsSuccess) + { + _roles.Add(_selectedRole); + Snackbar.Add("Role assigned", Severity.Success); + } + else + { + Snackbar.Add(result.ErrorText ?? "Failed", Severity.Error); + } + + _selectedRole = null; + } + + private async Task RemoveRole(string role) + { + var confirm = await DialogService.ShowMessageBoxAsync( + "Remove Role", + $"Remove {role} from user?", + yesText: "Remove", + noText: "Cancel", + options: new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, BackgroundClass = "uauth-blur-slight" }); + + if (confirm != true) + { + Snackbar.Add("Role remove process cancelled.", Severity.Info); + return; + } + + if (role == "Admin") + { + var confirm2 = await DialogService.ShowMessageBoxAsync( + "Are You Sure", + "You are going to remove admin role. This action may cause the application unuseable.", + yesText: "Remove", + noText: "Cancel", + options: new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, BackgroundClass = "uauth-blur-slight" }); + + if (confirm2 != true) + { + Snackbar.Add("Role remove process cancelled.", Severity.Info); + return; + } + } + + var result = await UAuthClient.Authorization.RemoveRoleFromUserAsync(UserKey, role); + + if (result.IsSuccess) + { + _roles.Remove(role); + Snackbar.Add("Role removed.", Severity.Success); + } + else + { + Snackbar.Add(result.ErrorText ?? "Failed", Severity.Error); + } + } + + private void Close() => MudDialog.Close(); +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UsersDialog.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UsersDialog.razor new file mode 100644 index 00000000..5fba5e4c --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UsersDialog.razor @@ -0,0 +1,85 @@ +@using CodeBeam.UltimateAuth.Core.Contracts +@using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Common +@using CodeBeam.UltimateAuth.Users.Contracts +@inject IUAuthClient UAuthClient +@inject IDialogService DialogService +@inject ISnackbar Snackbar + + + + User Management + Browse, create and manage users + + + + + + + + + + + + + + + + + Users + + New User + + + + + + + + + + + @context.Item.Status + + + + + + + + + + + + + + + + + + + + + Id + @context.Item.UserKey.Value + + + + Created At + @context.Item.CreatedAt + + + + + + + + + + + + + Close + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UsersDialog.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UsersDialog.razor.cs new file mode 100644 index 00000000..2344bdf8 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Dialogs/UsersDialog.razor.cs @@ -0,0 +1,176 @@ +using CodeBeam.UltimateAuth.Client; +using CodeBeam.UltimateAuth.Core.Contracts; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Common; +using CodeBeam.UltimateAuth.Users.Contracts; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Dialogs; + +public partial class UsersDialog +{ + private MudDataGrid? _grid; + private bool _loading; + private string? _search; + private bool _reloadQueued; + private UserStatus? _statusFilter; + + [CascadingParameter] + IMudDialogInstance MudDialog { get; set; } = default!; + + [Parameter] + public UAuthState AuthState { get; set; } = default!; + + private async Task> LoadUsers(GridState state, CancellationToken ct) + { + var sort = state.SortDefinitions?.FirstOrDefault(); + + var req = new UserQuery + { + PageNumber = state.Page + 1, + PageSize = state.PageSize, + Search = _search, + Status = _statusFilter, + SortBy = sort?.SortBy, + Descending = sort?.Descending ?? false + }; + + var res = await UAuthClient.Users.QueryUsersAsync(req); + + if (!res.IsSuccess || res.Value == null) + { + Snackbar.Add(res.ErrorText ?? "Failed to load users.", Severity.Error); + + return new GridData + { + Items = Array.Empty(), + TotalItems = 0 + }; + } + + return new GridData + { + Items = res.Value.Items, + TotalItems = res.Value.TotalCount + }; + } + + private async Task ReloadAsync() + { + if (_loading) + { + _reloadQueued = true; + return; + } + + if (_grid is null) + return; + + _loading = true; + await InvokeAsync(StateHasChanged); + + try + { + await _grid.ReloadServerData(); + } + finally + { + _loading = false; + + if (_reloadQueued) + { + _reloadQueued = false; + await ReloadAsync(); + } + + await InvokeAsync(StateHasChanged); + } + } + + private async Task OnStatusChanged(UserStatus? status) + { + _statusFilter = status; + await ReloadAsync(); + } + + private async Task OpenUser(UserKey userKey) + { + var dialog = await DialogService.ShowAsync("User", UAuthDialog.GetDialogParameters(AuthState, userKey), UAuthDialog.GetDialogOptions()); + await dialog.Result; + await ReloadAsync(); + } + + private async Task OpenCreateUser() + { + var dialog = await DialogService.ShowAsync( + "Create User", + new DialogOptions + { + MaxWidth = MaxWidth.Small, + FullWidth = true, + CloseButton = true + }); + + var result = await dialog.Result; + + if (result?.Canceled == false) + await ReloadAsync(); + } + + private async Task DeleteUserAsync(UserSummary user) + { + var confirm = await DialogService.ShowMessageBoxAsync( + title: "Delete user", + markupMessage: (MarkupString)$""" + Are you sure you want to delete {user.DisplayName ?? user.UserName ?? user.PrimaryEmail ?? user.UserKey}? +

+ This operation is intended for admin usage. + """, + yesText: "Delete", + cancelText: "Cancel", + options: new DialogOptions + { + MaxWidth = MaxWidth.Medium, + FullWidth = true, + BackgroundClass = "uauth-blur-slight" + }); + + if (confirm != true) + return; + + var req = new DeleteUserRequest + { + Mode = DeleteMode.Soft + }; + + var result = await UAuthClient.Users.DeleteUserAsync(UserKey.Parse(user.UserKey, null), req); + + if (result.IsSuccess) + { + Snackbar.Add("User deleted successfully.", Severity.Success); + await ReloadAsync(); + } + else + { + Snackbar.Add(result.ErrorText ?? "Failed to delete user.", Severity.Error); + } + } + + private static Color GetStatusColor(UserStatus status) + { + return status switch + { + UserStatus.Active => Color.Success, + UserStatus.SelfSuspended => Color.Warning, + UserStatus.Suspended => Color.Warning, + UserStatus.Disabled => Color.Error, + _ => Color.Default + }; + } + + private void Close() + { + MudDialog.Close(); + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/MainLayout.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/MainLayout.razor new file mode 100644 index 00000000..2e4a24bb --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/MainLayout.razor @@ -0,0 +1,65 @@ +@inherits LayoutComponentBase +@inject IUAuthClient UAuthClient +@inject ISnackbar Snackbar +@inject NavigationManager Nav + + + + + UltimateAuth + + Blazor Server EFCore Sample + + + + + + + + + +
+ + + @((state.Identity?.DisplayName ?? "?").Trim() is var n ? (n.Length >= 2 ? n[..2] : n[..1]) : "?") + + +
+
+ + + @state.Identity?.DisplayName + @string.Join(", ", state.Claims.Roles) + + + + + + + + @if (state.Identity?.SessionState is not null && state.Identity.SessionState != SessionState.Active) + { + + + } + +
+
+ + + + +
+
+ + + @Body + +
+ + +
+ An unhandled error has occurred. + Reload + 🗙 +
diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/MainLayout.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/MainLayout.razor.cs new file mode 100644 index 00000000..8ae19aa7 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/MainLayout.razor.cs @@ -0,0 +1,130 @@ +using CodeBeam.UltimateAuth.Client; +using CodeBeam.UltimateAuth.Client.Errors; +using CodeBeam.UltimateAuth.Core.Contracts; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Errors; +using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Infrastructure; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Layout; + +public partial class MainLayout +{ + [CascadingParameter] + public UAuthState UAuth { get; set; } = default!; + + [CascadingParameter] + public DarkModeManager DarkModeManager { get; set; } = default!; + + private async Task Refresh() + { + await UAuthClient.Flows.RefreshAsync(); + } + + private async Task Logout() + { + await UAuthClient.Flows.LogoutAsync(); + } + + private Color GetBadgeColor() + { + if (UAuth is null || !UAuth.IsAuthenticated) + return Color.Error; + + if (UAuth.IsStale) + return Color.Warning; + + var state = UAuth.Identity?.SessionState; + + if (state is null || state == SessionState.Active) + return Color.Success; + + if (state == SessionState.Invalid) + return Color.Error; + + return Color.Warning; + } + + private void HandleSignInClick() + { + var uri = Nav.ToAbsoluteUri(Nav.Uri); + + if (uri.AbsolutePath.EndsWith("/login", StringComparison.OrdinalIgnoreCase)) + { + Nav.NavigateTo("/login?focus=1", replace: true, forceLoad: true); + return; + } + + GoToLoginWithReturn(); + } + + private async Task Validate() + { + try + { + var result = await UAuthClient.Flows.ValidateAsync(); + + if (result.IsValid) + { + if (result.Snapshot?.Identity.UserStatus == UserStatus.SelfSuspended) + { + Snackbar.Add("Your account is suspended by you.", Severity.Warning); + return; + } + Snackbar.Add($"Session active • Tenant: {result.Snapshot?.Identity?.Tenant.Value} • User: {result.Snapshot?.Identity?.PrimaryUserName}", Severity.Success); + } + else + { + switch (result.State) + { + case SessionState.Expired: + Snackbar.Add("Session expired. Please sign in again.", Severity.Warning); + break; + + case SessionState.DeviceMismatch: + Snackbar.Add("Session invalid for this device.", Severity.Error); + break; + + default: + Snackbar.Add($"Session state: {result.State}", Severity.Error); + break; + } + } + } + catch (UAuthTransportException) + { + Snackbar.Add("Network error.", Severity.Error); + } + catch (UAuthProtocolException) + { + Snackbar.Add("Invalid response.", Severity.Error); + } + catch (UAuthException ex) + { + Snackbar.Add($"UAuth error: {ex.Message}", Severity.Error); + } + catch (Exception ex) + { + Snackbar.Add($"Unexpected error: {ex.Message}", Severity.Error); + } + } + + private void GoToLoginWithReturn() + { + var uri = Nav.ToAbsoluteUri(Nav.Uri); + + if (uri.AbsolutePath.EndsWith("/login", StringComparison.OrdinalIgnoreCase)) + { + Nav.NavigateTo("/login", replace: true); + return; + } + + var current = Nav.ToBaseRelativePath(uri.ToString()); + if (string.IsNullOrWhiteSpace(current)) + current = "home"; + + var returnUrl = Uri.EscapeDataString("/" + current.TrimStart('/')); + Nav.NavigateTo($"/login?returnUrl={returnUrl}", replace: true); + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/MainLayout.razor.css b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/MainLayout.razor.css new file mode 100644 index 00000000..df8c10ff --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/MainLayout.razor.css @@ -0,0 +1,18 @@ +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/ReconnectModal.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/ReconnectModal.razor new file mode 100644 index 00000000..e740b0c8 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/ReconnectModal.razor @@ -0,0 +1,31 @@ + + + +
+ +

+ Rejoining the server... +

+

+ Rejoin failed... trying again in seconds. +

+

+ Failed to rejoin.
Please retry or reload the page. +

+ +

+ The session has been paused by the server. +

+

+ Failed to resume the session.
Please retry or reload the page. +

+ +
+
diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/ReconnectModal.razor.css b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/ReconnectModal.razor.css new file mode 100644 index 00000000..3ad3773f --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/ReconnectModal.razor.css @@ -0,0 +1,157 @@ +.components-reconnect-first-attempt-visible, +.components-reconnect-repeated-attempt-visible, +.components-reconnect-failed-visible, +.components-pause-visible, +.components-resume-failed-visible, +.components-rejoining-animation { + display: none; +} + +#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible, +#components-reconnect-modal.components-reconnect-show .components-rejoining-animation, +#components-reconnect-modal.components-reconnect-paused .components-pause-visible, +#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible, +#components-reconnect-modal.components-reconnect-retrying, +#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible, +#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation, +#components-reconnect-modal.components-reconnect-failed, +#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible { + display: block; +} + + +#components-reconnect-modal { + background-color: white; + width: 20rem; + margin: 20vh auto; + padding: 2rem; + border: 0; + border-radius: 0.5rem; + box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3); + opacity: 0; + transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete; + animation: components-reconnect-modal-fadeOutOpacity 0.5s both; + &[open] + +{ + animation: components-reconnect-modal-slideUp 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity 0.5s ease-in-out 0.3s; + animation-fill-mode: both; +} + +} + +#components-reconnect-modal::backdrop { + background-color: rgba(0, 0, 0, 0.4); + animation: components-reconnect-modal-fadeInOpacity 0.5s ease-in-out; + opacity: 1; +} + +@keyframes components-reconnect-modal-slideUp { + 0% { + transform: translateY(30px) scale(0.95); + } + + 100% { + transform: translateY(0); + } +} + +@keyframes components-reconnect-modal-fadeInOpacity { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes components-reconnect-modal-fadeOutOpacity { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} + +.components-reconnect-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; +} + +#components-reconnect-modal p { + margin: 0; + text-align: center; +} + +#components-reconnect-modal button { + border: 0; + background-color: #6b9ed2; + color: white; + padding: 4px 24px; + border-radius: 4px; +} + + #components-reconnect-modal button:hover { + background-color: #3b6ea2; + } + + #components-reconnect-modal button:active { + background-color: #6b9ed2; + } + +.components-rejoining-animation { + position: relative; + width: 80px; + height: 80px; +} + + .components-rejoining-animation div { + position: absolute; + border: 3px solid #0087ff; + opacity: 1; + border-radius: 50%; + animation: components-rejoining-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite; + } + + .components-rejoining-animation div:nth-child(2) { + animation-delay: -0.5s; + } + +@keyframes components-rejoining-animation { + 0% { + top: 40px; + left: 40px; + width: 0; + height: 0; + opacity: 0; + } + + 4.9% { + top: 40px; + left: 40px; + width: 0; + height: 0; + opacity: 0; + } + + 5% { + top: 40px; + left: 40px; + width: 0; + height: 0; + opacity: 1; + } + + 100% { + top: 0px; + left: 0px; + width: 80px; + height: 80px; + opacity: 0; + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/ReconnectModal.razor.js b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/ReconnectModal.razor.js new file mode 100644 index 00000000..a44de78d --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Layout/ReconnectModal.razor.js @@ -0,0 +1,63 @@ +// Set up event handlers +const reconnectModal = document.getElementById("components-reconnect-modal"); +reconnectModal.addEventListener("components-reconnect-state-changed", handleReconnectStateChanged); + +const retryButton = document.getElementById("components-reconnect-button"); +retryButton.addEventListener("click", retry); + +const resumeButton = document.getElementById("components-resume-button"); +resumeButton.addEventListener("click", resume); + +function handleReconnectStateChanged(event) { + if (event.detail.state === "show") { + reconnectModal.showModal(); + } else if (event.detail.state === "hide") { + reconnectModal.close(); + } else if (event.detail.state === "failed") { + document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible); + } else if (event.detail.state === "rejected") { + location.reload(); + } +} + +async function retry() { + document.removeEventListener("visibilitychange", retryWhenDocumentBecomesVisible); + + try { + // Reconnect will asynchronously return: + // - true to mean success + // - false to mean we reached the server, but it rejected the connection (e.g., unknown circuit ID) + // - exception to mean we didn't reach the server (this can be sync or async) + const successful = await Blazor.reconnect(); + if (!successful) { + // We have been able to reach the server, but the circuit is no longer available. + // We'll reload the page so the user can continue using the app as quickly as possible. + const resumeSuccessful = await Blazor.resumeCircuit(); + if (!resumeSuccessful) { + location.reload(); + } else { + reconnectModal.close(); + } + } + } catch (err) { + // We got an exception, server is currently unavailable + document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible); + } +} + +async function resume() { + try { + const successful = await Blazor.resumeCircuit(); + if (!successful) { + location.reload(); + } + } catch { + reconnectModal.classList.replace("components-reconnect-paused", "components-reconnect-resume-failed"); + } +} + +async function retryWhenDocumentBecomesVisible() { + if (document.visibilityState === "visible") { + await retry(); + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Error.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Error.razor new file mode 100644 index 00000000..576cc2d2 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Error.razor @@ -0,0 +1,36 @@ +@page "/Error" +@using System.Diagnostics + +Error + +

Error.

+

An error occurred while processing your request.

+ +@if (ShowRequestId) +{ +

+ Request ID: @RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

+ +@code{ + [CascadingParameter] + private HttpContext? HttpContext { get; set; } + + private string? RequestId { get; set; } + private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + protected override void OnInitialized() => + RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Home.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Home.razor new file mode 100644 index 00000000..76de9054 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Home.razor @@ -0,0 +1,444 @@ +@page "/home" +@attribute [Authorize] +@inherits UAuthFlowPageBase + +@inject IUAuthClient UAuthClient +@inject UAuthClientDiagnostics Diagnostics +@inject AuthenticationStateProvider AuthStateProvider +@inject ISnackbar Snackbar +@inject IDialogService DialogService +@using System.Security.Claims +@using CodeBeam.UltimateAuth.Core.Contracts +@using CodeBeam.UltimateAuth.Core.Defaults +@using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Custom +@using Microsoft.AspNetCore.Authorization + +@if (AuthState?.Identity?.UserStatus == UserStatus.SelfSuspended) +{ + + + + Your account is suspended. Please active it before continue. + + + + Set Active + Logout + + + + return; +} + +@if (AuthState?.Identity?.UserStatus == UserStatus.Suspended) +{ + + + + Your account is suspended. Please contact with administrator. + + + + Logout + + + + return; +} + + + + + + + + + + + Session + + + + + + + Validate + + + + + + Manual Refresh + + + + + + Logout + + + + + + Account + + + + + Manage Sessions + + + + Manage Profile + + + + Manage Identifiers + + + + Manage Credentials + + + + Suspend | Delete Account + + + + Admin + + + + + + + + + @if (_showAdminPreview) + { + + Admin operations are shown for preview. Sign in as an Admin to execute them. + + } + + @if (AuthState?.IsInRole("Admin") == true || _showAdminPreview) + { + + + + @* *@ + @* *@ + User Management + @* *@ + + + + + + @* *@ + Role Management + @* *@ + + + + } + + + + + + + + + + + @((AuthState?.Identity?.DisplayName ?? "?").Substring(0, Math.Min(2, (AuthState?.Identity?.DisplayName ?? "?").Length))) + + + + @AuthState?.Identity?.DisplayName + + @foreach (var role in AuthState?.Claims?.Roles ?? Enumerable.Empty()) + { + + @role + + } + + + + + + + + + + @if (_selectedAuthState == "UAuthState") + { + + +
+ + + Tenant + + @AuthState?.Identity?.Tenant.Value +
+ +
+ + +
+ + + User Id + + @AuthState?.Identity?.UserKey.Value +
+
+ + +
+ + + Authenticated + + @(AuthState?.IsAuthenticated == true ? "Yes" : "No") +
+
+ + +
+ + + Session State + + @AuthState?.Identity?.SessionState?.ToDescriptionString() +
+
+ + +
+ + + Username + + @AuthState?.Identity?.PrimaryUserName +
+
+ + +
+ + + Display Name + + @AuthState?.Identity?.DisplayName +
+
+ + + + + + + Email + + @AuthState?.Identity?.PrimaryEmail + + + + + + Phone + + @AuthState?.Identity?.PrimaryPhone + + + + + + + + Authenticated At + + @* TODO: Add IUAuthDateTimeFormatter *@ + @FormatLocalTime(AuthState?.Identity?.AuthenticatedAt) + + + + + + Last Validated At + + @* TODO: Validation call should update last validated at *@ + @FormatLocalTime(AuthState?.LastValidatedAt) + +
+ } + else if (_selectedAuthState == "AspNetCoreState") + { + + +
+ + + Authenticated + + @(_aspNetCoreState?.Identity?.IsAuthenticated == true ? "Yes" : "No") +
+
+ + +
+ + + User Id + + @_aspNetCoreState?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value +
+
+ + +
+ + + Username + + @_aspNetCoreState?.Identity?.Name +
+
+ + +
+ + + Authentication Type + + @_aspNetCoreState?.Identity?.AuthenticationType +
+
+
+ } +
+
+
+ + + + + + @GetHealthText() + + + Lifecycle + + + + + + Started + @Diagnostics.StartCount + + @if (Diagnostics.StartedAt is not null) + { + + + + @FormatRelative(Diagnostics.StartedAt) + + + } + + + + + Stopped + @Diagnostics.StopCount + + + + + + Terminated + @Diagnostics.TerminatedCount + + @if (Diagnostics.TerminatedAt is not null) + { + + + + + @FormatRelative(Diagnostics.TerminatedAt) + + + + } + + + + + + Refresh Metrics + + + + + + + Total Attempts + @Diagnostics.RefreshAttemptCount + + + + + + + Success + + @Diagnostics.RefreshSuccessCount + + + + + + Automatic + @Diagnostics.AutomaticRefreshCount + + + + + + Manual + @Diagnostics.ManualRefreshCount + + + + + + Touched/Rotated + @Diagnostics.RefreshTouchedCount / @Diagnostics.RefreshRotatedCount + + + + + + No-Op + @Diagnostics.RefreshNoOpCount + + + + + + Reauth Required + @Diagnostics.RefreshReauthRequiredCount + + + + + + + +
+
+
diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Home.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Home.razor.cs new file mode 100644 index 00000000..c5d1da90 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Home.razor.cs @@ -0,0 +1,222 @@ +using CodeBeam.UltimateAuth.Client.Blazor; +using CodeBeam.UltimateAuth.Client.Errors; +using CodeBeam.UltimateAuth.Core.Contracts; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Errors; +using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Common; +using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Dialogs; +using CodeBeam.UltimateAuth.Users.Contracts; +using Microsoft.AspNetCore.Components.Authorization; +using MudBlazor; +using System.Security.Claims; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Pages; + +public partial class Home : UAuthFlowPageBase +{ + private string _selectedAuthState = "UAuthState"; + private ClaimsPrincipal? _aspNetCoreState; + + private bool _showAdminPreview = false; + + protected override async Task OnInitializedAsync() + { + var initial = await AuthStateProvider.GetAuthenticationStateAsync(); + _aspNetCoreState = initial.User; + AuthStateProvider.AuthenticationStateChanged += OnAuthStateChanged; + Diagnostics.Changed += OnDiagnosticsChanged; + } + + private void OnAuthStateChanged(Task task) + { + _ = HandleAuthStateChangedAsync(task); + } + + private async Task HandleAuthStateChangedAsync(Task task) + { + try + { + var state = await task; + _aspNetCoreState = state.User; + await InvokeAsync(StateHasChanged); + } + catch + { + + } + } + + private void OnDiagnosticsChanged() + { + InvokeAsync(StateHasChanged); + } + + private async Task Logout() => await UAuthClient.Flows.LogoutAsync(); + + private async Task RefreshSession() => await UAuthClient.Flows.RefreshAsync(false); + + private async Task Validate() + { + try + { + var result = await UAuthClient.Flows.ValidateAsync(); + + if (result.IsValid) + { + if (result.Snapshot?.Identity.UserStatus == UserStatus.SelfSuspended) + { + Snackbar.Add("Your account is suspended by you.", Severity.Warning); + return; + } + Snackbar.Add($"Session active • Tenant: {result.Snapshot?.Identity?.Tenant.Value} • User: {result.Snapshot?.Identity?.PrimaryUserName}", Severity.Success); + } + else + { + switch (result.State) + { + case SessionState.Expired: + Snackbar.Add("Session expired. Please sign in again.", Severity.Warning); + break; + + case SessionState.DeviceMismatch: + Snackbar.Add("Session invalid for this device.", Severity.Error); + break; + + default: + Snackbar.Add($"Session state: {result.State}", Severity.Error); + break; + } + } + } + catch (UAuthTransportException) + { + Snackbar.Add("Network error.", Severity.Error); + } + catch (UAuthProtocolException) + { + Snackbar.Add("Invalid response.", Severity.Error); + } + catch (UAuthException ex) + { + Snackbar.Add($"UAuth error: {ex.Message}", Severity.Error); + } + catch (Exception ex) + { + Snackbar.Add($"Unexpected error: {ex.Message}", Severity.Error); + } + } + + private Color GetHealthColor() + { + if (Diagnostics.RefreshReauthRequiredCount > 0) + return Color.Warning; + + if (Diagnostics.TerminatedCount > 0) + return Color.Error; + + return Color.Success; + } + + private string GetHealthText() + { + if (Diagnostics.RefreshReauthRequiredCount > 0) + return "Reauthentication Required"; + + if (Diagnostics.TerminatedCount > 0) + return "Session Terminated"; + + return "Healthy"; + } + + private string? FormatRelative(DateTimeOffset? utc) + { + if (utc is null) + return null; + + var diff = DateTimeOffset.UtcNow - utc.Value; + + if (diff.TotalSeconds < 5) + return "just now"; + + if (diff.TotalSeconds < 60) + return $"{(int)diff.Seconds} secs ago"; + + if (diff.TotalMinutes < 60) + return $"{(int)diff.TotalMinutes} min ago"; + + if (diff.TotalHours < 24) + return $"{(int)diff.TotalHours} hrs ago"; + + return utc.Value.ToLocalTime().ToString("dd MMM yyyy"); + } + + private string? FormatLocalTime(DateTimeOffset? utc) + { + return utc?.ToLocalTime().ToString("dd MMM yyyy • HH:mm:ss"); + } + + private async Task OpenProfileDialog() + { + await DialogService.ShowAsync("Manage Profile", GetDialogParameters(), UAuthDialog.GetDialogOptions()); + } + + private async Task OpenIdentifierDialog() + { + await DialogService.ShowAsync("Manage Identifiers", GetDialogParameters(), UAuthDialog.GetDialogOptions()); + } + + private async Task OpenSessionDialog() + { + await DialogService.ShowAsync("Manage Sessions", GetDialogParameters(), UAuthDialog.GetDialogOptions()); + } + + private async Task OpenCredentialDialog() + { + await DialogService.ShowAsync("Session Diagnostics", GetDialogParameters(), UAuthDialog.GetDialogOptions()); + } + + private async Task OpenAccountStatusDialog() + { + await DialogService.ShowAsync("Manage Account", GetDialogParameters(), UAuthDialog.GetDialogOptions(MaxWidth.ExtraSmall)); + } + + private async Task OpenUserDialog() + { + await DialogService.ShowAsync("User Management", GetDialogParameters(), UAuthDialog.GetDialogOptions(MaxWidth.Large)); + } + + private async Task OpenRoleDialog() + { + await DialogService.ShowAsync("Role Management", GetDialogParameters(), UAuthDialog.GetDialogOptions()); + } + + private DialogParameters GetDialogParameters() + { + return new DialogParameters + { + ["AuthState"] = AuthState + }; + } + + private async Task SetAccountActiveAsync() + { + ChangeUserStatusSelfRequest request = new() { NewStatus = SelfUserStatus.Active }; + var result = await UAuthClient.Users.ChangeStatusSelfAsync(request); + + if (result.IsSuccess) + { + Snackbar.Add("Account activated successfully.", Severity.Success); + } + else + { + Snackbar.Add(result?.Problem?.Detail ?? result?.Problem?.Title ?? "Activation failed.", Severity.Error); + } + } + + public override void Dispose() + { + base.Dispose(); + AuthStateProvider.AuthenticationStateChanged -= OnAuthStateChanged; + Diagnostics.Changed -= OnDiagnosticsChanged; + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/LandingPage.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/LandingPage.razor new file mode 100644 index 00000000..1e4a9016 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/LandingPage.razor @@ -0,0 +1,4 @@ +@page "/" + +@inject NavigationManager Nav +@inject AuthenticationStateProvider AuthProvider diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/LandingPage.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/LandingPage.razor.cs new file mode 100644 index 00000000..5733e2eb --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/LandingPage.razor.cs @@ -0,0 +1,17 @@ +using CodeBeam.UltimateAuth.Core.Defaults; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Pages; + +public partial class LandingPage +{ + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender) + return; + + var state = await AuthProvider.GetAuthenticationStateAsync(); + var isAuthenticated = state.User.Identity?.IsAuthenticated == true; + + Nav.NavigateTo(isAuthenticated ? "/home" : $"{UAuthConstants.Routes.LoginRedirect}?fresh=true"); + } +} 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 new file mode 100644 index 00000000..f1d587c7 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Login.razor @@ -0,0 +1,126 @@ +@page "/login" +@attribute [UAuthLoginPage] +@inherits UAuthFlowPageBase + +@implements IDisposable +@inject IUAuthClient UAuthClient +@inject ISnackbar Snackbar +@inject IUAuthClientProductInfoProvider ClientProductInfoProvider +@inject IDeviceIdProvider DeviceIdProvider +@inject IDialogService DialogService + + + + + + + + + + + + + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Login.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Login.razor.cs new file mode 100644 index 00000000..0559a29b --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Login.razor.cs @@ -0,0 +1,211 @@ +using CodeBeam.UltimateAuth.Client.Blazor; +using CodeBeam.UltimateAuth.Client.Runtime; +using CodeBeam.UltimateAuth.Core.Contracts; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Dialogs; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Pages; + +public partial class Login : UAuthFlowPageBase +{ + private string? _username; + private string? _password; + private UAuthClientProductInfo? _productInfo; + private MudTextField _usernameField = default!; + + private CancellationTokenSource? _lockoutCts; + private PeriodicTimer? _lockoutTimer; + private DateTimeOffset? _lockoutUntil; + private TimeSpan _remaining; + private bool _isLocked; + private DateTimeOffset? _lockoutStartedAt; + private TimeSpan _lockoutDuration; + private double _progressPercent; + private int? _remainingAttempts = null; + + protected override async Task OnInitializedAsync() + { + _productInfo = ClientProductInfoProvider.Get(); + } + + protected override Task OnUAuthPayloadAsync(AuthFlowPayload payload) + { + HandleLoginPayload(payload); + return Task.CompletedTask; + } + + protected override async Task OnFocusRequestedAsync() + { + await _usernameField.FocusAsync(); + } + + private void HandleLoginPayload(AuthFlowPayload payload) + { + if (payload.Flow != AuthFlowType.Login) + return; + + if (payload.Reason == AuthFailureReason.LockedOut && payload.LockoutUntilUtc is { } until) + { + _lockoutUntil = until; + StartCountdown(); + } + + _remainingAttempts = payload.RemainingAttempts; + ShowLoginError(payload.Reason, payload.RemainingAttempts); + } + + private void ShowLoginError(AuthFailureReason? reason, int? remainingAttempts) + { + string message = reason switch + { + AuthFailureReason.InvalidCredentials when remainingAttempts is > 0 + => $"Invalid username or password. {remainingAttempts} attempt(s) remaining.", + + AuthFailureReason.InvalidCredentials + => "Invalid username or password.", + + AuthFailureReason.RequiresMfa + => "Multi-factor authentication required.", + + AuthFailureReason.LockedOut + => "Your account is locked.", + + _ => "Login failed." + }; + + Snackbar.Add(message, Severity.Error); + } + + private async Task ProgrammaticLogin() + { + var request = new LoginRequest + { + Identifier = "admin", + Secret = "admin", + }; + await UAuthClient.Flows.LoginAsync(request, ReturnUrl ?? "/home"); + } + + private async void StartCountdown() + { + if (_lockoutUntil is null) + return; + + _isLocked = true; + _lockoutStartedAt = DateTimeOffset.UtcNow; + _lockoutDuration = _lockoutUntil.Value - DateTimeOffset.UtcNow; + UpdateRemaining(); + + _lockoutCts?.Cancel(); + _lockoutCts = new CancellationTokenSource(); + + _lockoutTimer?.Dispose(); + _lockoutTimer = new PeriodicTimer(TimeSpan.FromSeconds(1)); + + try + { + while (await _lockoutTimer.WaitForNextTickAsync(_lockoutCts.Token)) + { + UpdateRemaining(); + + if (_remaining <= TimeSpan.Zero) + { + ResetLockoutState(); + await InvokeAsync(StateHasChanged); + break; + } + + await InvokeAsync(StateHasChanged); + } + } + catch (OperationCanceledException) + { + + } + } + + private void ResetLockoutState() + { + _isLocked = false; + _lockoutUntil = null; + _progressPercent = 0; + _remainingAttempts = null; + } + + private void UpdateRemaining() + { + if (_lockoutUntil is null || _lockoutStartedAt is null) + return; + + var now = DateTimeOffset.UtcNow; + + _remaining = _lockoutUntil.Value - now; + + if (_remaining <= TimeSpan.Zero) + { + _remaining = TimeSpan.Zero; + return; + } + + var elapsed = now - _lockoutStartedAt.Value; + + if (_lockoutDuration.TotalSeconds > 0) + { + var percent = 100 - (elapsed.TotalSeconds / _lockoutDuration.TotalSeconds * 100); + _progressPercent = Math.Max(0, percent); + } + } + + private void HandleTry(IUAuthTryResult result) + { + if (result is TryLoginResult pkce) + { + if (!result.Success) + { + if (result.Reason == AuthFailureReason.LockedOut && result.LockoutUntilUtc is { } until) + { + _lockoutUntil = until; + StartCountdown(); + } + + _remainingAttempts = result.RemainingAttempts; + ShowLoginError(result.Reason, result.RemainingAttempts); + } + } + else + { + Snackbar.Add("Unexpected result type.", Severity.Error); + } + } + + private async Task OpenResetDialog() + { + await DialogService.ShowAsync("Reset Credentials", GetDialogParameters(), GetDialogOptions()); + } + + private DialogOptions GetDialogOptions() + { + return new DialogOptions + { + MaxWidth = MaxWidth.Medium, + FullWidth = true, + CloseButton = true + }; + } + + private DialogParameters GetDialogParameters() + { + return new DialogParameters + { + ["AuthState"] = AuthState + }; + } + + public override void Dispose() + { + base.Dispose(); + _lockoutCts?.Cancel(); + _lockoutTimer?.Dispose(); + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/NotAuthorized.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/NotAuthorized.razor new file mode 100644 index 00000000..d8eb7138 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/NotAuthorized.razor @@ -0,0 +1,27 @@ +@inject NavigationManager Nav + + + + + + + Access Denied + + + You don’t have permission to view this page. + If you think this is a mistake, sign in with a different account or request access. + + + + Sign In + Go Back + + + + + + UltimateAuth protects this resource based on your session and permissions. + + + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/NotAuthorized.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/NotAuthorized.razor.cs new file mode 100644 index 00000000..b38fbe97 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/NotAuthorized.razor.cs @@ -0,0 +1,15 @@ +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Pages; + +public partial class NotAuthorized +{ + private string LoginHref + { + get + { + var returnUrl = Uri.EscapeDataString(Nav.ToBaseRelativePath(Nav.Uri)); + return $"/login?returnUrl=/{returnUrl}"; + } + } + + private void GoBack() => Nav.NavigateTo("/", replace: false); +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Routes.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Routes.razor new file mode 100644 index 00000000..033ad7bd --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Routes.razor @@ -0,0 +1,73 @@ +@using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Pages +@using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Infrastructure +@inject ISnackbar Snackbar +@inject DarkModeManager DarkModeManager + + + + + + + + + + + + + + + + @* Advanced: you can fully control routing by providing your own Router *@ + @* + + + + + + + + + + + + + + + + *@ + + +@code { + private async Task HandleReauth() + { + Snackbar.Add("Reauthentication required. Please log in again.", Severity.Warning); + } + + #region DarkMode + + protected override void OnInitialized() + { + DarkModeManager.Changed += OnThemeChanged; + } + + private void OnThemeChanged() + { + InvokeAsync(StateHasChanged); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + await DarkModeManager.InitializeAsync(); + StateHasChanged(); + } + } + + public void Dispose() + { + DarkModeManager.Changed -= OnThemeChanged; + } + + #endregion +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/_Imports.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/_Imports.razor new file mode 100644 index 00000000..88b26436 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/_Imports.razor @@ -0,0 +1,22 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Authorization +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore +@using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components +@using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Layout + +@using CodeBeam.UltimateAuth.Core.Abstractions +@using CodeBeam.UltimateAuth.Core.Domain +@using CodeBeam.UltimateAuth.Client +@using CodeBeam.UltimateAuth.Client.Runtime +@using CodeBeam.UltimateAuth.Client.Diagnostics +@using CodeBeam.UltimateAuth.Client.Blazor + +@using MudBlazor +@using MudExtensions diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Infrastructure/DarkModeManager.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Infrastructure/DarkModeManager.cs new file mode 100644 index 00000000..befbcb29 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Infrastructure/DarkModeManager.cs @@ -0,0 +1,45 @@ +using CodeBeam.UltimateAuth.Client.Contracts; +using CodeBeam.UltimateAuth.Client.Infrastructure; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Infrastructure; + +public sealed class DarkModeManager +{ + private const string StorageKey = "uauth:theme:dark"; + + private readonly IClientStorage _storage; + + public DarkModeManager(IClientStorage storage) + { + _storage = storage; + } + + public async Task InitializeAsync() + { + var value = await _storage.GetAsync(StorageScope.Local, StorageKey); + + if (bool.TryParse(value, out var parsed)) + IsDarkMode = parsed; + } + + public bool IsDarkMode { get; set; } + + public event Action? Changed; + + public async Task ToggleAsync() + { + IsDarkMode = !IsDarkMode; + + await _storage.SetAsync(StorageScope.Local, StorageKey, IsDarkMode.ToString()); + Changed?.Invoke(); + } + + public void Set(bool value) + { + if (IsDarkMode == value) + return; + + IsDarkMode = value; + Changed?.Invoke(); + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260326115139_InitSessions.Designer.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260326115139_InitSessions.Designer.cs new file mode 100644 index 00000000..c540962d --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260326115139_InitSessions.Designer.cs @@ -0,0 +1,238 @@ +// +using System; +using CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations +{ + [DbContext(typeof(UAuthSessionDbContext))] + [Migration("20260326115139_InitSessions")] + partial class InitSessions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AbsoluteExpiresAt") + .HasColumnType("TEXT"); + + b.Property("ActiveSessionId") + .HasColumnType("TEXT"); + + b.Property("ChainId") + .HasColumnType("TEXT"); + + b.Property("ClaimsSnapshot") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Device") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("LastSeenAt") + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("RootId") + .HasColumnType("TEXT"); + + b.Property("RotationCount") + .HasColumnType("INTEGER"); + + b.Property("SecurityVersionAtCreation") + .HasColumnType("INTEGER"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("TouchCount") + .HasColumnType("INTEGER"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "ChainId") + .IsUnique(); + + b.HasIndex("Tenant", "RootId"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "UserKey", "DeviceId"); + + b.ToTable("UAuth_SessionChains", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChainId") + .HasColumnType("TEXT"); + + b.Property("Claims") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Device") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("SecurityVersionAtCreation") + .HasColumnType("INTEGER"); + + b.Property("SessionId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "ChainId"); + + b.HasIndex("Tenant", "ExpiresAt"); + + b.HasIndex("Tenant", "RevokedAt"); + + b.HasIndex("Tenant", "SessionId") + .IsUnique(); + + b.HasIndex("Tenant", "ChainId", "RevokedAt"); + + b.HasIndex("Tenant", "UserKey", "RevokedAt"); + + b.ToTable("UAuth_Sessions", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionRootProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("RootId") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("SecurityVersion") + .HasColumnType("INTEGER"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "RootId") + .IsUnique(); + + b.HasIndex("Tenant", "UserKey") + .IsUnique(); + + b.ToTable("UAuth_SessionRoots", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", b => + { + b.HasOne("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionRootProjection", null) + .WithMany() + .HasForeignKey("Tenant", "RootId") + .HasPrincipalKey("Tenant", "RootId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionProjection", b => + { + b.HasOne("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", null) + .WithMany() + .HasForeignKey("Tenant", "ChainId") + .HasPrincipalKey("Tenant", "ChainId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260326115139_InitSessions.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260326115139_InitSessions.cs new file mode 100644 index 00000000..6846225f --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260326115139_InitSessions.cs @@ -0,0 +1,178 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations +{ + /// + public partial class InitSessions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UAuth_SessionRoots", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RootId = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + RevokedAt = table.Column(type: "TEXT", nullable: true), + SecurityVersion = table.Column(type: "INTEGER", nullable: false), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_SessionRoots", x => x.Id); + table.UniqueConstraint("AK_UAuth_SessionRoots_Tenant_RootId", x => new { x.Tenant, x.RootId }); + }); + + migrationBuilder.CreateTable( + name: "UAuth_SessionChains", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ChainId = table.Column(type: "TEXT", nullable: false), + RootId = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + LastSeenAt = table.Column(type: "TEXT", nullable: false), + AbsoluteExpiresAt = table.Column(type: "TEXT", nullable: true), + DeviceId = table.Column(type: "TEXT", maxLength: 64, nullable: false), + Device = table.Column(type: "TEXT", nullable: false), + ClaimsSnapshot = table.Column(type: "TEXT", nullable: false), + ActiveSessionId = table.Column(type: "TEXT", nullable: true), + RotationCount = table.Column(type: "INTEGER", nullable: false), + TouchCount = table.Column(type: "INTEGER", nullable: false), + SecurityVersionAtCreation = table.Column(type: "INTEGER", nullable: false), + RevokedAt = table.Column(type: "TEXT", nullable: true), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_SessionChains", x => x.Id); + table.UniqueConstraint("AK_UAuth_SessionChains_Tenant_ChainId", x => new { x.Tenant, x.ChainId }); + table.ForeignKey( + name: "FK_UAuth_SessionChains_UAuth_SessionRoots_Tenant_RootId", + columns: x => new { x.Tenant, x.RootId }, + principalTable: "UAuth_SessionRoots", + principalColumns: new[] { "Tenant", "RootId" }, + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "UAuth_Sessions", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + SessionId = table.Column(type: "TEXT", nullable: false), + ChainId = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + ExpiresAt = table.Column(type: "TEXT", nullable: false), + RevokedAt = table.Column(type: "TEXT", nullable: true), + SecurityVersionAtCreation = table.Column(type: "INTEGER", nullable: false), + Device = table.Column(type: "TEXT", nullable: false), + Claims = table.Column(type: "TEXT", nullable: false), + Metadata = table.Column(type: "TEXT", nullable: false), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_Sessions", x => x.Id); + table.ForeignKey( + name: "FK_UAuth_Sessions_UAuth_SessionChains_Tenant_ChainId", + columns: x => new { x.Tenant, x.ChainId }, + principalTable: "UAuth_SessionChains", + principalColumns: new[] { "Tenant", "ChainId" }, + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_SessionChains_Tenant_ChainId", + table: "UAuth_SessionChains", + columns: new[] { "Tenant", "ChainId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_SessionChains_Tenant_RootId", + table: "UAuth_SessionChains", + columns: new[] { "Tenant", "RootId" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_SessionChains_Tenant_UserKey", + table: "UAuth_SessionChains", + columns: new[] { "Tenant", "UserKey" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_SessionChains_Tenant_UserKey_DeviceId", + table: "UAuth_SessionChains", + columns: new[] { "Tenant", "UserKey", "DeviceId" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_SessionRoots_Tenant_RootId", + table: "UAuth_SessionRoots", + columns: new[] { "Tenant", "RootId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_SessionRoots_Tenant_UserKey", + table: "UAuth_SessionRoots", + columns: new[] { "Tenant", "UserKey" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Sessions_Tenant_ChainId", + table: "UAuth_Sessions", + columns: new[] { "Tenant", "ChainId" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Sessions_Tenant_ChainId_RevokedAt", + table: "UAuth_Sessions", + columns: new[] { "Tenant", "ChainId", "RevokedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Sessions_Tenant_ExpiresAt", + table: "UAuth_Sessions", + columns: new[] { "Tenant", "ExpiresAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Sessions_Tenant_RevokedAt", + table: "UAuth_Sessions", + columns: new[] { "Tenant", "RevokedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Sessions_Tenant_SessionId", + table: "UAuth_Sessions", + columns: new[] { "Tenant", "SessionId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Sessions_Tenant_UserKey_RevokedAt", + table: "UAuth_Sessions", + columns: new[] { "Tenant", "UserKey", "RevokedAt" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UAuth_Sessions"); + + migrationBuilder.DropTable( + name: "UAuth_SessionChains"); + + migrationBuilder.DropTable( + name: "UAuth_SessionRoots"); + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/20260326121436_InitAuthentication.Designer.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/20260326121436_InitAuthentication.Designer.cs new file mode 100644 index 00000000..198e9c90 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/20260326121436_InitAuthentication.Designer.cs @@ -0,0 +1,95 @@ +// +using System; +using CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthAuthenticationDb +{ + [DbContext(typeof(UAuthAuthenticationDbContext))] + [Migration("20260326121436_InitAuthentication")] + partial class InitAuthentication + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore.AuthenticationSecurityStateProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CredentialType") + .HasColumnType("INTEGER"); + + b.Property("FailedAttempts") + .HasColumnType("INTEGER"); + + b.Property("LastFailedAt") + .HasColumnType("TEXT"); + + b.Property("LockedUntil") + .HasColumnType("TEXT"); + + b.Property("RequiresReauthentication") + .HasColumnType("INTEGER"); + + b.Property("ResetAttempts") + .HasColumnType("INTEGER"); + + b.Property("ResetConsumedAt") + .HasColumnType("TEXT"); + + b.Property("ResetExpiresAt") + .HasColumnType("TEXT"); + + b.Property("ResetRequestedAt") + .HasColumnType("TEXT"); + + b.Property("ResetTokenHash") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Scope") + .HasColumnType("INTEGER"); + + b.Property("SecurityVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "LockedUntil"); + + b.HasIndex("Tenant", "ResetRequestedAt"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "UserKey", "Scope"); + + b.HasIndex("Tenant", "UserKey", "Scope", "CredentialType") + .IsUnique(); + + b.ToTable("UAuth_Authentication", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/20260326121436_InitAuthentication.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/20260326121436_InitAuthentication.cs new file mode 100644 index 00000000..1410deff --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/20260326121436_InitAuthentication.cs @@ -0,0 +1,73 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthAuthenticationDb +{ + /// + public partial class InitAuthentication : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UAuth_Authentication", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Scope = table.Column(type: "INTEGER", nullable: false), + CredentialType = table.Column(type: "INTEGER", nullable: true), + FailedAttempts = table.Column(type: "INTEGER", nullable: false), + LastFailedAt = table.Column(type: "TEXT", nullable: true), + LockedUntil = table.Column(type: "TEXT", nullable: true), + RequiresReauthentication = table.Column(type: "INTEGER", nullable: false), + ResetRequestedAt = table.Column(type: "TEXT", nullable: true), + ResetExpiresAt = table.Column(type: "TEXT", nullable: true), + ResetConsumedAt = table.Column(type: "TEXT", nullable: true), + ResetTokenHash = table.Column(type: "TEXT", maxLength: 512, nullable: true), + ResetAttempts = table.Column(type: "INTEGER", nullable: false), + SecurityVersion = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_Authentication", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Authentication_Tenant_LockedUntil", + table: "UAuth_Authentication", + columns: new[] { "Tenant", "LockedUntil" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Authentication_Tenant_ResetRequestedAt", + table: "UAuth_Authentication", + columns: new[] { "Tenant", "ResetRequestedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Authentication_Tenant_UserKey", + table: "UAuth_Authentication", + columns: new[] { "Tenant", "UserKey" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Authentication_Tenant_UserKey_Scope", + table: "UAuth_Authentication", + columns: new[] { "Tenant", "UserKey", "Scope" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Authentication_Tenant_UserKey_Scope_CredentialType", + table: "UAuth_Authentication", + columns: new[] { "Tenant", "UserKey", "Scope", "CredentialType" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UAuth_Authentication"); + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/UAuthAuthenticationDbContextModelSnapshot.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/UAuthAuthenticationDbContextModelSnapshot.cs new file mode 100644 index 00000000..acc4b9a5 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/UAuthAuthenticationDbContextModelSnapshot.cs @@ -0,0 +1,92 @@ +// +using System; +using CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthAuthenticationDb +{ + [DbContext(typeof(UAuthAuthenticationDbContext))] + partial class UAuthAuthenticationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore.AuthenticationSecurityStateProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CredentialType") + .HasColumnType("INTEGER"); + + b.Property("FailedAttempts") + .HasColumnType("INTEGER"); + + b.Property("LastFailedAt") + .HasColumnType("TEXT"); + + b.Property("LockedUntil") + .HasColumnType("TEXT"); + + b.Property("RequiresReauthentication") + .HasColumnType("INTEGER"); + + b.Property("ResetAttempts") + .HasColumnType("INTEGER"); + + b.Property("ResetConsumedAt") + .HasColumnType("TEXT"); + + b.Property("ResetExpiresAt") + .HasColumnType("TEXT"); + + b.Property("ResetRequestedAt") + .HasColumnType("TEXT"); + + b.Property("ResetTokenHash") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Scope") + .HasColumnType("INTEGER"); + + b.Property("SecurityVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "LockedUntil"); + + b.HasIndex("Tenant", "ResetRequestedAt"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "UserKey", "Scope"); + + b.HasIndex("Tenant", "UserKey", "Scope", "CredentialType") + .IsUnique(); + + b.ToTable("UAuth_Authentication", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/20260326121602_InitAuthorization.Designer.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/20260326121602_InitAuthorization.Designer.cs new file mode 100644 index 00000000..8e1e0351 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/20260326121602_InitAuthorization.Designer.cs @@ -0,0 +1,116 @@ +// +using System; +using CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthAuthorizationDb +{ + [DbContext(typeof(UAuthAuthorizationDbContext))] + [Migration("20260326121602_InitAuthorization")] + partial class InitAuthorization + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.RolePermissionProjection", b => + { + b.Property("Tenant") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.Property("Permission") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Tenant", "RoleId", "Permission"); + + b.HasIndex("Tenant", "Permission"); + + b.HasIndex("Tenant", "RoleId"); + + b.ToTable("UAuth_RolePermissions", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.RoleProjection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "Id") + .IsUnique(); + + b.HasIndex("Tenant", "NormalizedName") + .IsUnique(); + + b.ToTable("UAuth_Roles", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.UserRoleProjection", b => + { + b.Property("Tenant") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UserKey") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.Property("AssignedAt") + .HasColumnType("TEXT"); + + b.HasKey("Tenant", "UserKey", "RoleId"); + + b.HasIndex("Tenant", "RoleId"); + + b.HasIndex("Tenant", "UserKey"); + + b.ToTable("UAuth_UserRoles", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/20260326121602_InitAuthorization.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/20260326121602_InitAuthorization.cs new file mode 100644 index 00000000..11cb3664 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/20260326121602_InitAuthorization.cs @@ -0,0 +1,105 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthAuthorizationDb +{ + /// + public partial class InitAuthorization : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UAuth_RolePermissions", + columns: table => new + { + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + RoleId = table.Column(type: "TEXT", nullable: false), + Permission = table.Column(type: "TEXT", maxLength: 256, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_RolePermissions", x => new { x.Tenant, x.RoleId, x.Permission }); + }); + + migrationBuilder.CreateTable( + name: "UAuth_Roles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Name = table.Column(type: "TEXT", maxLength: 128, nullable: false), + NormalizedName = table.Column(type: "TEXT", maxLength: 128, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + DeletedAt = table.Column(type: "TEXT", nullable: true), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_Roles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UAuth_UserRoles", + columns: table => new + { + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + RoleId = table.Column(type: "TEXT", nullable: false), + AssignedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_UserRoles", x => new { x.Tenant, x.UserKey, x.RoleId }); + }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RolePermissions_Tenant_Permission", + table: "UAuth_RolePermissions", + columns: new[] { "Tenant", "Permission" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RolePermissions_Tenant_RoleId", + table: "UAuth_RolePermissions", + columns: new[] { "Tenant", "RoleId" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Roles_Tenant_Id", + table: "UAuth_Roles", + columns: new[] { "Tenant", "Id" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Roles_Tenant_NormalizedName", + table: "UAuth_Roles", + columns: new[] { "Tenant", "NormalizedName" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserRoles_Tenant_RoleId", + table: "UAuth_UserRoles", + columns: new[] { "Tenant", "RoleId" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserRoles_Tenant_UserKey", + table: "UAuth_UserRoles", + columns: new[] { "Tenant", "UserKey" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UAuth_RolePermissions"); + + migrationBuilder.DropTable( + name: "UAuth_Roles"); + + migrationBuilder.DropTable( + name: "UAuth_UserRoles"); + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/UAuthAuthorizationDbContextModelSnapshot.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/UAuthAuthorizationDbContextModelSnapshot.cs new file mode 100644 index 00000000..5487d6ed --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/UAuthAuthorizationDbContextModelSnapshot.cs @@ -0,0 +1,113 @@ +// +using System; +using CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthAuthorizationDb +{ + [DbContext(typeof(UAuthAuthorizationDbContext))] + partial class UAuthAuthorizationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.RolePermissionProjection", b => + { + b.Property("Tenant") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.Property("Permission") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Tenant", "RoleId", "Permission"); + + b.HasIndex("Tenant", "Permission"); + + b.HasIndex("Tenant", "RoleId"); + + b.ToTable("UAuth_RolePermissions", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.RoleProjection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "Id") + .IsUnique(); + + b.HasIndex("Tenant", "NormalizedName") + .IsUnique(); + + b.ToTable("UAuth_Roles", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.UserRoleProjection", b => + { + b.Property("Tenant") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UserKey") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.Property("AssignedAt") + .HasColumnType("TEXT"); + + b.HasKey("Tenant", "UserKey", "RoleId"); + + b.HasIndex("Tenant", "RoleId"); + + b.HasIndex("Tenant", "UserKey"); + + b.ToTable("UAuth_UserRoles", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/20260326121243_InitCredentials.Designer.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/20260326121243_InitCredentials.Designer.cs new file mode 100644 index 00000000..629e4bb8 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/20260326121243_InitCredentials.Designer.cs @@ -0,0 +1,91 @@ +// +using System; +using CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthCredentialDb +{ + [DbContext(typeof(UAuthCredentialDbContext))] + [Migration("20260326121243_InitCredentials")] + partial class InitCredentials + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore.PasswordCredentialProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("LastUsedAt") + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("SecretHash") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "ExpiresAt"); + + b.HasIndex("Tenant", "Id") + .IsUnique(); + + b.HasIndex("Tenant", "RevokedAt"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "UserKey", "DeletedAt"); + + b.ToTable("UAuth_PasswordCredentials", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/20260326121243_InitCredentials.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/20260326121243_InitCredentials.cs new file mode 100644 index 00000000..a206e802 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/20260326121243_InitCredentials.cs @@ -0,0 +1,71 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthCredentialDb +{ + /// + public partial class InitCredentials : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UAuth_PasswordCredentials", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + SecretHash = table.Column(type: "TEXT", maxLength: 512, nullable: false), + RevokedAt = table.Column(type: "TEXT", nullable: true), + ExpiresAt = table.Column(type: "TEXT", nullable: true), + SecurityStamp = table.Column(type: "TEXT", nullable: false), + LastUsedAt = table.Column(type: "TEXT", nullable: true), + Source = table.Column(type: "TEXT", maxLength: 128, nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + DeletedAt = table.Column(type: "TEXT", nullable: true), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_PasswordCredentials", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_PasswordCredentials_Tenant_ExpiresAt", + table: "UAuth_PasswordCredentials", + columns: new[] { "Tenant", "ExpiresAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_PasswordCredentials_Tenant_Id", + table: "UAuth_PasswordCredentials", + columns: new[] { "Tenant", "Id" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_PasswordCredentials_Tenant_RevokedAt", + table: "UAuth_PasswordCredentials", + columns: new[] { "Tenant", "RevokedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_PasswordCredentials_Tenant_UserKey", + table: "UAuth_PasswordCredentials", + columns: new[] { "Tenant", "UserKey" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_PasswordCredentials_Tenant_UserKey_DeletedAt", + table: "UAuth_PasswordCredentials", + columns: new[] { "Tenant", "UserKey", "DeletedAt" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UAuth_PasswordCredentials"); + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/UAuthCredentialDbContextModelSnapshot.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/UAuthCredentialDbContextModelSnapshot.cs new file mode 100644 index 00000000..858dbb27 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/UAuthCredentialDbContextModelSnapshot.cs @@ -0,0 +1,88 @@ +// +using System; +using CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthCredentialDb +{ + [DbContext(typeof(UAuthCredentialDbContext))] + partial class UAuthCredentialDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore.PasswordCredentialProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("LastUsedAt") + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("SecretHash") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "ExpiresAt"); + + b.HasIndex("Tenant", "Id") + .IsUnique(); + + b.HasIndex("Tenant", "RevokedAt"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "UserKey", "DeletedAt"); + + b.ToTable("UAuth_PasswordCredentials", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthSessionDbContextModelSnapshot.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthSessionDbContextModelSnapshot.cs new file mode 100644 index 00000000..7b2a1b2c --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthSessionDbContextModelSnapshot.cs @@ -0,0 +1,235 @@ +// +using System; +using CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations +{ + [DbContext(typeof(UAuthSessionDbContext))] + partial class UAuthSessionDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AbsoluteExpiresAt") + .HasColumnType("TEXT"); + + b.Property("ActiveSessionId") + .HasColumnType("TEXT"); + + b.Property("ChainId") + .HasColumnType("TEXT"); + + b.Property("ClaimsSnapshot") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Device") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("LastSeenAt") + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("RootId") + .HasColumnType("TEXT"); + + b.Property("RotationCount") + .HasColumnType("INTEGER"); + + b.Property("SecurityVersionAtCreation") + .HasColumnType("INTEGER"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("TouchCount") + .HasColumnType("INTEGER"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "ChainId") + .IsUnique(); + + b.HasIndex("Tenant", "RootId"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "UserKey", "DeviceId"); + + b.ToTable("UAuth_SessionChains", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChainId") + .HasColumnType("TEXT"); + + b.Property("Claims") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Device") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("SecurityVersionAtCreation") + .HasColumnType("INTEGER"); + + b.Property("SessionId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "ChainId"); + + b.HasIndex("Tenant", "ExpiresAt"); + + b.HasIndex("Tenant", "RevokedAt"); + + b.HasIndex("Tenant", "SessionId") + .IsUnique(); + + b.HasIndex("Tenant", "ChainId", "RevokedAt"); + + b.HasIndex("Tenant", "UserKey", "RevokedAt"); + + b.ToTable("UAuth_Sessions", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionRootProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("RootId") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("SecurityVersion") + .HasColumnType("INTEGER"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "RootId") + .IsUnique(); + + b.HasIndex("Tenant", "UserKey") + .IsUnique(); + + b.ToTable("UAuth_SessionRoots", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", b => + { + b.HasOne("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionRootProjection", null) + .WithMany() + .HasForeignKey("Tenant", "RootId") + .HasPrincipalKey("Tenant", "RootId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionProjection", b => + { + b.HasOne("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", null) + .WithMany() + .HasForeignKey("Tenant", "ChainId") + .HasPrincipalKey("Tenant", "ChainId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/20260326120856_InitTokens.Designer.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/20260326120856_InitTokens.Designer.cs new file mode 100644 index 00000000..95f4fed6 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/20260326120856_InitTokens.Designer.cs @@ -0,0 +1,96 @@ +// +using System; +using CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthTokenDb +{ + [DbContext(typeof(UAuthTokenDbContext))] + [Migration("20260326120856_InitTokens")] + partial class InitTokens + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore.RefreshTokenProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChainId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("ReplacedByTokenHash") + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("TokenHash") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("TokenId") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "ChainId"); + + b.HasIndex("Tenant", "ExpiresAt"); + + b.HasIndex("Tenant", "ReplacedByTokenHash"); + + b.HasIndex("Tenant", "SessionId"); + + b.HasIndex("Tenant", "TokenHash") + .IsUnique(); + + b.HasIndex("Tenant", "TokenId"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "ExpiresAt", "RevokedAt"); + + b.HasIndex("Tenant", "TokenHash", "RevokedAt"); + + b.ToTable("UAuth_RefreshTokens", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/20260326120856_InitTokens.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/20260326120856_InitTokens.cs new file mode 100644 index 00000000..474cec55 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/20260326120856_InitTokens.cs @@ -0,0 +1,91 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthTokenDb +{ + /// + public partial class InitTokens : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UAuth_RefreshTokens", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + TokenId = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + TokenHash = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + SessionId = table.Column(type: "TEXT", nullable: false), + ChainId = table.Column(type: "TEXT", nullable: true), + ReplacedByTokenHash = table.Column(type: "TEXT", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + ExpiresAt = table.Column(type: "TEXT", nullable: false), + RevokedAt = table.Column(type: "TEXT", nullable: true), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_RefreshTokens", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_ChainId", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "ChainId" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_ExpiresAt", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "ExpiresAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_ExpiresAt_RevokedAt", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "ExpiresAt", "RevokedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_ReplacedByTokenHash", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "ReplacedByTokenHash" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_SessionId", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "SessionId" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_TokenHash", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "TokenHash" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_TokenHash_RevokedAt", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "TokenHash", "RevokedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_TokenId", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "TokenId" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_UserKey", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "UserKey" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UAuth_RefreshTokens"); + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/UAuthTokenDbContextModelSnapshot.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/UAuthTokenDbContextModelSnapshot.cs new file mode 100644 index 00000000..1cb54a1b --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/UAuthTokenDbContextModelSnapshot.cs @@ -0,0 +1,93 @@ +// +using System; +using CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthTokenDb +{ + [DbContext(typeof(UAuthTokenDbContext))] + partial class UAuthTokenDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore.RefreshTokenProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChainId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("ReplacedByTokenHash") + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("TokenHash") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("TokenId") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "ChainId"); + + b.HasIndex("Tenant", "ExpiresAt"); + + b.HasIndex("Tenant", "ReplacedByTokenHash"); + + b.HasIndex("Tenant", "SessionId"); + + b.HasIndex("Tenant", "TokenHash") + .IsUnique(); + + b.HasIndex("Tenant", "TokenId"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "ExpiresAt", "RevokedAt"); + + b.HasIndex("Tenant", "TokenHash", "RevokedAt"); + + b.ToTable("UAuth_RefreshTokens", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/20260326121123_InitUsers.Designer.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/20260326121123_InitUsers.Designer.cs new file mode 100644 index 00000000..cbef033f --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/20260326121123_InitUsers.Designer.cs @@ -0,0 +1,198 @@ +// +using System; +using CodeBeam.UltimateAuth.Users.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthUserDb +{ + [DbContext(typeof(UAuthUserDbContext))] + [Migration("20260326121123_InitUsers")] + partial class InitUsers + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserIdentifierProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("IsPrimary") + .HasColumnType("INTEGER"); + + b.Property("NormalizedValue") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("VerifiedAt") + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "NormalizedValue"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "Type", "NormalizedValue") + .IsUnique(); + + b.HasIndex("Tenant", "UserKey", "IsPrimary"); + + b.HasIndex("Tenant", "UserKey", "Type", "IsPrimary"); + + b.ToTable("UAuth_UserIdentifiers", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserLifecycleProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("SecurityVersion") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "UserKey") + .IsUnique(); + + b.ToTable("UAuth_UserLifecycles", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserProfileProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Bio") + .HasColumnType("TEXT"); + + b.Property("BirthDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Culture") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("FirstName") + .HasColumnType("TEXT"); + + b.Property("Gender") + .HasColumnType("TEXT"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LastName") + .HasColumnType("TEXT"); + + b.Property("Metadata") + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("TimeZone") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "UserKey"); + + b.ToTable("UAuth_UserProfiles", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/20260326121123_InitUsers.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/20260326121123_InitUsers.cs new file mode 100644 index 00000000..4e74b680 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/20260326121123_InitUsers.cs @@ -0,0 +1,133 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthUserDb +{ + /// + public partial class InitUsers : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UAuth_UserIdentifiers", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Type = table.Column(type: "INTEGER", nullable: false), + Value = table.Column(type: "TEXT", maxLength: 256, nullable: false), + NormalizedValue = table.Column(type: "TEXT", maxLength: 256, nullable: false), + IsPrimary = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + VerifiedAt = table.Column(type: "TEXT", nullable: true), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + DeletedAt = table.Column(type: "TEXT", nullable: true), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_UserIdentifiers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UAuth_UserLifecycles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Status = table.Column(type: "INTEGER", nullable: false), + SecurityVersion = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + DeletedAt = table.Column(type: "TEXT", nullable: true), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_UserLifecycles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UAuth_UserProfiles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + FirstName = table.Column(type: "TEXT", nullable: true), + LastName = table.Column(type: "TEXT", nullable: true), + DisplayName = table.Column(type: "TEXT", nullable: true), + BirthDate = table.Column(type: "TEXT", nullable: true), + Gender = table.Column(type: "TEXT", nullable: true), + Bio = table.Column(type: "TEXT", nullable: true), + Language = table.Column(type: "TEXT", nullable: true), + TimeZone = table.Column(type: "TEXT", nullable: true), + Culture = table.Column(type: "TEXT", nullable: true), + Metadata = table.Column(type: "TEXT", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + DeletedAt = table.Column(type: "TEXT", nullable: true), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_UserProfiles", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserIdentifiers_Tenant_NormalizedValue", + table: "UAuth_UserIdentifiers", + columns: new[] { "Tenant", "NormalizedValue" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserIdentifiers_Tenant_Type_NormalizedValue", + table: "UAuth_UserIdentifiers", + columns: new[] { "Tenant", "Type", "NormalizedValue" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserIdentifiers_Tenant_UserKey", + table: "UAuth_UserIdentifiers", + columns: new[] { "Tenant", "UserKey" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserIdentifiers_Tenant_UserKey_IsPrimary", + table: "UAuth_UserIdentifiers", + columns: new[] { "Tenant", "UserKey", "IsPrimary" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserIdentifiers_Tenant_UserKey_Type_IsPrimary", + table: "UAuth_UserIdentifiers", + columns: new[] { "Tenant", "UserKey", "Type", "IsPrimary" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserLifecycles_Tenant_UserKey", + table: "UAuth_UserLifecycles", + columns: new[] { "Tenant", "UserKey" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserProfiles_Tenant_UserKey", + table: "UAuth_UserProfiles", + columns: new[] { "Tenant", "UserKey" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UAuth_UserIdentifiers"); + + migrationBuilder.DropTable( + name: "UAuth_UserLifecycles"); + + migrationBuilder.DropTable( + name: "UAuth_UserProfiles"); + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/UAuthUserDbContextModelSnapshot.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/UAuthUserDbContextModelSnapshot.cs new file mode 100644 index 00000000..cfe195d3 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/UAuthUserDbContextModelSnapshot.cs @@ -0,0 +1,195 @@ +// +using System; +using CodeBeam.UltimateAuth.Users.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthUserDb +{ + [DbContext(typeof(UAuthUserDbContext))] + partial class UAuthUserDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserIdentifierProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("IsPrimary") + .HasColumnType("INTEGER"); + + b.Property("NormalizedValue") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("VerifiedAt") + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "NormalizedValue"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "Type", "NormalizedValue") + .IsUnique(); + + b.HasIndex("Tenant", "UserKey", "IsPrimary"); + + b.HasIndex("Tenant", "UserKey", "Type", "IsPrimary"); + + b.ToTable("UAuth_UserIdentifiers", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserLifecycleProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("SecurityVersion") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "UserKey") + .IsUnique(); + + b.ToTable("UAuth_UserLifecycles", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserProfileProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Bio") + .HasColumnType("TEXT"); + + b.Property("BirthDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Culture") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("FirstName") + .HasColumnType("TEXT"); + + b.Property("Gender") + .HasColumnType("TEXT"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LastName") + .HasColumnType("TEXT"); + + b.Property("Metadata") + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("TimeZone") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "UserKey"); + + b.ToTable("UAuth_UserProfiles", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Program.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Program.cs new file mode 100644 index 00000000..0cf248cd --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Program.cs @@ -0,0 +1,107 @@ +using CodeBeam.UltimateAuth.Client.Blazor; +using CodeBeam.UltimateAuth.Client.Blazor.Extensions; +using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Infrastructure; +using CodeBeam.UltimateAuth.EntityFrameworkCore; +using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components; +using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Infrastructure; +using CodeBeam.UltimateAuth.Sample.Seed.Extensions; +using CodeBeam.UltimateAuth.Server.Extensions; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.EntityFrameworkCore; +using MudBlazor.Services; +using MudExtensions.Services; +using Scalar.AspNetCore; + +var builder = WebApplication.CreateBuilder(args); + +#region Core + +builder.Services.AddRazorComponents() + .AddInteractiveServerComponents() + .AddCircuitOptions(options => + { + options.DetailedErrors = true; + }); + +builder.Services.AddOpenApi(); + +#endregion + +# region UI & MudBlazor & Extensions + +builder.Services.AddMudServices(o => { + o.SnackbarConfiguration.PreventDuplicates = false; +}); +builder.Services.AddMudExtensions(); +builder.Services.AddScoped(); + +#endregion + + +builder.Services.AddUltimateAuthServer(o => +{ + o.Diagnostics.EnableRefreshDetails = true; + //o.Session.MaxLifetime = TimeSpan.FromSeconds(32); + //o.Session.Lifetime = TimeSpan.FromSeconds(32); + //o.Session.TouchInterval = TimeSpan.FromSeconds(9); + //o.Session.IdleTimeout = TimeSpan.FromSeconds(15); + //o.Token.AccessTokenLifetime = TimeSpan.FromSeconds(30); + //o.Token.RefreshTokenLifetime = TimeSpan.FromSeconds(32); + o.Login.MaxFailedAttempts = 2; + o.Login.LockoutDuration = TimeSpan.FromSeconds(10); + o.Identifiers.AllowMultipleUsernames = true; +}) + .AddUltimateAuthEntityFrameworkCore(db => + { + db.UseSqlite("Data Source=uauth.db", x => x.MigrationsAssembly("CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore")); + }); + +builder.Services.AddUltimateAuthClientBlazor(o => +{ + //o.AutoRefresh.Interval = TimeSpan.FromSeconds(5); + o.Reauth.Behavior = ReauthBehavior.RaiseEvent; + //o.UAuthStateRefreshMode = UAuthStateRefreshMode.Validate; +}); + +builder.Services.AddScopedUltimateAuthSampleSeed(); + +builder.Services.Configure(options => +{ + options.ForwardedHeaders = + ForwardedHeaders.XForwardedFor | + ForwardedHeaders.XForwardedProto; +}); + +var app = builder.Build(); + +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error", createScopeForErrors: true); + app.UseHsts(); +} +else +{ + app.MapOpenApi(); + app.MapScalarApiReference(); + + using var scope = app.Services.CreateScope(); + var seedRunner = scope.ServiceProvider.GetRequiredService(); + await seedRunner.RunAsync(null); +} + +app.UseForwardedHeaders(); + +app.UseHttpsRedirection(); +app.UseStaticFiles(); + +app.UseUltimateAuthWithAspNetCore(enableCors: true); +app.UseAntiforgery(); + +app.MapUltimateAuthEndpoints(); +app.MapRazorComponents() + .AddInteractiveServerRenderMode() + .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient()); + +app.Run(); diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Properties/launchSettings.json b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Properties/launchSettings.json new file mode 100644 index 00000000..45dcb3a9 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5276", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7230;http://localhost:5276", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } + } diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/appsettings.Development.json b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/appsettings.json b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/uauth.db b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/uauth.db new file mode 100644 index 0000000000000000000000000000000000000000..fe98795ecb365cab2d1310cfd4f44df62a64f2d8 GIT binary patch literal 311296 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 z z^Mm6Qk30~700bZa0SG_<0uX=z1Rwwb2z&;C*vxX(aNe%p*dKe-KT7f#&h7Pk{J8)B z8QK9^ApijgKmY;|fB*y_009U<00Pf~fUEoe`2N3Vp(zm~1Rwwb2tWV=5P$##AOHaf zoM3?`{Q3X==l_39F@M4Q_yjAWY7l?`1Rwwb2tWV=5P$##AOL}W>!w)4~h$VPARff6V@anV-$P;(31Y z`qDq6{_i475?*zt>V>j?b~i=$ZZw*8>3(siR^_|$om#oIFKtTggGQ&*Xf->viqsUF zvbwlfw@q>-R~Gn+z!gh*VTTL2D=ENjl(>Lxc>!)E(BEp{92ZdHDb@qQ2QOasv#YE0 z!`rfWYhUW;tahZflAvRGo7Y0B@Yyv%-^?H}*}&ogM_&47xQeh-;Yu6i|LWS>Ic=qq z{+e_YuwSdiWxtXqc(x}Rk*@uG~`JJ2Y#=mY(k&U7TZk-UR(XFn0^3Z%9qKu_wx; zTj;$)i>J1|*E*DHxwceS(un(&qeHEQ%=s>B?NTijx2mH(#+=yAMvUFaoZH-s3~*|! zO3yNf8wmFJjGvVfbgw&g)%%#{0KV-oEX=8FD|zf%1$*bxWK@$nSVr~OjoL+j={V6( z8+WjGCFN)Dl5oH4hP*6wB)Kfz>q;G28C!O7zcp3d))L)TU?g9#mrnXwk<{e2o0_b( z_U=mcDj6&Lc9r;zsVZS8%x(O}EWwYz8uzn_1pRwg3^S{_!`KCx(SkJ1vF2>UiI&XZ z=9!YSodT`d4fCKrD{FfOb7}fYeVE&??T>u2pQ;E~g!0IIZ~g!_j|!Nc8> zX{#hbj_mTg2gV(jHASU$S884rJ9liVrG_yXXIt9qwj1)%b*Zf!V~kdd?9;9!eC)f4 z@%uD$Z>ed!H1pDnJu&8IKe$Xkc*#64H^olpgI2pfZ1ushj=opROZ$>xqOr-5au}#K zNv#w8pm5`uwaMqW9~%}B?7bcJvwN56-rZ^I*dN$7n@t|OX2+^euvduq+1sQF9oJPT zOYf7uIM@%^R3b5bB?g5~RgGaTvLgtSJus<=1HYafgT4 zHVMrgOF6_QCkqlQl2Zc9KFr~iV0f0$??skm1$+En;li{HJ(#r|duz;oTQ!?%>pp`* zr(zoB3T~bEvC`%7GnC!-wQ0&`RrI6fke|JLnSQio*c00%baaSl4yJvwO#xHym~G2= z%zI~PuPw>OJ&R?ZIvix9d$(b`jIPsnH`}s!aH#LKEM+VEYclN{PS_0lz3o=Fy(j58 z4J*jLLsWMV`Z`Og)x)}Zy&>;#`W}sV+>UXPj8YGCnPUEk{J;wW5P$##AOHafKmY;| zfB*y_00BFJn&-Ld^7{PT97U1;$PYS|jbvl-SUwakGpSrCDXe2If*>Asb#pP1^x5?UsBAknO}bHwZR4<009U<00Izz00bZa0SG_<0uXpS0hqhh_8igGQ6j zuNO;WN>}dxdzeos=2P+mF9<*Y0uX=z1Rwwb2tWV=5P$##o(6#xZ7hQr9Y&Y?=vruUwAcDf1^MB>@|CW3!6!X^7+lwow|J$htr>-r8=YKt) znEQCnIX=)G9*vm2{)FV|?+rTfL5T9xn0cWTNP*pyrQQm0mt znqpH{7hIE$ENj;Z10#04yc1h|zze=C7=WF^@$*-#+ZJ0tkn zbrRa;385)*7VDNmuept^pW8)XTEbxO)x4jro~3&?&2gEyO0D*RxZn6ts+YtAsUMq~ z;5%Ae)lzY*Itn+Y5MzjHGP9d`i-eEL`PoY(%+Q!HbvCuUt5bwltWOAsBnb9GSwFj* zqI)++L)ets2aQgr(P|F5m=c6BAVZQX4y~zVF(lJFYriCGgocD>IxTsym*M@aNaDTi zD&Apv!}c96>lCJod6?66+=eW{OKSVoKR;Kj>+ zc6F70cv}{4?Q0!&FbP;v=e3$u`0ScsH=Gw2IP%g5HCGXKDqLxU{9j#LJEyJG$GQDl zy#;Nv@}%F~Zc24t4%p_-ZIrevWxiOdaDmNh*2)LC&2n*_FYj{Kgk9UIhs;8Hmsnf~ zCcf=si)Y8nD#<%FO;vr9d$4oK&z?O?fAoPtR9!XfEuE~Lq?(BIPz_2MY9Z@DX(GOb*ZgP4gs=j zsR&nu@}U2a7Ad~vV{fJ=x5#jOnA*m$n168QH~efWML#TBTFH=T*j|Q(Z#%V|Aku|02seju3VLgnZ{YRZj0 zQ6|l!Pq^A(u5ItN4y79D6LlqxxL-Ls)aDX%zRQ}P!(Pyw*v&?abEY}Bd8*fE9a3X8 zR^^Dp94^^y@fklWCFovv>ZP~8^F9uP9EtDk;rL>`OpbGzkO9m6QO@$s~%l7HR}w(Xm$9JQMx1yiaj4+S5i+$4@* zYPY43PVd{lb(67iRYT3ga|%`*Y?=yO$BwxQ@!1M&BYK6p%zpauG*DB!(K4Qywox$E z=QlUMgu%(BkNhdkefS}uX`}a}zwSXu;jML{AQ1rK_97Sn1`z_JPn3WIfGSM|fZ{_% z0YDQ_EC8SIkUOF_2>}2x|8L;`mo&x=-|A<6^eNKIEn+@0x_oF!01*2fEEOEyOrly*H>k97sM|~xyO(Z6&@gXQZV~BY!-2}J&-XO*XB4?F zNmIv8Lug~fXnItF6A(q3G~b(Kilwm;G)^^|mS~lsTL4Aa_RD?ip^V zId~>~+I3YD0Jj~7wmW*r3aFa|Qr$)#A&ZW1aBT3e|87IKGiN_1wD4*~Gj1VR;GDql zRuvU4Fpws>$EG2nBzj5iWG)e9}kpIl2HW@q)5M8!DzVos-dlyPd*VvX2sHJDT_O!$q zfE@ai|LzYX`4zr22i}+mN(#BL z*WRlF(5b6pSI(nHdV8)FuOfBXWe-mY43-AvuF zn!fskmV+}K7|?PmbIZG{V%d;dU3u2rm(t#I$6Zw;z56P3I12Kj552!bD|7=S3OuQ;Px`so}aw=O?&a&_8$Q7*-Q|;&a#%~!~g81&&CxJfm4=cR^xV!1P%KT+64=YxgcN$ z%Kgb3qPpyTsH!+_8;Nj11AynB6UZpb!Wy2nC7}s57*IL>$LwT$96|6^Fa}VXxga70 zs_8CM`v^GbJ1qdf-;?b^PvMeTJV72TGQl9nwP^0_k3OeeAt$J}c^wT>S=p)$O}qI2 zH0=hh$A~Ow)^fD@|6T0-{k0xnO48m-68qi<@qIg$c=_CHMgQBCh$7F9mU?n|{JhhN zwaqmZ8zy!R8S4uboN-nNW^kl3WWCj3a>4vV(NO>O5eNg@wLSZMh6@286BLz z;v?g5nR%zk_jj5AK%1O(Gh*oJoS$1s7L&bLWp?+UCIWL-oSA=pLswfEMJq}IwqYe- zzV|y>j`uUPGYprkw>vS~+lU8*ps^-BtT>5a%9De_J}gz;`?h5CH|=l4!Fp@|)fbkN zjp`CV6(Vh^jh`7Bg-)pxa2t2o zCaQ%#q-N?C^=HHg*Eeq}_aAaF#%;(_R}Ub@Mx)X`&CG%6K#FsdFS_yDufpdv|Ej`C zBqR|f7qcDGzt0#RdOcY!wAB>-dLom!^4`+CBhV4!AxEVsjyll7Lzc*^5|0IPIV(!K z4^p>|N{Aae8ot{TCQds>Lh`J_I9r$}4bvDy-2(4JT5hegRmBQr7VF_`ED_AnFHY~m zHX6)o2WV(W&_sWeO8#scw%ruIDY+=996pJyznvvk-#n$G$jKV5Zon_kj|+)KM@5c4 z@TK>EyX?P(#z8{lyKW#|Yg;NbOePm^eZTDgdvgBD_j2sDpe%b_l46CVro`I?eYAa4 z!bztju>5iVYF!4~wKL|3eA|t)k)~+7Lha2A0_`+JlrZemBA^q)O9mD)RLamuh9n-h zaWlLfH6dQ5qU5A3#z=F=-UaN&&BV8irRJ>VOacvhH1d1HG6zZ}E;sT;WPZ^r(}@8V zQdEl62-iiUjW2I|cqg8$ix-Y(ZM^H|5i~(CW~v1K)%gofw!g|yGA{w=ugtXY(#9@3 zFM+QKA{dHgOZ4k7YjZoR2lE2*9sgNq2zNBXzGbZ@6OI* zJS6X~57+j8_;oNyY$6H8>MrG|kkDh|$ytdtSbsAFlTr8H*zrF1OS&3v;x>Fg$bPk{6$LOEfdXy^;edRe8@dlEpNsmtu z%EJcT35v&b?VwVA{LNu|UhA#avWI{xs;TpGHlWKifb;K~J=o4anT&5rKdYR(Vpwup z`2%ymU1{ix@66ti4F1b&4U?6d1s-!&Oq{t>ygKuG|LSPB#N(y;b>$l>;ayYX@)9P! z@DLX_w|?vIcB-b_zI=l&b}Hlr=u%CwdhD__6k5E8NqxHp4olu&ubvUeT3;Dr&WI9K zGJ@a!{(It(xYGK!tji4#%j?(Iiv_;*X9a7$$hW#y@(XTS=xVpn)@w}BjkiMK8IB>p zwb)N`QT<(Xg;h*fKa1U9v%3|Zp?rezn_=G{V!%2;^0JYyzFoMI#5mpG+*Z&>FQGhh zk*51C7hfn#3tJuz-v!@`3Wcx!eKxsxp`q`*@Ri&drXFTF9>v-%bV@rZ24*ka>oC&$ z75eTkBN>}NW<}@u9h9Z}`i;F=sqg6#_g%l;kMpmu>!|2FwPb$8O$b3*=8ULl(;Hm) znq@|MpRV_88vUZSu`wQI-LBlyJ2KtSabl{e#Ziw$jEDPs?Xc6`zin4@pNcm%>6y?i zwa(C66T=-E$0zpkCQoH^@*$Wm0^G}_5^4Y6D;+;Lq}z9dU)_j3ZuERAZB2Q;ZS-uZ z$L$BZmSRPA#H^k@sgNmX*RQeeX$>E`W zTk7W@XJ69U*L*xwW$_nvQAQuw8f`tQu01B!VYe#?EFS%N-WKrcKS`((;B0`xqXSbpHJnFO{x zrQ1q?q<`1be~FNUFx#j3e6=|Y-XiQL*+DxreuNu;49UN<%AYOXoQ}Ve9GpJjmfUis7>tlxE$A_Eh<5hM-$@Scf zdiN$HxlCmFt)V&5hlC)rS>cS+N255yR__?S6|tdRcJf#i9c?b}m6X(lYu~NlWvtPw z6+cvsJvh+3G2jVL!rSh(WX`sVvXH!M0rbaNMyb%!XR5KmJqe~;dL?AXith}1Jj%L& z7z@c!aKiK#^Ic4MrI?uqB+%@#Zm^|j5^yb5WX2}@t{tVKXPJRvt$xPIby*LXrI(9J z&NeTm%&*h88iOpju{SOsP@aExx$Lrr0duB9ua{8o^l+(QrQ*GR(Q1&m*&QQu=J0a1 zQB1%+JyW`0QsT^&;K858``8Bjq9*;fyXkOzwr1@A&AO6FUp5!RyQf-_Ir6Nc;_k70 zN6*>t{y7H|C)-PP&3 zi=DHo?pqtM&}BB&dS*R<{?fTwfiGDPu3W0PDSqknq0p%ohIszY$|f!XC4EFCpJidc ztGv(>aboz-W-VO^zYk5IODKcW=}`F6Ob5>TOxSO$58=!-xC7H8pDtWZu&Vgo+7&R; z+zPeSrYr}%9(NZd4p$h4@7b8=be!KA>3uIK+c#-ra=0LWmTdi@*)aWKMd<2*)wv^Q zb>nHj4b7d_Vw&-ljZohm8H;HZJI0)vEt4w9%%2e*9f88~#~GrdYe_*%7D+*Ii@&cD zDAWHSe;bDH-+6*@l)2Wt@#$VCsW*i=UvkG)uML ztmme`THm;uJ0v|3Ba$UenP43-#?kfUj<6%&v$7JuYK(9`rOQb}|C~2N8@-UJXG2kT?BLPyT;#l7?DzsiSbp=u**21U4TF zrl99_(?j5Dmr1^gc&Ja%_wX<7^FrJu+!Sww;5>I%CETt@YW|r%mE{&dcA*pWcuI5I zmFgxb^d9S~2naabBh??yR22&z8pP(GzU1-Yk73BK`1jBH9M-KOvL3(#e+*h$JVKCs zJ|6z-w<2@7jOSJbphQk63kZ_ZV}m?nf}$8{e9((`0 znlQ8u{l(`~p?(MMy?dsu{4142%VOQd@$>iIN++eA)n|-0ny{bzQnP6N=x}&-qH^t| z(a}vN(251Uv>2gF(hiakce5`}K8yI$aJ>rKh|@#nPGF)Xv>o*Qu}l(51baY-B{wT5lWZl3L|)S~ZHlJiiTxSEjNo zFGW&0%qRC9T5mq~sw+SJ6|@hh5=#_Te22|BDQ|-JBJPd!x8Y}BM!eT$S_PRS5h+l( zX_t4_1)m)7hTi1W0E`_}tYaBr)3@0Ep}E=V1vorhBTo_^Nw)j;Jg$eRfdwg|?1 zFY0TD7fo7$R&W?P5edDrl`-MkVaoI()4=`IbM4AmmX8cQywmR3!kUXGXi6kxf_fH9 zxqRTy&{{%0mgSMiE#|kJ%?b&TGX2wbHK;e7rVfEj!=l#vvut1eMU#3ZX`XQ8t5N}@ z<~8a6`Q7ehw8EnWBEQ@jf(Z%e%RsmPUba*D(#v#UJaU#T9Quy|?<1gfrTO)T&k1qJ z5m9>o%KCLlVh5zEbUk-sEuqeMezdoL=|VS#qaOO?QX?wn^im8hYtdQIC>Y4#Nkp|p zKel_}YfnV52FtyVo3uA^3PLZgy3XnL;ysyW9^;Djj`8M43ulac!5Ii{4UMy9c;G;Q z8qQ#0sl)I9D~6iIU4v$9Xv8T266N$>ja<6klV$yr_yh(@57&8o4XxPP70ho6$+F2n zHeObt?fY>UO(>dH@2Z<;V`%(RNM1CGy>{T}OmnA*L0c=5*d3G454Zni7L*wMG z@@Gw|t43jeZCCpl9n4&cl0pY_iOC3`WQNhp9XAf{wP*7>fMC36V@^#=LPO!J^gEMJ zOxAcx(iTUqx`%Av4i>g$@wNETkJK@WXXpb_&DGQ zlThXVK=;BY&(%#PQV69HC4qJ8Sr*#PodjIvE{_{5WXE7iZH{cudsPy=fk`i)<2pN) znSZ_w37O+Cg<>Rx65wE;Ay}>rG0biv0UJv=?C}3N!9RmN<9FGrj``sX7!#RI z1ummg#l_(u4~Ck^^FJ9hC#LVqCC^}%wog{Q&v4=d_QTNIA}4&tuv`%Y${NjpfA``r ztbH=WVJZWlyb+lEWx3;+I7v{FjAAEr;6M-*lYwj`7?^|mlxV<=Du{<~dH&0|8UJ09 zg2~$d64CJgp=4AZF4_W(&Hr8Fl(Rt&$_@M2hr!%=#tbV{o{8snmKo^h1)yh#E8?I4 z<4Hi=R^E7v}anDsK)0HqtrrjVK} zIRPadJ~eSLx-q~xL|51;{vG!%lR6e2DV~ih+FmTe^>w^+DV7*OQ7*%We3za_WNL?D z?ZBh>=Qczs6wwifr8Lt)EgUJf#ea8`1m!ynssVsGKJJp3{AebuOrREqTZI^Ug(x@U z<~F$J)I{12>W_rlU8$_Ol5)LX1pg>S4j>tsJn?PF8zpr-afKB8vfXEbI2efs$>jQH z|Dzye6N!(otOHAZV0Gr?YYghRiIkOYRdXTuFsA`Trn>oX5G)Qs_E!ywdcz2h9Q1#Qk$=} z5agjd=xbN+exx_UfdCh*teXiyvfsP!Jy#$Y^_x4U;Gyr)wdue<~_9geR7u{Q6jZQW&YL78; z{%byDK=Y+Y5f{mwtuepl)_)3fuSJUXliW9#9Y+AgqLZ&{2cam$&<22{uih{jgA|!j7z7fM+GPP;k04`4=+LuII)oxW4mr3(vTU2&h{!Bi{dMmE)cUSM{fz5ZR_(OSYfo~oNfUSy#Df8)n1lBgO=0kQ;)ULreST0K++;`HFO6ExuSi056%m!elO2>StOPXp23;F%Oa-f#{ zfFKQ2@_QDd0dM|G7d_rn$?<6dZ`(M*fZOALq_-&(+Fy;CFnsfjwgbxV=fQW&L`Zoe z^j=Z}o(UD*8{#mhk~vv8By;^Q!))}4IPV^zg+%3#upSIc#0ig*!nR)71>V6tX@^tC z#f6P&X6fqB9XC{%F1<>jGq+S;Y>dx2d}bwi-mP9T8o_q^!iY`cwSO<3Bv$V?1j|<+ zliIXCDPxK8zRM#6A@fm(+Umc1dNRGf-*CouVjjHWk)*!6XCluG|L)K#gmwP0=naD~ zBdhU#dIkk!;ad_iXehRcB-YT(qxqgRALEEngy}GOuel;!id{Pk2IKD`aInceCZ>vc1F!6PiuK-ZS@1kS(r|Kw(~U9);X1u=l`O z35Sxe9)0ktL5f;`JF(7Tu?(%OS;Ut}Z2axOB6-!f^ffzs6=PSBEe}K7n{apv-#V*4 zmNm1MieNo%W$}c5?p2IO6%Q=^>S<{0zzldVDz`WpT#>!9@H-p>bF`a2a#AlGFLPEmIFxgwDXdR05SMNW@!>*rgFMF6r)It)fci+ zCD`PRg=9xT%#!9UtjNxMkkSPa`jGOR=^G@4++p}CZW?-5HZ^|6@bZX;R;~81g-}ib z8=8n<6_X{-YfqHY7P+!@N2Gm~T7@ui(uuWOjfuJZT8Y|ZZGuh2m=Jq24~8%@>C-P{ zOw35oDj;QJNqL(Zt8P_KOoL_nutO~?e0#x^e|E^j2n!_pn#LV-D>ElfS_Okqb(mFn z6yxxCJ+y3fg-SjHmCr@Ro_%!!#t;vWJ8s;##M55mL$!beg#1)&QKIj=KZp=>%QT7) zlk!N8Z@8BmjmlPC>`JFVQ^~n$_g-wz1UB=mIP18h-wysfEAfPZ+$UQ$ zleVbqVT`Te{wT=!GHqskCfW zwG7q*pFm9j z@sQX`zNEJ-_3s1OnCmxjW1sY>6#mg5b}>J;l2mkPE4-kd2k~N-B&n2y`%C$fb9<4C zv2NXeOOi1_@$F0z&orLYbM*C<88L|L#oZfU#$bRq?eXST^9cf=?GE|-%>`do-g7pf zlbDdL@8+8ojSU={oS!|!$_SQdUS9eNt;~x=?vhQ;d%eq4zrT+xkt0+Yj5-~L9V3z3 zuz^z_RpuPm9%~5oR}A%$DuP`U(}&zeMLgal3xw99fUK=@;LJBFn$Bv$3LaDh&fBsv3AUPmhCVJ+O`b(bUMH5~8*-A_fNr1_?xh1M?)*8| ziM;Rd_p6?=F}62m$jkGP`_p|1Zz2J);cj{mdx(o-w=A{umYbd(F9w{)8-JyuuC-Mt zXt}Ll`lk?a-AYAY`#z*ca{y=YW9b<*%VYOWpro&+OATKkFJO#_Q}ZLmrQ%IM<$X~> zm%=L*;|x&z}Lzl|J{zSZnDP4(zfY#dYxheULCGWbEK5gwVet}H{TX;ezxut zuCExFc~wVzlF8^}EdPCQ$;~%_Wz=9s{?;YF*M!IvB|=qC9D&9m5O#5;%josLqoeb+ z<|4kTOlN!TNlk%0%G$D=z^m;E%Qm$xOXdK3e4d6_>wV2YbG9`8`IGaV z1NNZ?rpZ??;4uJ4Ii(`M&>1;Ionx}J0kOL zqdAJKM+kct&Cz8&LfAAk#{xd;%0%WtymqWdLgWNH!Sp-a^CAmes|YD>3W~!$8p%0M zMav~)fOEbnS}skU;DhA*L0}nzX+azTq$uD`FK`h;&3Q4E2jV0{4VgnL4?*BZagroFi3%|fWSXL^Ou=Te1labIbjn>Jx2X9> zY+(7y$7FwK9?KnnuNeM)NO?xtwb_`ugNm5(8VC8}SdAn>pk68xX=v5{R63c`qZ1@S zsWe5ixd^0sHI0nx0*{ic=s1AhM5OcyrtVPZ3%6k zazK`ZK%r5@-{XxtdQO*EAs-JyVHoU)98jjBJ$rdEEz(lR4+3>S87i^&d61WaL_&AF zFaavt&fFsRE6~kGBKZ(PN$lk5b-3ZQ-Q%Vx%uIdcfOxj(XhMRnNp_~7kq6<5HUldce2ZPm@<-1m1FGR$&xOH!TV;hlIm3^@v6033lZB5*`T z4M(iqYKD4cp^@jIjQ!nT>g4lz-9aP8AZ~vm?jtWk=**Ul`J-`RfrApV$yiky)*wB0 zxo|-1GnaH&{9$2p=;3R%61{iCFO685oos|98`$>{4%2H9ilcWpV;@5w_N@SH9{k7) z6=4jz5F|e=CIxRk>>L9rC>~2C)Q*4@c0A5cit1bg5BrfHTPb3wv8s7O%|}tbm=;E0ix;T#p8D2NiC7GFJb#{_;wCxSZT^}QnvoYN8Z)cGdZ;9lr<*q zAkNr)1~E2=95RyH4qKeNcL|_v{~8+Y1z=XZg`kE*f4KUQz(m}Gi!Iq>W+Bj(+p>a@ zgsZc|_b;Kmv`!ttOcWf&<3@cn__@_s&5pIO>sStyzwWwT`(fkj z5Q8&cN#odI3m(0*N_~!x_}df}tWQ7%^Fkh1DXehNJVIi_6OqW>Tm*TW%_b`EjlfYm z@4(3X(ko9&vFPoDIL2l)5{X<4*^986VHf9GrsuX07pye(R*8dfIrg+5EB&AP z{yfA~Q`E*3F_wWl@YAjA%{Huj^F=%^;GzbQ5~c$A7RQ@3GL-k(vih-Rqn#DwMucZarb;8J+-IOwU+ z%pqA{^gq@au%|pZB89BN8ubM0NC(AQg?6Bo2^otyHWP9LoWUrhNG}k^4HB=fw;UoN zp-?fr(uHzK%}@lP&xPmTJr1c8qBP=-QHmG=sf<64*;5^Vn1Je$P@?)IZ1?nU+TbXG z3EYpJ_(Xdt!b& z)+4|ehgpI9Q1W=p!KA=mh9=dA$9e5TFe1AhMe^$YDW?dB7i=#}mx`fq2b-gX=Q8ow zJ=G9``=f>UeLg7`8ttohv|f^Zmh0+o`g>L}v=u@#D~ zjl}kPz+F^zbK~cFo*GX!zgO~+loL?3za}koRbCfP^^CaPiN{{MEs>dNRkL=ZBgWAd zhC2W1NnV!^uwWbVo|QU_82(=LD;hPUDcZZT5^z=0ITEpEv!I6Pg`@*v~>Mf@l{`P zQnH(TBrFO+eaUNc)~>I;ka=;1c0^0|vxA=qrHh5MUH^Ld#GLWjCg|Y1!EJvE?QE1n zZkb+DLk9K7wnRP~)+(N|^}DVT+@rtv5LL9dbJ`Sy_-$m4g|IstLdnXRp)EqN_!pfn zTc6=)?lN{PeLSMc&W@%cdV?*r)>J1&;m#n%5ej}e;i|8yy6k66Ic!&)*tL1o+5A8Z z8@%E%Q-$@!Of!s`Ebgom(n<@ zYmR8o$J5%9JD|k&v207So@X5PV>!&gB$_(mB%w8=em{`ZptgMT5C)UV%SnYbrWV$9 z1^4a4gs1Vg5cyB=gqMxnWxEB<1Ty>Rs_)~R0(Qc4jb^s1l9F&Mz>SYS{=0LRO{R?x zK@2{dSwmyl7dMYiJb(si;8-9Pcz@%yw%u)=koivuOH;vH$f{2|JOfWcyxo6xkxR|+ zIG-R0`NJhgbc_A@gij8>n=x}%u*Ae_{11Aee<~xd%2!={?V1Qjp`rOO*rN*X_Qy#H zc5;e7*+1cB2Sf^`afSOKmz=HpO$6oJpU8G$I&lPCA-in7b)LT9AfFbe^AZ$zXXM!6 zk^vlygM+ZGy%1J7oa58(9Ufcg8hYb)%HPdOa=8D)PnVYB%g_<9w^ro48`IT_|XLGWyp9mz&6 z*veDrutu}XJw|?Hp=<5A*0k;UOisI3M6e`B1$L+7u+w!Wc>Z)=&KB{{n}A)r{>sIk zG`~AM(4-bMC!j=5NE3g~ofvuQS$Qnc9F}>^(+J#_uJX?xu%ek|Gca)t7K3gQxCt-d zfzG(`0R;|=*HL0D%UxN&yw6Z-)R0=(rY``Cak_5i2n~%IoUpZ+obO5Ov-6gkuv?A> zFr|%^-=}Um7_jm%h}R6r!*+Q2>jdudQ*g})eQ6vyW$eoCwe$xz?Meb>!H02}Bv=X) zbcbo|b20Mw{lcG~hJb@`W956`3D)B_^3Qte0*Bb4oCI0Pf&B`*-W@usEfWc*oFNUH zV&nzqS)X%ky@CfGF8|jE#BD2R39PK|7Y11fbE#yHDVLp(co-LGK8&OeJvpoq=zf z;rvWBQJ!XL=5xDi-AXp-_u@ed=f^VStr2y6&h|S&=zJZ46im|f2`#n$(>u_yf=cXrY;wC zg)0W<{!`LDKkXj($piLpeaT>Re$Q(OoV#_m<@gNCJb{H%F}Tx}2jDj=(h9j*jTbdp zDBU1Rb=JpDkCOUQKKq$Kid2ulN^GhJJVGmUeGS;7$QtuwjitB#TRKGqF5Lt1l-*hS zx`#17o{g|Pd6^(iN{Ff^Kc)0s=CsbcY9<}#(S6{$rL1*(&b$2MstER4w_H2O`%iv9 zU@E;n@ - -
diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/CodeBeam.UltimateAuth.Sample.BlazorServer.csproj b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/CodeBeam.UltimateAuth.Sample.BlazorServer.csproj index c27fc66a..9d71e7d7 100644 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/CodeBeam.UltimateAuth.Sample.BlazorServer.csproj +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/CodeBeam.UltimateAuth.Sample.BlazorServer.csproj @@ -17,7 +17,7 @@ - + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Program.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Program.cs index 5995bf9f..485e813b 100644 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Program.cs +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Program.cs @@ -1,15 +1,17 @@ +using CodeBeam.UltimateAuth.Client.Blazor; using CodeBeam.UltimateAuth.Client.Blazor.Extensions; +using CodeBeam.UltimateAuth.Core.Abstractions; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.Infrastructure; using CodeBeam.UltimateAuth.InMemory; using CodeBeam.UltimateAuth.Sample.BlazorServer.Components; using CodeBeam.UltimateAuth.Sample.BlazorServer.Infrastructure; +using CodeBeam.UltimateAuth.Sample.Seed.Extensions; using CodeBeam.UltimateAuth.Server.Extensions; using Microsoft.AspNetCore.HttpOverrides; using MudBlazor.Services; using MudExtensions.Services; using Scalar.AspNetCore; -using CodeBeam.UltimateAuth.Client.Blazor; var builder = WebApplication.CreateBuilder(args); @@ -59,6 +61,7 @@ //o.UAuthStateRefreshMode = UAuthStateRefreshMode.Validate; }); +builder.Services.AddUltimateAuthSampleSeed(); builder.Services.Configure(options => { @@ -67,7 +70,6 @@ ForwardedHeaders.XForwardedProto; }); - var app = builder.Build(); if (!app.Environment.IsDevelopment()) @@ -90,7 +92,7 @@ app.UseHttpsRedirection(); app.UseStaticFiles(); -app.UseUltimateAuthWithAspNetCore(); +app.UseUltimateAuthWithAspNetCore(enableCors: true); app.UseAntiforgery(); app.MapUltimateAuthEndpoints(); diff --git a/src/users/CodeBeam.UltimateAuth.Users.InMemory/Infrastructure/InMemoryUserSeedContributor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Seed/UserSeedContributor.cs similarity index 95% rename from src/users/CodeBeam.UltimateAuth.Users.InMemory/Infrastructure/InMemoryUserSeedContributor.cs rename to samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Seed/UserSeedContributor.cs index 659f30d2..72a60172 100644 --- a/src/users/CodeBeam.UltimateAuth.Users.InMemory/Infrastructure/InMemoryUserSeedContributor.cs +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Seed/UserSeedContributor.cs @@ -6,9 +6,9 @@ using CodeBeam.UltimateAuth.Users.Contracts; using CodeBeam.UltimateAuth.Users.Reference; -namespace CodeBeam.UltimateAuth.Users.InMemory; +namespace CodeBeam.UltimateAuth.Sample.Seed; -internal sealed class InMemoryUserSeedContributor : ISeedContributor +public sealed class UserSeedContributor : ISeedContributor { public int Order => 0; @@ -19,7 +19,7 @@ internal sealed class InMemoryUserSeedContributor : ISeedContributor private readonly IIdentifierNormalizer _identifierNormalizer; private readonly IClock _clock; - public InMemoryUserSeedContributor( + public UserSeedContributor( IUserLifecycleStoreFactory lifecycleFactory, IUserProfileStoreFactory profileFactory, IUserIdentifierStoreFactory identifierFactory, diff --git a/src/CodeBeam.UltimateAuth.Server/Extensions/ServiceCollectionExtensions.cs b/src/CodeBeam.UltimateAuth.Server/Extensions/ServiceCollectionExtensions.cs index 6c6977dd..93ebb780 100644 --- a/src/CodeBeam.UltimateAuth.Server/Extensions/ServiceCollectionExtensions.cs +++ b/src/CodeBeam.UltimateAuth.Server/Extensions/ServiceCollectionExtensions.cs @@ -256,8 +256,6 @@ private static IServiceCollection AddUltimateAuthServerInternal(this IServiceCol services.TryAddScoped(); services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddScoped(); // Endpoints @@ -299,6 +297,29 @@ private static IServiceCollection AddUltimateAuthServerInternal(this IServiceCol opt.CustomResolversFirst = true; }); + services.AddCors(options => + { + options.AddPolicy("UAuthHub", policy => + { + var sp = services.BuildServiceProvider(); + var serverOptions = sp.GetRequiredService>().Value; + + var origins = serverOptions.Hub.AllowedClientOrigins + .Select(OriginHelper.Normalize) + .ToArray(); + + if (origins.Length > 0) + { + policy + .WithOrigins(origins) + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials() + .WithExposedHeaders("X-UAuth-Refresh"); + } + }); + }); + return services; } @@ -382,28 +403,28 @@ public static IServiceCollection AddUAuthHub(this IServiceCollection services, A services.TryAddScoped(); services.TryAddScoped(); - services.AddCors(options => - { - options.AddPolicy("UAuthHub", policy => - { - var sp = services.BuildServiceProvider(); - var serverOptions = sp.GetRequiredService>().Value; - - var origins = serverOptions.Hub.AllowedClientOrigins - .Select(OriginHelper.Normalize) - .ToArray(); - - if (origins.Length > 0) - { - policy - .WithOrigins(origins) - .AllowAnyHeader() - .AllowAnyMethod() - .AllowCredentials() - .WithExposedHeaders("X-UAuth-Refresh"); - } - }); - }); + //services.AddCors(options => + //{ + // options.AddPolicy("UAuthHub", policy => + // { + // var sp = services.BuildServiceProvider(); + // var serverOptions = sp.GetRequiredService>().Value; + + // var origins = serverOptions.Hub.AllowedClientOrigins + // .Select(OriginHelper.Normalize) + // .ToArray(); + + // if (origins.Length > 0) + // { + // policy + // .WithOrigins(origins) + // .AllowAnyHeader() + // .AllowAnyMethod() + // .AllowCredentials() + // .WithExposedHeaders("X-UAuth-Refresh"); + // } + // }); + //}); return services; } diff --git a/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Data/UAuthAuthenticationDbContext.cs b/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Data/UAuthAuthenticationDbContext.cs index c2ff82b3..d08975b7 100644 --- a/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Data/UAuthAuthenticationDbContext.cs +++ b/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Data/UAuthAuthenticationDbContext.cs @@ -4,7 +4,7 @@ namespace CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore; -internal sealed class UAuthAuthenticationDbContext : DbContext +public sealed class UAuthAuthenticationDbContext : DbContext { public DbSet AuthenticationSecurityStates => Set(); diff --git a/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Projections/AuthenticationSecutiryStateProjection.cs b/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Projections/AuthenticationSecutiryStateProjection.cs index fc9740ad..266bf15a 100644 --- a/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Projections/AuthenticationSecutiryStateProjection.cs +++ b/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Projections/AuthenticationSecutiryStateProjection.cs @@ -3,7 +3,7 @@ namespace CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore; -internal sealed class AuthenticationSecurityStateProjection +public sealed class AuthenticationSecurityStateProjection { public Guid Id { get; set; } diff --git a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Data/UAuthAuthorizationDbContext.cs b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Data/UAuthAuthorizationDbContext.cs index 7783f457..dc322533 100644 --- a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Data/UAuthAuthorizationDbContext.cs +++ b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Data/UAuthAuthorizationDbContext.cs @@ -5,7 +5,7 @@ namespace CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore; -internal sealed class UAuthAuthorizationDbContext : DbContext +public sealed class UAuthAuthorizationDbContext : DbContext { public DbSet Roles => Set(); public DbSet RolePermissions => Set(); diff --git a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Projections/RolePermissionProjection.cs b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Projections/RolePermissionProjection.cs index 571425a7..bc12b9a0 100644 --- a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Projections/RolePermissionProjection.cs +++ b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Projections/RolePermissionProjection.cs @@ -3,7 +3,7 @@ namespace CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore; -internal sealed class RolePermissionProjection +public sealed class RolePermissionProjection { public TenantKey Tenant { get; set; } diff --git a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Projections/RoleProjection.cs b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Projections/RoleProjection.cs index 1c4e155a..71976cbb 100644 --- a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Projections/RoleProjection.cs +++ b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Projections/RoleProjection.cs @@ -3,7 +3,7 @@ namespace CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore; -internal sealed class RoleProjection +public sealed class RoleProjection { public RoleId Id { get; set; } diff --git a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Projections/UserRoleProjection.cs b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Projections/UserRoleProjection.cs index 42f1f186..22f956e2 100644 --- a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Projections/UserRoleProjection.cs +++ b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Projections/UserRoleProjection.cs @@ -4,7 +4,7 @@ namespace CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore; -internal sealed class UserRoleProjection +public sealed class UserRoleProjection { public TenantKey Tenant { get; set; } diff --git a/src/authorization/CodeBeam.UltimateAuth.Authorization.InMemory/Extensions/ServiceCollectionExtensions.cs b/src/authorization/CodeBeam.UltimateAuth.Authorization.InMemory/Extensions/ServiceCollectionExtensions.cs index 3ce68afc..919b6a61 100644 --- a/src/authorization/CodeBeam.UltimateAuth.Authorization.InMemory/Extensions/ServiceCollectionExtensions.cs +++ b/src/authorization/CodeBeam.UltimateAuth.Authorization.InMemory/Extensions/ServiceCollectionExtensions.cs @@ -11,9 +11,6 @@ public static IServiceCollection AddUltimateAuthAuthorizationInMemory(this IServ services.TryAddSingleton(); services.TryAddSingleton(); - // Never try add - seeding is enumerated and all contributors are added. - services.AddSingleton(); - return services; } } diff --git a/src/authorization/CodeBeam.UltimateAuth.Authorization.InMemory/IAuthorizationSeeder.cs b/src/authorization/CodeBeam.UltimateAuth.Authorization.InMemory/IAuthorizationSeeder.cs deleted file mode 100644 index 750a4d66..00000000 --- a/src/authorization/CodeBeam.UltimateAuth.Authorization.InMemory/IAuthorizationSeeder.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace CodeBeam.UltimateAuth.Authorization.InMemory; - -public interface IAuthorizationSeeder -{ - Task SeedAsync(CancellationToken ct = default); -} diff --git a/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Data/UAuthCredentialDbContext.cs b/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Data/UAuthCredentialDbContext.cs index 6e40935a..c97910f9 100644 --- a/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Data/UAuthCredentialDbContext.cs +++ b/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Data/UAuthCredentialDbContext.cs @@ -4,7 +4,7 @@ namespace CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore; -internal sealed class UAuthCredentialDbContext : DbContext +public sealed class UAuthCredentialDbContext : DbContext { public DbSet PasswordCredentials => Set(); diff --git a/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Projections/PasswordCredentialProjection.cs b/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Projections/PasswordCredentialProjection.cs index d25e8edc..ae4a8dee 100644 --- a/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Projections/PasswordCredentialProjection.cs +++ b/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Projections/PasswordCredentialProjection.cs @@ -3,7 +3,7 @@ namespace CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore; -internal sealed class PasswordCredentialProjection +public sealed class PasswordCredentialProjection { public Guid Id { get; set; } diff --git a/src/credentials/CodeBeam.UltimateAuth.Credentials.InMemory/ServiceCollectionExtensions.cs b/src/credentials/CodeBeam.UltimateAuth.Credentials.InMemory/ServiceCollectionExtensions.cs index ef75640c..9b53af9c 100644 --- a/src/credentials/CodeBeam.UltimateAuth.Credentials.InMemory/ServiceCollectionExtensions.cs +++ b/src/credentials/CodeBeam.UltimateAuth.Credentials.InMemory/ServiceCollectionExtensions.cs @@ -11,9 +11,6 @@ public static IServiceCollection AddUltimateAuthCredentialsInMemory(this IServic { services.TryAddSingleton(); - // Never try add seed - services.AddSingleton(); - return services; } } diff --git a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Data/UAuthSessionDbContext.cs b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Data/UAuthSessionDbContext.cs index 886762d5..4cc649d2 100644 --- a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Data/UAuthSessionDbContext.cs +++ b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Data/UAuthSessionDbContext.cs @@ -5,14 +5,14 @@ namespace CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; -internal sealed class UAuthSessionDbContext : DbContext +public sealed class UAuthSessionDbContext : DbContext { public DbSet Roots => Set(); public DbSet Chains => Set(); public DbSet Sessions => Set(); - public UAuthSessionDbContext(DbContextOptions options) : base(options) + public UAuthSessionDbContext(DbContextOptions options) : base(options) { } diff --git a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Projections/SessionChainProjection.cs b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Projections/SessionChainProjection.cs index ffc23f10..c9f417a9 100644 --- a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Projections/SessionChainProjection.cs +++ b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Projections/SessionChainProjection.cs @@ -3,7 +3,7 @@ namespace CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; -internal sealed class SessionChainProjection +public sealed class SessionChainProjection { public long Id { get; set; } diff --git a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Projections/SessionProjection.cs b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Projections/SessionProjection.cs index 19061c50..3f815941 100644 --- a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Projections/SessionProjection.cs +++ b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Projections/SessionProjection.cs @@ -3,7 +3,7 @@ namespace CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; -internal sealed class SessionProjection +public sealed class SessionProjection { public long Id { get; set; } // EF internal PK diff --git a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Projections/SessionRootProjection.cs b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Projections/SessionRootProjection.cs index 4d9c0ff8..c6ae69d9 100644 --- a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Projections/SessionRootProjection.cs +++ b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Projections/SessionRootProjection.cs @@ -3,7 +3,7 @@ namespace CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; -internal sealed class SessionRootProjection +public sealed class SessionRootProjection { public long Id { get; set; } public SessionRootId RootId { get; set; } diff --git a/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Data/UAuthTokenDbContext.cs b/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Data/UAuthTokenDbContext.cs index 304b7a74..09dd3eec 100644 --- a/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Data/UAuthTokenDbContext.cs +++ b/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Data/UAuthTokenDbContext.cs @@ -5,7 +5,7 @@ namespace CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore; -internal sealed class UAuthTokenDbContext : DbContext +public sealed class UAuthTokenDbContext : DbContext { public DbSet RefreshTokens => Set(); //public DbSet RevokedTokenIds => Set(); // TODO: Add when JWT added. diff --git a/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Projections/RefreshTokenProjection.cs b/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Projections/RefreshTokenProjection.cs index 3f47970e..09e26055 100644 --- a/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Projections/RefreshTokenProjection.cs +++ b/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Projections/RefreshTokenProjection.cs @@ -3,7 +3,7 @@ namespace CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore; -internal sealed class RefreshTokenProjection +public sealed class RefreshTokenProjection { public long Id { get; set; } // EF PK diff --git a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Data/UAuthUserDbContext.cs b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Data/UAuthUserDbContext.cs index f7f0d05c..40603d07 100644 --- a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Data/UAuthUserDbContext.cs +++ b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Data/UAuthUserDbContext.cs @@ -5,7 +5,7 @@ namespace CodeBeam.UltimateAuth.Users.EntityFrameworkCore; -internal sealed class UAuthUserDbContext : DbContext +public sealed class UAuthUserDbContext : DbContext { public DbSet Identifiers => Set(); public DbSet Lifecycles => Set(); diff --git a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Projections/UserIdentifierProjections.cs b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Projections/UserIdentifierProjections.cs index 1600e974..44ecf317 100644 --- a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Projections/UserIdentifierProjections.cs +++ b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Projections/UserIdentifierProjections.cs @@ -4,7 +4,7 @@ namespace CodeBeam.UltimateAuth.Users.EntityFrameworkCore; -internal sealed class UserIdentifierProjection +public sealed class UserIdentifierProjection { public Guid Id { get; set; } diff --git a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Projections/UserLifecycleProjection.cs b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Projections/UserLifecycleProjection.cs index 0f33546f..07ea8cde 100644 --- a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Projections/UserLifecycleProjection.cs +++ b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Projections/UserLifecycleProjection.cs @@ -4,7 +4,7 @@ namespace CodeBeam.UltimateAuth.Users.EntityFrameworkCore; -internal sealed class UserLifecycleProjection +public sealed class UserLifecycleProjection { public Guid Id { get; set; } diff --git a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Projections/UserProfileProjection.cs b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Projections/UserProfileProjection.cs index 6e5f07cd..654b96af 100644 --- a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Projections/UserProfileProjection.cs +++ b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Projections/UserProfileProjection.cs @@ -3,7 +3,7 @@ namespace CodeBeam.UltimateAuth.Users.EntityFrameworkCore; -internal sealed class UserProfileProjection +public sealed class UserProfileProjection { public Guid Id { get; set; } diff --git a/src/users/CodeBeam.UltimateAuth.Users.InMemory/Extensions/ServiceCollectionExtensions.cs b/src/users/CodeBeam.UltimateAuth.Users.InMemory/Extensions/ServiceCollectionExtensions.cs index 3d585691..72310f36 100644 --- a/src/users/CodeBeam.UltimateAuth.Users.InMemory/Extensions/ServiceCollectionExtensions.cs +++ b/src/users/CodeBeam.UltimateAuth.Users.InMemory/Extensions/ServiceCollectionExtensions.cs @@ -14,10 +14,6 @@ public static IServiceCollection AddUltimateAuthUsersInMemory(this IServiceColle services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.TryAddSingleton, InMemoryUserIdProvider>(); - - // Seed never try add - services.AddSingleton(); return services; } diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/CodeBeam.UltimateAuth.Tests.Unit.csproj b/tests/CodeBeam.UltimateAuth.Tests.Unit/CodeBeam.UltimateAuth.Tests.Unit.csproj index 073f0b56..465c2fc0 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/CodeBeam.UltimateAuth.Tests.Unit.csproj +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/CodeBeam.UltimateAuth.Tests.Unit.csproj @@ -19,8 +19,9 @@ - + + diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Helpers/TestAuthRuntime.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Helpers/TestAuthRuntime.cs index 38a62c52..a37eb1d7 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Helpers/TestAuthRuntime.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Helpers/TestAuthRuntime.cs @@ -8,6 +8,7 @@ using CodeBeam.UltimateAuth.Core.Options; using CodeBeam.UltimateAuth.Credentials.Reference; using CodeBeam.UltimateAuth.InMemory; +using CodeBeam.UltimateAuth.Sample.Seed.Extensions; using CodeBeam.UltimateAuth.Server.Auth; using CodeBeam.UltimateAuth.Server.Extensions; using CodeBeam.UltimateAuth.Server.Flows; @@ -36,6 +37,8 @@ public TestAuthRuntime(Action? configureServer = null, Actio configureServer?.Invoke(options); }); + services.AddUltimateAuthSampleSeed(); + services.AddSingleton(); // InMemory plugins services.AddUltimateAuthInMemory(); From beff8631e0d8eeee990c5911ce22e91a7dacaf6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Thu, 26 Mar 2026 21:40:26 +0300 Subject: [PATCH 3/6] Session Store Fix --- .../uauth.db | Bin 311296 -> 319488 bytes .../Issuers/UAuthSessionIssuer.cs | 26 ++++++++- .../Data/UAuthSessionDbContext.cs | 16 +++++- .../Stores/EfCoreSessionStore.cs | 53 +++++++++++++----- .../Data/UAuthUserDbContext.cs | 18 ++++-- 5 files changed, 90 insertions(+), 23 deletions(-) diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/uauth.db b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/uauth.db index fe98795ecb365cab2d1310cfd4f44df62a64f2d8..13c7c594e3a35facc7e77d8d28c4afbce5b402bd 100644 GIT binary patch delta 11942 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 B#HRoN delta 402 zcmYL^&r3o<5XX1tZ4dJ0&YBQZhaS~U1^WeE!hfI$FM&59N+?RAd%aFU7ZsresZ=To z0xb;o79P4kGlC#^=C$sf3R-)IZZq@!e3%)h8Frj-ezY$_2w5!H2^zUeUnCTB==a#M z4eW{u5dZC8mA$7-&jkFSqBb@BeA&kxS`nsIBe=qkf-k|?z^YYigha_wwFpcB?N{$H zn1q0d7Qs5s;;RyIX!D{iBFaj@SKQL40QU%Hxu?xGm);%}*a4@5YIh$Bf|-~iDv*bH z)Yh;}mElI1m?Zd#MSR39yvC$97f>ea&IzBT8_HNA)!}mr-tfu!9UmHG+oWXnkBJaz>i6!%EIjl^jdQe*sG$cC-Kh diff --git a/src/CodeBeam.UltimateAuth.Server/Infrastructure/Issuers/UAuthSessionIssuer.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/Issuers/UAuthSessionIssuer.cs index 4f1d4909..66d5b8ee 100644 --- a/src/CodeBeam.UltimateAuth.Server/Infrastructure/Issuers/UAuthSessionIssuer.cs +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/Issuers/UAuthSessionIssuer.cs @@ -67,8 +67,30 @@ await kernel.ExecuteAsync(async _ => if (context.ChainId is not null) { - chain = await kernel.GetChainAsync(context.ChainId.Value) - ?? throw new UAuthNotFoundException("Chain not found."); + var existing = await kernel.GetChainAsync(context.ChainId.Value); + + if (existing is null) + { + chain = UAuthSessionChain.Create( + SessionChainId.New(), + root.RootId, + context.Tenant, + context.UserKey, + now, + expiresAt, + context.Device, + ClaimsSnapshot.Empty, + root.SecurityVersion + ); + await kernel.CreateChainAsync(chain); + } + else + { + chain = existing; + } + + //chain = await kernel.GetChainAsync(context.ChainId.Value) + // ?? throw new UAuthNotFoundException("Chain not found."); if (chain.IsRevoked) throw new UAuthValidationException("Chain revoked."); diff --git a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Data/UAuthSessionDbContext.cs b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Data/UAuthSessionDbContext.cs index 4cc649d2..102d90d5 100644 --- a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Data/UAuthSessionDbContext.cs +++ b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Data/UAuthSessionDbContext.cs @@ -24,7 +24,11 @@ protected override void OnModelCreating(ModelBuilder b) e.HasKey(x => x.Id); e.Property(x => x.Version).IsConcurrencyToken(); - e.Property(x => x.CreatedAt).IsRequired(); + + e.Property(x => x.CreatedAt).IsRequired() + .HasConversion( + v => v.UtcDateTime, + v => new DateTimeOffset(v, TimeSpan.Zero)); e.Property(x => x.UserKey) .HasConversion( @@ -59,7 +63,10 @@ protected override void OnModelCreating(ModelBuilder b) e.HasKey(x => x.Id); e.Property(x => x.Version).IsConcurrencyToken(); - e.Property(x => x.CreatedAt).IsRequired(); + e.Property(x => x.CreatedAt).IsRequired() + .HasConversion( + v => v.UtcDateTime, + v => new DateTimeOffset(v, TimeSpan.Zero)); e.Property(x => x.UserKey) .HasConversion( @@ -120,6 +127,11 @@ protected override void OnModelCreating(ModelBuilder b) e.Property(x => x.Version).IsConcurrencyToken(); e.Property(x => x.CreatedAt).IsRequired(); + e.Property(x => x.CreatedAt).IsRequired() + .HasConversion( + v => v.UtcDateTime, + v => new DateTimeOffset(v, TimeSpan.Zero)); + e.Property(x => x.UserKey) .HasConversion( v => v.Value, diff --git a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Stores/EfCoreSessionStore.cs b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Stores/EfCoreSessionStore.cs index c9a31e6e..d1fcd845 100644 --- a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Stores/EfCoreSessionStore.cs +++ b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Stores/EfCoreSessionStore.cs @@ -77,6 +77,11 @@ public async Task ExecuteAsync(Func x.Tenant == _tenant && x.SessionId == sessionId); + + if (local != null) + return local.ToDomain(); + var projection = await _db.Sessions .AsNoTracking() .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.SessionId == sessionId); @@ -88,11 +93,16 @@ public async Task SaveSessionAsync(UAuthSession session, long expectedVersion, C { ct.ThrowIfCancellationRequested(); - var projection = await _db.Sessions + var projection = _db.Sessions.Local.FirstOrDefault(x => x.Tenant == _tenant && x.SessionId == session.SessionId); + + if (projection == null) + { + projection = await _db.Sessions .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.SessionId == session.SessionId, ct); + } if (projection is null) throw new UAuthNotFoundException("session_not_found"); @@ -210,9 +220,14 @@ public async Task RevokeOtherSessionsAsync(UserKey user, SessionChainId keepChai { ct.ThrowIfCancellationRequested(); + var local = _db.Chains.Local.FirstOrDefault(x => x.Tenant == _tenant && x.ChainId == chainId); + + if (local is not null) + return local.ToDomain(); + var projection = await _db.Chains .AsNoTracking() - .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.ChainId == chainId); + .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.ChainId == chainId, ct); return projection?.ToDomain(); } @@ -221,6 +236,17 @@ public async Task RevokeOtherSessionsAsync(UserKey user, SessionChainId keepChai { ct.ThrowIfCancellationRequested(); + var local = _db.Chains.Local + .Where(x => + x.Tenant == _tenant && + x.UserKey == userKey && + x.RevokedAt == null && + x.DeviceId == deviceId) + .FirstOrDefault(); + + if (local != null) + return local.ToDomain(); + var projection = await _db.Chains .AsNoTracking() .Where(x => @@ -228,7 +254,7 @@ public async Task RevokeOtherSessionsAsync(UserKey user, SessionChainId keepChai x.UserKey == userKey && x.RevokedAt == null && x.DeviceId == deviceId) - .SingleOrDefaultAsync(ct); + .FirstOrDefaultAsync(ct); return projection?.ToDomain(); } @@ -237,11 +263,13 @@ public async Task SaveChainAsync(UAuthSessionChain chain, long expectedVersion, { ct.ThrowIfCancellationRequested(); - var projection = await _db.Chains - .SingleOrDefaultAsync(x => - x.Tenant == _tenant && - x.ChainId == chain.ChainId, - ct); + var projection = _db.Chains.Local.FirstOrDefault(x => x.Tenant == _tenant && x.ChainId == chain.ChainId); + + if (projection is null) + { + projection = await _db.Chains + .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.ChainId == chain.ChainId, ct); + } if (projection is null) throw new UAuthNotFoundException("chain_not_found"); @@ -263,6 +291,7 @@ public Task CreateChainAsync(UAuthSessionChain chain, CancellationToken ct = def var projection = chain.ToProjection(); _db.Chains.Add(projection); + _db.Entry(projection).State = EntityState.Added; return Task.CompletedTask; } @@ -369,17 +398,15 @@ public async Task SetActiveSessionIdAsync(SessionChainId chainId, AuthSessionId { ct.ThrowIfCancellationRequested(); - var projection = _db.Chains.Local - .FirstOrDefault(x => x.Tenant == _tenant && x.ChainId == chainId); + var projection = _db.Chains.Local.FirstOrDefault(x => x.Tenant == _tenant && x.ChainId == chainId); if (projection is null) { - projection = await _db.Chains - .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.ChainId == chainId, ct); + projection = await _db.Chains.SingleOrDefaultAsync(x => x.Tenant == _tenant && x.ChainId == chainId, ct); } if (projection is null) - return; + throw new UAuthNotFoundException("chain_not_found"); projection.ActiveSessionId = sessionId; projection.Version++; diff --git a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Data/UAuthUserDbContext.cs b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Data/UAuthUserDbContext.cs index 40603d07..a51e248b 100644 --- a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Data/UAuthUserDbContext.cs +++ b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Data/UAuthUserDbContext.cs @@ -61,8 +61,10 @@ private void ConfigureIdentifiers(ModelBuilder b) e.HasIndex(x => new { x.Tenant, x.UserKey, x.IsPrimary }); e.HasIndex(x => new { x.Tenant, x.NormalizedValue }); - e.Property(x => x.CreatedAt) - .IsRequired(); + e.Property(x => x.CreatedAt).IsRequired() + .HasConversion( + v => v.UtcDateTime, + v => new DateTimeOffset(v, TimeSpan.Zero)); }); } @@ -95,8 +97,10 @@ private void ConfigureLifecycles(ModelBuilder b) e.Property(x => x.SecurityVersion) .IsRequired(); - e.Property(x => x.CreatedAt) - .IsRequired(); + e.Property(x => x.CreatedAt).IsRequired() + .HasConversion( + v => v.UtcDateTime, + v => new DateTimeOffset(v, TimeSpan.Zero)); }); } @@ -130,8 +134,10 @@ private void ConfigureProfiles(ModelBuilder b) .HasConversion(new NullableJsonValueConverter>()) .Metadata.SetValueComparer(JsonValueComparers.Create>()); - e.Property(x => x.CreatedAt) - .IsRequired(); + e.Property(x => x.CreatedAt).IsRequired() + .HasConversion( + v => v.UtcDateTime, + v => new DateTimeOffset(v, TimeSpan.Zero)); }); } } \ No newline at end of file From 789eb1fecee48ec65dc03a8c825ee6bd99d64073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Fri, 27 Mar 2026 21:44:22 +0300 Subject: [PATCH 4/6] Added Facade UAuthDbContext --- .../Data/UAuthDbContext.cs | 53 ++ ...timateAuthEntityFrameworkCoreExtensions.cs | 57 +- .../{ => Options}/UAuthEfCoreOptions.cs | 0 .../20260326115139_InitSessions.Designer.cs | 238 ------ .../Migrations/20260326115139_InitSessions.cs | 178 ----- ...0260327184128_InitUltimateAuth.Designer.cs | 710 ++++++++++++++++++ .../20260327184128_InitUltimateAuth.cs | 556 ++++++++++++++ ...60326121436_InitAuthentication.Designer.cs | 95 --- .../20260326121436_InitAuthentication.cs | 73 -- ...uthAuthenticationDbContextModelSnapshot.cs | 92 --- ...260326121602_InitAuthorization.Designer.cs | 116 --- .../20260326121602_InitAuthorization.cs | 105 --- ...AuthAuthorizationDbContextModelSnapshot.cs | 113 --- ...20260326121243_InitCredentials.Designer.cs | 91 --- .../20260326121243_InitCredentials.cs | 71 -- .../UAuthCredentialDbContextModelSnapshot.cs | 88 --- .../Migrations/UAuthDbContextModelSnapshot.cs | 707 +++++++++++++++++ .../UAuthSessionDbContextModelSnapshot.cs | 235 ------ .../20260326120856_InitTokens.Designer.cs | 96 --- .../UAuthTokenDb/20260326120856_InitTokens.cs | 91 --- .../UAuthTokenDbContextModelSnapshot.cs | 93 --- .../20260326121123_InitUsers.Designer.cs | 198 ----- .../UAuthUserDb/20260326121123_InitUsers.cs | 133 ---- .../UAuthUserDbContextModelSnapshot.cs | 195 ----- .../Program.cs | 15 +- .../Seed/UAuthDbInitializer.cs | 46 ++ .../uauth.db | Bin 319488 -> 4096 bytes .../uauth.db-shm | Bin 0 -> 32768 bytes .../uauth.db-wal | Bin 0 -> 914672 bytes .../Seed/UserSeedContributor.cs | 102 --- .../Data/UAuthAuthenticationDbContext.cs | 65 +- .../Data/UAuthAuthenticationModelBuilder.cs | 75 ++ .../Extensions/ServiceCollectionExtensions.cs | 10 +- .../EfCoreAuthenticationSecurityStateStore.cs | 18 +- ...AuthenticationSecurityStateStoreFactory.cs | 9 +- .../Data/UAuthAuthorizationDbContext.cs | 118 +-- .../Data/UAuthAuthorizationModelBuilder.cs | 109 +++ .../Extensions/ServiceCollectionExtensions.cs | 12 +- .../Projections/RoleProjection.cs | 2 - .../Stores/EfCoreRoleStore.cs | 47 +- .../Stores/EfCoreRoleStoreFactory.cs | 9 +- .../Stores/EfCoreUserRoleStore.cs | 22 +- .../Stores/EfCoreUserRoleStoreFactory.cs | 9 +- .../Data/UAuthCredentialDbContext.cs | 54 +- .../Data/UAuthCredentialsModelBuilder.cs | 60 ++ .../Extensions/ServiceCollectionExtensions.cs | 10 +- .../Stores/EfCorePasswordCredentialStore.cs | 28 +- .../EfCorePasswordCredentialStoreFactory.cs | 9 +- .../Infrastructure/DateTimeOffsetConverter.cs | 31 + .../Data/UAuthSessionDbContext.cs | 171 +---- .../Data/UAuthSessionsModelBuilder.cs | 170 +++++ .../Extensions/ServiceCollectionExtensions.cs | 10 +- .../Projections/SessionProjection.cs | 3 - .../Stores/EfCoreSessionStore.cs | 94 +-- .../Stores/EfCoreSessionStoreFactory.cs | 9 +- .../Data/UAuthTokenDbContext.cs | 58 +- .../Data/UAuthTokenModelBuilder.cs | 73 ++ .../Extensions/ServiceCollectionExtensions.cs | 10 +- .../Projections/RefreshTokenProjection.cs | 2 - .../Stores/EfCoreRefreshTokenStore.cs | 20 +- .../Stores/EfCoreRefreshTokenStoreFactory.cs | 9 +- .../Data/UAuthUserDbContext.cs | 133 +--- .../Data/UAuthUsersModelBuilder.cs | 122 +++ .../Extensions/ServiceCollectionExtensions.cs | 14 +- .../Projections/UserProfileProjection.cs | 2 - .../Stores/EFCoreUserProfileStoreFactory.cs | 9 +- .../Stores/EfCoreUserIdentifierStore.cs | 40 +- .../EfCoreUserIdentifierStoreFactory.cs | 8 +- .../Stores/EfCoreUserLifecycleStore.cs | 22 +- .../Stores/EfCoreUserLifecycleStoreFactory.cs | 8 +- .../Stores/EfCoreUserProfileStore.cs | 24 +- .../EfCoreAuthenticationStoreTests.cs | 30 +- .../EfCoreCredentialStoreTests.cs | 32 +- .../EfCoreRoleStoreTests.cs | 32 +- .../EfCoreSessionStoreTests.cs | 60 +- .../EfCoreTokenStoreTests.cs | 10 +- .../EfCoreUserIdentifierStoreTests.cs | 20 +- .../EfCoreUserLifecycleStoreTests.cs | 26 +- .../EfCoreUserProfileStoreTests.cs | 20 +- .../EfCoreUserRoleStoreTests.cs | 16 +- 80 files changed, 3167 insertions(+), 3334 deletions(-) create mode 100644 nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/Data/UAuthDbContext.cs rename nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/{ => Extensions}/UltimateAuthEntityFrameworkCoreExtensions.cs (59%) rename nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/{ => Options}/UAuthEfCoreOptions.cs (100%) delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260326115139_InitSessions.Designer.cs delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260326115139_InitSessions.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260327184128_InitUltimateAuth.Designer.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260327184128_InitUltimateAuth.cs delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/20260326121436_InitAuthentication.Designer.cs delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/20260326121436_InitAuthentication.cs delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/UAuthAuthenticationDbContextModelSnapshot.cs delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/20260326121602_InitAuthorization.Designer.cs delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/20260326121602_InitAuthorization.cs delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/UAuthAuthorizationDbContextModelSnapshot.cs delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/20260326121243_InitCredentials.Designer.cs delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/20260326121243_InitCredentials.cs delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/UAuthCredentialDbContextModelSnapshot.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthDbContextModelSnapshot.cs delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthSessionDbContextModelSnapshot.cs delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/20260326120856_InitTokens.Designer.cs delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/20260326120856_InitTokens.cs delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/UAuthTokenDbContextModelSnapshot.cs delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/20260326121123_InitUsers.Designer.cs delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/20260326121123_InitUsers.cs delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/UAuthUserDbContextModelSnapshot.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Seed/UAuthDbInitializer.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/uauth.db-shm create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/uauth.db-wal delete mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Seed/UserSeedContributor.cs create mode 100644 src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Data/UAuthAuthenticationModelBuilder.cs create mode 100644 src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Data/UAuthAuthorizationModelBuilder.cs create mode 100644 src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Data/UAuthCredentialsModelBuilder.cs create mode 100644 src/persistence/CodeBeam.UltimateAuth.EntityFrameworkCore/Infrastructure/DateTimeOffsetConverter.cs create mode 100644 src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Data/UAuthSessionsModelBuilder.cs create mode 100644 src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Data/UAuthTokenModelBuilder.cs create mode 100644 src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Data/UAuthUsersModelBuilder.cs diff --git a/nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/Data/UAuthDbContext.cs b/nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/Data/UAuthDbContext.cs new file mode 100644 index 00000000..35347918 --- /dev/null +++ b/nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/Data/UAuthDbContext.cs @@ -0,0 +1,53 @@ +using CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore; +using CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore; +using CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore; +using CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; +using CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore; +using CodeBeam.UltimateAuth.Users.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace CodeBeam.UltimateAuth.EntityFrameworkCore; + +public sealed class UAuthDbContext : DbContext +{ + public UAuthDbContext(DbContextOptions options) + : base(options) + { + } + + // Users + public DbSet UserLifecycles => Set(); + public DbSet UserProfiles => Set(); + public DbSet UserIdentifiers => Set(); + + // Credentials + public DbSet PasswordCredentials => Set(); + + // Authorization + public DbSet Roles => Set(); + public DbSet UserRoleAssignments => Set(); + public DbSet UserPermissions => Set(); + + // Sessions + public DbSet Roots => Set(); + public DbSet Chains => Set(); + public DbSet Sessions => Set(); + + // Tokens + public DbSet RefreshTokens => Set(); + + // Authentication + public DbSet AuthenticationSecurityStates => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + UAuthSessionsModelBuilder.Configure(modelBuilder); + UAuthTokensModelBuilder.Configure(modelBuilder); + UAuthAuthenticationModelBuilder.Configure(modelBuilder); + UAuthUsersModelBuilder.Configure(modelBuilder); + UAuthCredentialsModelBuilder.Configure(modelBuilder); + UAuthAuthorizationModelBuilder.Configure(modelBuilder); + } +} diff --git a/nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/UltimateAuthEntityFrameworkCoreExtensions.cs b/nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/Extensions/UltimateAuthEntityFrameworkCoreExtensions.cs similarity index 59% rename from nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/UltimateAuthEntityFrameworkCoreExtensions.cs rename to nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/Extensions/UltimateAuthEntityFrameworkCoreExtensions.cs index f5efa97b..25f63b6d 100644 --- a/nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/UltimateAuthEntityFrameworkCoreExtensions.cs +++ b/nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/Extensions/UltimateAuthEntityFrameworkCoreExtensions.cs @@ -1,9 +1,15 @@ -using CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore.Extensions; +using CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore; +using CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore.Extensions; +using CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore; using CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.Extensions; +using CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore; using CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore.Extensions; using CodeBeam.UltimateAuth.Reference.Bundle; +using CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; using CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.Extensions; +using CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore; using CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore.Extensions; +using CodeBeam.UltimateAuth.Users.EntityFrameworkCore; using CodeBeam.UltimateAuth.Users.EntityFrameworkCore.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -56,18 +62,28 @@ public static class UltimateAuthEntityFrameworkCoreExtensions /// public static IServiceCollection AddUltimateAuthEntityFrameworkCore(this IServiceCollection services, Action configureDb) { - services - .AddUltimateAuthReferences() - .AddUltimateAuthUsersEntityFrameworkCore(configureDb) - .AddUltimateAuthCredentialsEntityFrameworkCore(configureDb) - .AddUltimateAuthAuthorizationEntityFrameworkCore(configureDb) - .AddUltimateAuthSessionsEntityFrameworkCore(configureDb) - .AddUltimateAuthTokensEntityFrameworkCore(configureDb) - .AddUltimateAuthAuthenticationEntityFrameworkCore(configureDb); + services.AddUltimateAuthReferences(); + services.AddDbContext(configureDb); + services.AddUltimateAuthEfCoreStores(); return services; } + + //public static IServiceCollection AddUltimateAuthEntityFrameworkCore(this IServiceCollection services, Action configureDb) + //{ + // services + // .AddUltimateAuthReferences() + // .AddUltimateAuthUsersEntityFrameworkCore(configureDb) + // .AddUltimateAuthCredentialsEntityFrameworkCore(configureDb) + // .AddUltimateAuthAuthorizationEntityFrameworkCore(configureDb) + // .AddUltimateAuthSessionsEntityFrameworkCore(configureDb) + // .AddUltimateAuthTokensEntityFrameworkCore(configureDb) + // .AddUltimateAuthAuthenticationEntityFrameworkCore(configureDb); + + // return services; + //} + /// /// Adds and configures Entity Framework Core-based UltimateAuth services and related references to the specified /// service collection. @@ -91,13 +107,24 @@ public static IServiceCollection AddUltimateAuthEntityFrameworkCore(this IServic services .AddUltimateAuthReferences() - .AddUltimateAuthUsersEntityFrameworkCore(options.Resolve(options.Users)) - .AddUltimateAuthCredentialsEntityFrameworkCore(options.Resolve(options.Credentials)) - .AddUltimateAuthAuthorizationEntityFrameworkCore(options.Resolve(options.Authorization)) - .AddUltimateAuthSessionsEntityFrameworkCore(options.Resolve(options.Sessions)) - .AddUltimateAuthTokensEntityFrameworkCore(options.Resolve(options.Tokens)) - .AddUltimateAuthAuthenticationEntityFrameworkCore(options.Resolve(options.Authentication)); + .AddUltimateAuthUsersEntityFrameworkCore(options.Resolve(options.Users)) + .AddUltimateAuthCredentialsEntityFrameworkCore(options.Resolve(options.Credentials)) + .AddUltimateAuthAuthorizationEntityFrameworkCore(options.Resolve(options.Authorization)) + .AddUltimateAuthSessionsEntityFrameworkCore(options.Resolve(options.Sessions)) + .AddUltimateAuthTokensEntityFrameworkCore(options.Resolve(options.Tokens)) + .AddUltimateAuthAuthenticationEntityFrameworkCore(options.Resolve(options.Authentication)); return services; } + + public static IServiceCollection AddUltimateAuthEfCoreStores(this IServiceCollection services) + { + return services + .AddUltimateAuthUsersEntityFrameworkCore() + .AddUltimateAuthSessionsEntityFrameworkCore() + .AddUltimateAuthTokensEntityFrameworkCore() + .AddUltimateAuthAuthorizationEntityFrameworkCore() + .AddUltimateAuthCredentialsEntityFrameworkCore() + .AddUltimateAuthAuthenticationEntityFrameworkCore(); + } } diff --git a/nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/UAuthEfCoreOptions.cs b/nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/Options/UAuthEfCoreOptions.cs similarity index 100% rename from nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/UAuthEfCoreOptions.cs rename to nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/Options/UAuthEfCoreOptions.cs diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260326115139_InitSessions.Designer.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260326115139_InitSessions.Designer.cs deleted file mode 100644 index c540962d..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260326115139_InitSessions.Designer.cs +++ /dev/null @@ -1,238 +0,0 @@ -// -using System; -using CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations -{ - [DbContext(typeof(UAuthSessionDbContext))] - [Migration("20260326115139_InitSessions")] - partial class InitSessions - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AbsoluteExpiresAt") - .HasColumnType("TEXT"); - - b.Property("ActiveSessionId") - .HasColumnType("TEXT"); - - b.Property("ChainId") - .HasColumnType("TEXT"); - - b.Property("ClaimsSnapshot") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("Device") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("DeviceId") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("TEXT"); - - b.Property("LastSeenAt") - .HasColumnType("TEXT"); - - b.Property("RevokedAt") - .HasColumnType("TEXT"); - - b.Property("RootId") - .HasColumnType("TEXT"); - - b.Property("RotationCount") - .HasColumnType("INTEGER"); - - b.Property("SecurityVersionAtCreation") - .HasColumnType("INTEGER"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("TouchCount") - .HasColumnType("INTEGER"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "ChainId") - .IsUnique(); - - b.HasIndex("Tenant", "RootId"); - - b.HasIndex("Tenant", "UserKey"); - - b.HasIndex("Tenant", "UserKey", "DeviceId"); - - b.ToTable("UAuth_SessionChains", (string)null); - }); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ChainId") - .HasColumnType("TEXT"); - - b.Property("Claims") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("Device") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("ExpiresAt") - .HasColumnType("TEXT"); - - b.Property("Metadata") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("RevokedAt") - .HasColumnType("TEXT"); - - b.Property("SecurityVersionAtCreation") - .HasColumnType("INTEGER"); - - b.Property("SessionId") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "ChainId"); - - b.HasIndex("Tenant", "ExpiresAt"); - - b.HasIndex("Tenant", "RevokedAt"); - - b.HasIndex("Tenant", "SessionId") - .IsUnique(); - - b.HasIndex("Tenant", "ChainId", "RevokedAt"); - - b.HasIndex("Tenant", "UserKey", "RevokedAt"); - - b.ToTable("UAuth_Sessions", (string)null); - }); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionRootProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("RevokedAt") - .HasColumnType("TEXT"); - - b.Property("RootId") - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("SecurityVersion") - .HasColumnType("INTEGER"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("UpdatedAt") - .HasColumnType("TEXT"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "RootId") - .IsUnique(); - - b.HasIndex("Tenant", "UserKey") - .IsUnique(); - - b.ToTable("UAuth_SessionRoots", (string)null); - }); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", b => - { - b.HasOne("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionRootProjection", null) - .WithMany() - .HasForeignKey("Tenant", "RootId") - .HasPrincipalKey("Tenant", "RootId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - }); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionProjection", b => - { - b.HasOne("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", null) - .WithMany() - .HasForeignKey("Tenant", "ChainId") - .HasPrincipalKey("Tenant", "ChainId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260326115139_InitSessions.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260326115139_InitSessions.cs deleted file mode 100644 index 6846225f..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260326115139_InitSessions.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations -{ - /// - public partial class InitSessions : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "UAuth_SessionRoots", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - RootId = table.Column(type: "TEXT", maxLength: 128, nullable: false), - Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), - UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), - CreatedAt = table.Column(type: "TEXT", nullable: false), - UpdatedAt = table.Column(type: "TEXT", nullable: true), - RevokedAt = table.Column(type: "TEXT", nullable: true), - SecurityVersion = table.Column(type: "INTEGER", nullable: false), - Version = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UAuth_SessionRoots", x => x.Id); - table.UniqueConstraint("AK_UAuth_SessionRoots_Tenant_RootId", x => new { x.Tenant, x.RootId }); - }); - - migrationBuilder.CreateTable( - name: "UAuth_SessionChains", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - ChainId = table.Column(type: "TEXT", nullable: false), - RootId = table.Column(type: "TEXT", nullable: false), - Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), - UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), - CreatedAt = table.Column(type: "TEXT", nullable: false), - LastSeenAt = table.Column(type: "TEXT", nullable: false), - AbsoluteExpiresAt = table.Column(type: "TEXT", nullable: true), - DeviceId = table.Column(type: "TEXT", maxLength: 64, nullable: false), - Device = table.Column(type: "TEXT", nullable: false), - ClaimsSnapshot = table.Column(type: "TEXT", nullable: false), - ActiveSessionId = table.Column(type: "TEXT", nullable: true), - RotationCount = table.Column(type: "INTEGER", nullable: false), - TouchCount = table.Column(type: "INTEGER", nullable: false), - SecurityVersionAtCreation = table.Column(type: "INTEGER", nullable: false), - RevokedAt = table.Column(type: "TEXT", nullable: true), - Version = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UAuth_SessionChains", x => x.Id); - table.UniqueConstraint("AK_UAuth_SessionChains_Tenant_ChainId", x => new { x.Tenant, x.ChainId }); - table.ForeignKey( - name: "FK_UAuth_SessionChains_UAuth_SessionRoots_Tenant_RootId", - columns: x => new { x.Tenant, x.RootId }, - principalTable: "UAuth_SessionRoots", - principalColumns: new[] { "Tenant", "RootId" }, - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "UAuth_Sessions", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - SessionId = table.Column(type: "TEXT", nullable: false), - ChainId = table.Column(type: "TEXT", nullable: false), - Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), - UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), - CreatedAt = table.Column(type: "TEXT", nullable: false), - ExpiresAt = table.Column(type: "TEXT", nullable: false), - RevokedAt = table.Column(type: "TEXT", nullable: true), - SecurityVersionAtCreation = table.Column(type: "INTEGER", nullable: false), - Device = table.Column(type: "TEXT", nullable: false), - Claims = table.Column(type: "TEXT", nullable: false), - Metadata = table.Column(type: "TEXT", nullable: false), - Version = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UAuth_Sessions", x => x.Id); - table.ForeignKey( - name: "FK_UAuth_Sessions_UAuth_SessionChains_Tenant_ChainId", - columns: x => new { x.Tenant, x.ChainId }, - principalTable: "UAuth_SessionChains", - principalColumns: new[] { "Tenant", "ChainId" }, - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_SessionChains_Tenant_ChainId", - table: "UAuth_SessionChains", - columns: new[] { "Tenant", "ChainId" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_SessionChains_Tenant_RootId", - table: "UAuth_SessionChains", - columns: new[] { "Tenant", "RootId" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_SessionChains_Tenant_UserKey", - table: "UAuth_SessionChains", - columns: new[] { "Tenant", "UserKey" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_SessionChains_Tenant_UserKey_DeviceId", - table: "UAuth_SessionChains", - columns: new[] { "Tenant", "UserKey", "DeviceId" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_SessionRoots_Tenant_RootId", - table: "UAuth_SessionRoots", - columns: new[] { "Tenant", "RootId" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_SessionRoots_Tenant_UserKey", - table: "UAuth_SessionRoots", - columns: new[] { "Tenant", "UserKey" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_Sessions_Tenant_ChainId", - table: "UAuth_Sessions", - columns: new[] { "Tenant", "ChainId" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_Sessions_Tenant_ChainId_RevokedAt", - table: "UAuth_Sessions", - columns: new[] { "Tenant", "ChainId", "RevokedAt" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_Sessions_Tenant_ExpiresAt", - table: "UAuth_Sessions", - columns: new[] { "Tenant", "ExpiresAt" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_Sessions_Tenant_RevokedAt", - table: "UAuth_Sessions", - columns: new[] { "Tenant", "RevokedAt" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_Sessions_Tenant_SessionId", - table: "UAuth_Sessions", - columns: new[] { "Tenant", "SessionId" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_Sessions_Tenant_UserKey_RevokedAt", - table: "UAuth_Sessions", - columns: new[] { "Tenant", "UserKey", "RevokedAt" }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "UAuth_Sessions"); - - migrationBuilder.DropTable( - name: "UAuth_SessionChains"); - - migrationBuilder.DropTable( - name: "UAuth_SessionRoots"); - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260327184128_InitUltimateAuth.Designer.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260327184128_InitUltimateAuth.Designer.cs new file mode 100644 index 00000000..079580bb --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260327184128_InitUltimateAuth.Designer.cs @@ -0,0 +1,710 @@ +// +using System; +using CodeBeam.UltimateAuth.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations +{ + [DbContext(typeof(UAuthDbContext))] + [Migration("20260327184128_InitUltimateAuth")] + partial class InitUltimateAuth + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore.AuthenticationSecurityStateProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CredentialType") + .HasColumnType("INTEGER"); + + b.Property("FailedAttempts") + .HasColumnType("INTEGER"); + + b.Property("LastFailedAt") + .HasColumnType("TEXT"); + + b.Property("LockedUntil") + .HasColumnType("TEXT"); + + b.Property("RequiresReauthentication") + .HasColumnType("INTEGER"); + + b.Property("ResetAttempts") + .HasColumnType("INTEGER"); + + b.Property("ResetConsumedAt") + .HasColumnType("TEXT"); + + b.Property("ResetExpiresAt") + .HasColumnType("TEXT"); + + b.Property("ResetRequestedAt") + .HasColumnType("TEXT"); + + b.Property("ResetTokenHash") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Scope") + .HasColumnType("INTEGER"); + + b.Property("SecurityVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "LockedUntil"); + + b.HasIndex("Tenant", "ResetRequestedAt"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "UserKey", "Scope"); + + b.HasIndex("Tenant", "UserKey", "Scope", "CredentialType") + .IsUnique(); + + b.ToTable("UAuth_Authentication", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.RolePermissionProjection", b => + { + b.Property("Tenant") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.Property("Permission") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Tenant", "RoleId", "Permission"); + + b.HasIndex("Tenant", "Permission"); + + b.HasIndex("Tenant", "RoleId"); + + b.ToTable("UAuth_RolePermissions", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.RoleProjection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "Id") + .IsUnique(); + + b.HasIndex("Tenant", "NormalizedName") + .IsUnique(); + + b.ToTable("UAuth_Roles", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.UserRoleProjection", b => + { + b.Property("Tenant") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UserKey") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.Property("AssignedAt") + .HasColumnType("TEXT"); + + b.HasKey("Tenant", "UserKey", "RoleId"); + + b.HasIndex("Tenant", "RoleId"); + + b.HasIndex("Tenant", "UserKey"); + + b.ToTable("UAuth_UserRoles", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore.PasswordCredentialProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("LastUsedAt") + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("SecretHash") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "ExpiresAt"); + + b.HasIndex("Tenant", "Id") + .IsUnique(); + + b.HasIndex("Tenant", "RevokedAt"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "UserKey", "DeletedAt"); + + b.ToTable("UAuth_PasswordCredentials", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AbsoluteExpiresAt") + .HasColumnType("TEXT"); + + b.Property("ActiveSessionId") + .HasColumnType("TEXT"); + + b.Property("ChainId") + .HasColumnType("TEXT"); + + b.Property("ClaimsSnapshot") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Device") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("LastSeenAt") + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("RootId") + .HasColumnType("TEXT"); + + b.Property("RotationCount") + .HasColumnType("INTEGER"); + + b.Property("SecurityVersionAtCreation") + .HasColumnType("INTEGER"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("TouchCount") + .HasColumnType("INTEGER"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "ChainId") + .IsUnique(); + + b.HasIndex("Tenant", "RootId"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "UserKey", "DeviceId"); + + b.ToTable("UAuth_SessionChains", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChainId") + .HasColumnType("TEXT"); + + b.Property("Claims") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Device") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("SecurityVersionAtCreation") + .HasColumnType("INTEGER"); + + b.Property("SessionId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "ChainId"); + + b.HasIndex("Tenant", "ExpiresAt"); + + b.HasIndex("Tenant", "RevokedAt"); + + b.HasIndex("Tenant", "SessionId") + .IsUnique(); + + b.HasIndex("Tenant", "ChainId", "RevokedAt"); + + b.HasIndex("Tenant", "UserKey", "RevokedAt"); + + b.ToTable("UAuth_Sessions", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionRootProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("RootId") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("SecurityVersion") + .HasColumnType("INTEGER"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "RootId") + .IsUnique(); + + b.HasIndex("Tenant", "UserKey") + .IsUnique(); + + b.ToTable("UAuth_SessionRoots", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore.RefreshTokenProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChainId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("ReplacedByTokenHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("TokenHash") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("TokenId") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "ChainId"); + + b.HasIndex("Tenant", "ExpiresAt"); + + b.HasIndex("Tenant", "ReplacedByTokenHash"); + + b.HasIndex("Tenant", "SessionId"); + + b.HasIndex("Tenant", "TokenHash") + .IsUnique(); + + b.HasIndex("Tenant", "TokenId"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "ExpiresAt", "RevokedAt"); + + b.HasIndex("Tenant", "TokenHash", "RevokedAt"); + + b.ToTable("UAuth_RefreshTokens", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserIdentifierProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("IsPrimary") + .HasColumnType("INTEGER"); + + b.Property("NormalizedValue") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("VerifiedAt") + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "NormalizedValue"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "Type", "NormalizedValue") + .IsUnique(); + + b.HasIndex("Tenant", "UserKey", "IsPrimary"); + + b.HasIndex("Tenant", "UserKey", "Type", "IsPrimary"); + + b.ToTable("UAuth_UserIdentifiers", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserLifecycleProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("SecurityVersion") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "UserKey") + .IsUnique(); + + b.ToTable("UAuth_UserLifecycles", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserProfileProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Bio") + .HasColumnType("TEXT"); + + b.Property("BirthDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Culture") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("FirstName") + .HasColumnType("TEXT"); + + b.Property("Gender") + .HasColumnType("TEXT"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LastName") + .HasColumnType("TEXT"); + + b.Property("Metadata") + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("TimeZone") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "UserKey"); + + b.ToTable("UAuth_UserProfiles", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", b => + { + b.HasOne("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionRootProjection", null) + .WithMany() + .HasForeignKey("Tenant", "RootId") + .HasPrincipalKey("Tenant", "RootId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionProjection", b => + { + b.HasOne("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", null) + .WithMany() + .HasForeignKey("Tenant", "ChainId") + .HasPrincipalKey("Tenant", "ChainId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260327184128_InitUltimateAuth.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260327184128_InitUltimateAuth.cs new file mode 100644 index 00000000..9f373138 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/20260327184128_InitUltimateAuth.cs @@ -0,0 +1,556 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations +{ + /// + public partial class InitUltimateAuth : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UAuth_Authentication", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Scope = table.Column(type: "INTEGER", nullable: false), + CredentialType = table.Column(type: "INTEGER", nullable: true), + FailedAttempts = table.Column(type: "INTEGER", nullable: false), + LastFailedAt = table.Column(type: "TEXT", nullable: true), + LockedUntil = table.Column(type: "TEXT", nullable: true), + RequiresReauthentication = table.Column(type: "INTEGER", nullable: false), + ResetRequestedAt = table.Column(type: "TEXT", nullable: true), + ResetExpiresAt = table.Column(type: "TEXT", nullable: true), + ResetConsumedAt = table.Column(type: "TEXT", nullable: true), + ResetTokenHash = table.Column(type: "TEXT", maxLength: 512, nullable: true), + ResetAttempts = table.Column(type: "INTEGER", nullable: false), + SecurityVersion = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_Authentication", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UAuth_PasswordCredentials", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + SecretHash = table.Column(type: "TEXT", maxLength: 512, nullable: false), + RevokedAt = table.Column(type: "TEXT", nullable: true), + ExpiresAt = table.Column(type: "TEXT", nullable: true), + SecurityStamp = table.Column(type: "TEXT", nullable: false), + LastUsedAt = table.Column(type: "TEXT", nullable: true), + Source = table.Column(type: "TEXT", maxLength: 128, nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + DeletedAt = table.Column(type: "TEXT", nullable: true), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_PasswordCredentials", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UAuth_RefreshTokens", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + TokenId = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + TokenHash = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + SessionId = table.Column(type: "TEXT", nullable: false), + ChainId = table.Column(type: "TEXT", nullable: true), + ReplacedByTokenHash = table.Column(type: "TEXT", maxLength: 128, nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + ExpiresAt = table.Column(type: "TEXT", nullable: false), + RevokedAt = table.Column(type: "TEXT", nullable: true), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_RefreshTokens", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UAuth_RolePermissions", + columns: table => new + { + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + RoleId = table.Column(type: "TEXT", nullable: false), + Permission = table.Column(type: "TEXT", maxLength: 256, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_RolePermissions", x => new { x.Tenant, x.RoleId, x.Permission }); + }); + + migrationBuilder.CreateTable( + name: "UAuth_Roles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Name = table.Column(type: "TEXT", maxLength: 128, nullable: false), + NormalizedName = table.Column(type: "TEXT", maxLength: 128, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + DeletedAt = table.Column(type: "TEXT", nullable: true), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_Roles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UAuth_SessionRoots", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RootId = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + RevokedAt = table.Column(type: "TEXT", nullable: true), + SecurityVersion = table.Column(type: "INTEGER", nullable: false), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_SessionRoots", x => x.Id); + table.UniqueConstraint("AK_UAuth_SessionRoots_Tenant_RootId", x => new { x.Tenant, x.RootId }); + }); + + migrationBuilder.CreateTable( + name: "UAuth_UserIdentifiers", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Type = table.Column(type: "INTEGER", nullable: false), + Value = table.Column(type: "TEXT", maxLength: 256, nullable: false), + NormalizedValue = table.Column(type: "TEXT", maxLength: 256, nullable: false), + IsPrimary = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + VerifiedAt = table.Column(type: "TEXT", nullable: true), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + DeletedAt = table.Column(type: "TEXT", nullable: true), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_UserIdentifiers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UAuth_UserLifecycles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Status = table.Column(type: "INTEGER", nullable: false), + SecurityVersion = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + DeletedAt = table.Column(type: "TEXT", nullable: true), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_UserLifecycles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UAuth_UserProfiles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + FirstName = table.Column(type: "TEXT", nullable: true), + LastName = table.Column(type: "TEXT", nullable: true), + DisplayName = table.Column(type: "TEXT", nullable: true), + BirthDate = table.Column(type: "TEXT", nullable: true), + Gender = table.Column(type: "TEXT", nullable: true), + Bio = table.Column(type: "TEXT", nullable: true), + Language = table.Column(type: "TEXT", nullable: true), + TimeZone = table.Column(type: "TEXT", nullable: true), + Culture = table.Column(type: "TEXT", nullable: true), + Metadata = table.Column(type: "TEXT", nullable: true), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: true), + DeletedAt = table.Column(type: "TEXT", nullable: true), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_UserProfiles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UAuth_UserRoles", + columns: table => new + { + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + RoleId = table.Column(type: "TEXT", nullable: false), + AssignedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_UserRoles", x => new { x.Tenant, x.UserKey, x.RoleId }); + }); + + migrationBuilder.CreateTable( + name: "UAuth_SessionChains", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ChainId = table.Column(type: "TEXT", nullable: false), + RootId = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + LastSeenAt = table.Column(type: "TEXT", nullable: false), + AbsoluteExpiresAt = table.Column(type: "TEXT", nullable: true), + DeviceId = table.Column(type: "TEXT", maxLength: 64, nullable: false), + Device = table.Column(type: "TEXT", nullable: false), + ClaimsSnapshot = table.Column(type: "TEXT", nullable: false), + ActiveSessionId = table.Column(type: "TEXT", nullable: true), + RotationCount = table.Column(type: "INTEGER", nullable: false), + TouchCount = table.Column(type: "INTEGER", nullable: false), + SecurityVersionAtCreation = table.Column(type: "INTEGER", nullable: false), + RevokedAt = table.Column(type: "TEXT", nullable: true), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_SessionChains", x => x.Id); + table.UniqueConstraint("AK_UAuth_SessionChains_Tenant_ChainId", x => new { x.Tenant, x.ChainId }); + table.ForeignKey( + name: "FK_UAuth_SessionChains_UAuth_SessionRoots_Tenant_RootId", + columns: x => new { x.Tenant, x.RootId }, + principalTable: "UAuth_SessionRoots", + principalColumns: new[] { "Tenant", "RootId" }, + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "UAuth_Sessions", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + SessionId = table.Column(type: "TEXT", nullable: false), + ChainId = table.Column(type: "TEXT", nullable: false), + Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), + UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + ExpiresAt = table.Column(type: "TEXT", nullable: false), + RevokedAt = table.Column(type: "TEXT", nullable: true), + SecurityVersionAtCreation = table.Column(type: "INTEGER", nullable: false), + Device = table.Column(type: "TEXT", nullable: false), + Claims = table.Column(type: "TEXT", nullable: false), + Metadata = table.Column(type: "TEXT", nullable: false), + Version = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UAuth_Sessions", x => x.Id); + table.ForeignKey( + name: "FK_UAuth_Sessions_UAuth_SessionChains_Tenant_ChainId", + columns: x => new { x.Tenant, x.ChainId }, + principalTable: "UAuth_SessionChains", + principalColumns: new[] { "Tenant", "ChainId" }, + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Authentication_Tenant_LockedUntil", + table: "UAuth_Authentication", + columns: new[] { "Tenant", "LockedUntil" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Authentication_Tenant_ResetRequestedAt", + table: "UAuth_Authentication", + columns: new[] { "Tenant", "ResetRequestedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Authentication_Tenant_UserKey", + table: "UAuth_Authentication", + columns: new[] { "Tenant", "UserKey" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Authentication_Tenant_UserKey_Scope", + table: "UAuth_Authentication", + columns: new[] { "Tenant", "UserKey", "Scope" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Authentication_Tenant_UserKey_Scope_CredentialType", + table: "UAuth_Authentication", + columns: new[] { "Tenant", "UserKey", "Scope", "CredentialType" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_PasswordCredentials_Tenant_ExpiresAt", + table: "UAuth_PasswordCredentials", + columns: new[] { "Tenant", "ExpiresAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_PasswordCredentials_Tenant_Id", + table: "UAuth_PasswordCredentials", + columns: new[] { "Tenant", "Id" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_PasswordCredentials_Tenant_RevokedAt", + table: "UAuth_PasswordCredentials", + columns: new[] { "Tenant", "RevokedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_PasswordCredentials_Tenant_UserKey", + table: "UAuth_PasswordCredentials", + columns: new[] { "Tenant", "UserKey" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_PasswordCredentials_Tenant_UserKey_DeletedAt", + table: "UAuth_PasswordCredentials", + columns: new[] { "Tenant", "UserKey", "DeletedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_ChainId", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "ChainId" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_ExpiresAt", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "ExpiresAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_ExpiresAt_RevokedAt", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "ExpiresAt", "RevokedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_ReplacedByTokenHash", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "ReplacedByTokenHash" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_SessionId", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "SessionId" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_TokenHash", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "TokenHash" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_TokenHash_RevokedAt", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "TokenHash", "RevokedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_TokenId", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "TokenId" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RefreshTokens_Tenant_UserKey", + table: "UAuth_RefreshTokens", + columns: new[] { "Tenant", "UserKey" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RolePermissions_Tenant_Permission", + table: "UAuth_RolePermissions", + columns: new[] { "Tenant", "Permission" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_RolePermissions_Tenant_RoleId", + table: "UAuth_RolePermissions", + columns: new[] { "Tenant", "RoleId" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Roles_Tenant_Id", + table: "UAuth_Roles", + columns: new[] { "Tenant", "Id" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Roles_Tenant_NormalizedName", + table: "UAuth_Roles", + columns: new[] { "Tenant", "NormalizedName" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_SessionChains_Tenant_ChainId", + table: "UAuth_SessionChains", + columns: new[] { "Tenant", "ChainId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_SessionChains_Tenant_RootId", + table: "UAuth_SessionChains", + columns: new[] { "Tenant", "RootId" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_SessionChains_Tenant_UserKey", + table: "UAuth_SessionChains", + columns: new[] { "Tenant", "UserKey" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_SessionChains_Tenant_UserKey_DeviceId", + table: "UAuth_SessionChains", + columns: new[] { "Tenant", "UserKey", "DeviceId" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_SessionRoots_Tenant_RootId", + table: "UAuth_SessionRoots", + columns: new[] { "Tenant", "RootId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_SessionRoots_Tenant_UserKey", + table: "UAuth_SessionRoots", + columns: new[] { "Tenant", "UserKey" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Sessions_Tenant_ChainId", + table: "UAuth_Sessions", + columns: new[] { "Tenant", "ChainId" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Sessions_Tenant_ChainId_RevokedAt", + table: "UAuth_Sessions", + columns: new[] { "Tenant", "ChainId", "RevokedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Sessions_Tenant_ExpiresAt", + table: "UAuth_Sessions", + columns: new[] { "Tenant", "ExpiresAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Sessions_Tenant_RevokedAt", + table: "UAuth_Sessions", + columns: new[] { "Tenant", "RevokedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Sessions_Tenant_SessionId", + table: "UAuth_Sessions", + columns: new[] { "Tenant", "SessionId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_Sessions_Tenant_UserKey_RevokedAt", + table: "UAuth_Sessions", + columns: new[] { "Tenant", "UserKey", "RevokedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserIdentifiers_Tenant_NormalizedValue", + table: "UAuth_UserIdentifiers", + columns: new[] { "Tenant", "NormalizedValue" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserIdentifiers_Tenant_Type_NormalizedValue", + table: "UAuth_UserIdentifiers", + columns: new[] { "Tenant", "Type", "NormalizedValue" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserIdentifiers_Tenant_UserKey", + table: "UAuth_UserIdentifiers", + columns: new[] { "Tenant", "UserKey" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserIdentifiers_Tenant_UserKey_IsPrimary", + table: "UAuth_UserIdentifiers", + columns: new[] { "Tenant", "UserKey", "IsPrimary" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserIdentifiers_Tenant_UserKey_Type_IsPrimary", + table: "UAuth_UserIdentifiers", + columns: new[] { "Tenant", "UserKey", "Type", "IsPrimary" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserLifecycles_Tenant_UserKey", + table: "UAuth_UserLifecycles", + columns: new[] { "Tenant", "UserKey" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserProfiles_Tenant_UserKey", + table: "UAuth_UserProfiles", + columns: new[] { "Tenant", "UserKey" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserRoles_Tenant_RoleId", + table: "UAuth_UserRoles", + columns: new[] { "Tenant", "RoleId" }); + + migrationBuilder.CreateIndex( + name: "IX_UAuth_UserRoles_Tenant_UserKey", + table: "UAuth_UserRoles", + columns: new[] { "Tenant", "UserKey" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UAuth_Authentication"); + + migrationBuilder.DropTable( + name: "UAuth_PasswordCredentials"); + + migrationBuilder.DropTable( + name: "UAuth_RefreshTokens"); + + migrationBuilder.DropTable( + name: "UAuth_RolePermissions"); + + migrationBuilder.DropTable( + name: "UAuth_Roles"); + + migrationBuilder.DropTable( + name: "UAuth_Sessions"); + + migrationBuilder.DropTable( + name: "UAuth_UserIdentifiers"); + + migrationBuilder.DropTable( + name: "UAuth_UserLifecycles"); + + migrationBuilder.DropTable( + name: "UAuth_UserProfiles"); + + migrationBuilder.DropTable( + name: "UAuth_UserRoles"); + + migrationBuilder.DropTable( + name: "UAuth_SessionChains"); + + migrationBuilder.DropTable( + name: "UAuth_SessionRoots"); + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/20260326121436_InitAuthentication.Designer.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/20260326121436_InitAuthentication.Designer.cs deleted file mode 100644 index 198e9c90..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/20260326121436_InitAuthentication.Designer.cs +++ /dev/null @@ -1,95 +0,0 @@ -// -using System; -using CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthAuthenticationDb -{ - [DbContext(typeof(UAuthAuthenticationDbContext))] - [Migration("20260326121436_InitAuthentication")] - partial class InitAuthentication - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore.AuthenticationSecurityStateProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("CredentialType") - .HasColumnType("INTEGER"); - - b.Property("FailedAttempts") - .HasColumnType("INTEGER"); - - b.Property("LastFailedAt") - .HasColumnType("TEXT"); - - b.Property("LockedUntil") - .HasColumnType("TEXT"); - - b.Property("RequiresReauthentication") - .HasColumnType("INTEGER"); - - b.Property("ResetAttempts") - .HasColumnType("INTEGER"); - - b.Property("ResetConsumedAt") - .HasColumnType("TEXT"); - - b.Property("ResetExpiresAt") - .HasColumnType("TEXT"); - - b.Property("ResetRequestedAt") - .HasColumnType("TEXT"); - - b.Property("ResetTokenHash") - .HasMaxLength(512) - .HasColumnType("TEXT"); - - b.Property("Scope") - .HasColumnType("INTEGER"); - - b.Property("SecurityVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "LockedUntil"); - - b.HasIndex("Tenant", "ResetRequestedAt"); - - b.HasIndex("Tenant", "UserKey"); - - b.HasIndex("Tenant", "UserKey", "Scope"); - - b.HasIndex("Tenant", "UserKey", "Scope", "CredentialType") - .IsUnique(); - - b.ToTable("UAuth_Authentication", (string)null); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/20260326121436_InitAuthentication.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/20260326121436_InitAuthentication.cs deleted file mode 100644 index 1410deff..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/20260326121436_InitAuthentication.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthAuthenticationDb -{ - /// - public partial class InitAuthentication : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "UAuth_Authentication", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), - UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), - Scope = table.Column(type: "INTEGER", nullable: false), - CredentialType = table.Column(type: "INTEGER", nullable: true), - FailedAttempts = table.Column(type: "INTEGER", nullable: false), - LastFailedAt = table.Column(type: "TEXT", nullable: true), - LockedUntil = table.Column(type: "TEXT", nullable: true), - RequiresReauthentication = table.Column(type: "INTEGER", nullable: false), - ResetRequestedAt = table.Column(type: "TEXT", nullable: true), - ResetExpiresAt = table.Column(type: "TEXT", nullable: true), - ResetConsumedAt = table.Column(type: "TEXT", nullable: true), - ResetTokenHash = table.Column(type: "TEXT", maxLength: 512, nullable: true), - ResetAttempts = table.Column(type: "INTEGER", nullable: false), - SecurityVersion = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UAuth_Authentication", x => x.Id); - }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_Authentication_Tenant_LockedUntil", - table: "UAuth_Authentication", - columns: new[] { "Tenant", "LockedUntil" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_Authentication_Tenant_ResetRequestedAt", - table: "UAuth_Authentication", - columns: new[] { "Tenant", "ResetRequestedAt" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_Authentication_Tenant_UserKey", - table: "UAuth_Authentication", - columns: new[] { "Tenant", "UserKey" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_Authentication_Tenant_UserKey_Scope", - table: "UAuth_Authentication", - columns: new[] { "Tenant", "UserKey", "Scope" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_Authentication_Tenant_UserKey_Scope_CredentialType", - table: "UAuth_Authentication", - columns: new[] { "Tenant", "UserKey", "Scope", "CredentialType" }, - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "UAuth_Authentication"); - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/UAuthAuthenticationDbContextModelSnapshot.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/UAuthAuthenticationDbContextModelSnapshot.cs deleted file mode 100644 index acc4b9a5..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthenticationDb/UAuthAuthenticationDbContextModelSnapshot.cs +++ /dev/null @@ -1,92 +0,0 @@ -// -using System; -using CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthAuthenticationDb -{ - [DbContext(typeof(UAuthAuthenticationDbContext))] - partial class UAuthAuthenticationDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore.AuthenticationSecurityStateProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("CredentialType") - .HasColumnType("INTEGER"); - - b.Property("FailedAttempts") - .HasColumnType("INTEGER"); - - b.Property("LastFailedAt") - .HasColumnType("TEXT"); - - b.Property("LockedUntil") - .HasColumnType("TEXT"); - - b.Property("RequiresReauthentication") - .HasColumnType("INTEGER"); - - b.Property("ResetAttempts") - .HasColumnType("INTEGER"); - - b.Property("ResetConsumedAt") - .HasColumnType("TEXT"); - - b.Property("ResetExpiresAt") - .HasColumnType("TEXT"); - - b.Property("ResetRequestedAt") - .HasColumnType("TEXT"); - - b.Property("ResetTokenHash") - .HasMaxLength(512) - .HasColumnType("TEXT"); - - b.Property("Scope") - .HasColumnType("INTEGER"); - - b.Property("SecurityVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "LockedUntil"); - - b.HasIndex("Tenant", "ResetRequestedAt"); - - b.HasIndex("Tenant", "UserKey"); - - b.HasIndex("Tenant", "UserKey", "Scope"); - - b.HasIndex("Tenant", "UserKey", "Scope", "CredentialType") - .IsUnique(); - - b.ToTable("UAuth_Authentication", (string)null); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/20260326121602_InitAuthorization.Designer.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/20260326121602_InitAuthorization.Designer.cs deleted file mode 100644 index 8e1e0351..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/20260326121602_InitAuthorization.Designer.cs +++ /dev/null @@ -1,116 +0,0 @@ -// -using System; -using CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthAuthorizationDb -{ - [DbContext(typeof(UAuthAuthorizationDbContext))] - [Migration("20260326121602_InitAuthorization")] - partial class InitAuthorization - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.RolePermissionProjection", b => - { - b.Property("Tenant") - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("RoleId") - .HasColumnType("TEXT"); - - b.Property("Permission") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.HasKey("Tenant", "RoleId", "Permission"); - - b.HasIndex("Tenant", "Permission"); - - b.HasIndex("Tenant", "RoleId"); - - b.ToTable("UAuth_RolePermissions", (string)null); - }); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.RoleProjection", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("DeletedAt") - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("NormalizedName") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("UpdatedAt") - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "Id") - .IsUnique(); - - b.HasIndex("Tenant", "NormalizedName") - .IsUnique(); - - b.ToTable("UAuth_Roles", (string)null); - }); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.UserRoleProjection", b => - { - b.Property("Tenant") - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("UserKey") - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("RoleId") - .HasColumnType("TEXT"); - - b.Property("AssignedAt") - .HasColumnType("TEXT"); - - b.HasKey("Tenant", "UserKey", "RoleId"); - - b.HasIndex("Tenant", "RoleId"); - - b.HasIndex("Tenant", "UserKey"); - - b.ToTable("UAuth_UserRoles", (string)null); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/20260326121602_InitAuthorization.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/20260326121602_InitAuthorization.cs deleted file mode 100644 index 11cb3664..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/20260326121602_InitAuthorization.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthAuthorizationDb -{ - /// - public partial class InitAuthorization : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "UAuth_RolePermissions", - columns: table => new - { - Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), - RoleId = table.Column(type: "TEXT", nullable: false), - Permission = table.Column(type: "TEXT", maxLength: 256, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UAuth_RolePermissions", x => new { x.Tenant, x.RoleId, x.Permission }); - }); - - migrationBuilder.CreateTable( - name: "UAuth_Roles", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), - Name = table.Column(type: "TEXT", maxLength: 128, nullable: false), - NormalizedName = table.Column(type: "TEXT", maxLength: 128, nullable: false), - CreatedAt = table.Column(type: "TEXT", nullable: false), - UpdatedAt = table.Column(type: "TEXT", nullable: true), - DeletedAt = table.Column(type: "TEXT", nullable: true), - Version = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UAuth_Roles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "UAuth_UserRoles", - columns: table => new - { - Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), - UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), - RoleId = table.Column(type: "TEXT", nullable: false), - AssignedAt = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UAuth_UserRoles", x => new { x.Tenant, x.UserKey, x.RoleId }); - }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_RolePermissions_Tenant_Permission", - table: "UAuth_RolePermissions", - columns: new[] { "Tenant", "Permission" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_RolePermissions_Tenant_RoleId", - table: "UAuth_RolePermissions", - columns: new[] { "Tenant", "RoleId" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_Roles_Tenant_Id", - table: "UAuth_Roles", - columns: new[] { "Tenant", "Id" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_Roles_Tenant_NormalizedName", - table: "UAuth_Roles", - columns: new[] { "Tenant", "NormalizedName" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_UserRoles_Tenant_RoleId", - table: "UAuth_UserRoles", - columns: new[] { "Tenant", "RoleId" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_UserRoles_Tenant_UserKey", - table: "UAuth_UserRoles", - columns: new[] { "Tenant", "UserKey" }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "UAuth_RolePermissions"); - - migrationBuilder.DropTable( - name: "UAuth_Roles"); - - migrationBuilder.DropTable( - name: "UAuth_UserRoles"); - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/UAuthAuthorizationDbContextModelSnapshot.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/UAuthAuthorizationDbContextModelSnapshot.cs deleted file mode 100644 index 5487d6ed..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthAuthorizationDb/UAuthAuthorizationDbContextModelSnapshot.cs +++ /dev/null @@ -1,113 +0,0 @@ -// -using System; -using CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthAuthorizationDb -{ - [DbContext(typeof(UAuthAuthorizationDbContext))] - partial class UAuthAuthorizationDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.RolePermissionProjection", b => - { - b.Property("Tenant") - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("RoleId") - .HasColumnType("TEXT"); - - b.Property("Permission") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.HasKey("Tenant", "RoleId", "Permission"); - - b.HasIndex("Tenant", "Permission"); - - b.HasIndex("Tenant", "RoleId"); - - b.ToTable("UAuth_RolePermissions", (string)null); - }); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.RoleProjection", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("DeletedAt") - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("NormalizedName") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("UpdatedAt") - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "Id") - .IsUnique(); - - b.HasIndex("Tenant", "NormalizedName") - .IsUnique(); - - b.ToTable("UAuth_Roles", (string)null); - }); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.UserRoleProjection", b => - { - b.Property("Tenant") - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("UserKey") - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("RoleId") - .HasColumnType("TEXT"); - - b.Property("AssignedAt") - .HasColumnType("TEXT"); - - b.HasKey("Tenant", "UserKey", "RoleId"); - - b.HasIndex("Tenant", "RoleId"); - - b.HasIndex("Tenant", "UserKey"); - - b.ToTable("UAuth_UserRoles", (string)null); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/20260326121243_InitCredentials.Designer.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/20260326121243_InitCredentials.Designer.cs deleted file mode 100644 index 629e4bb8..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/20260326121243_InitCredentials.Designer.cs +++ /dev/null @@ -1,91 +0,0 @@ -// -using System; -using CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthCredentialDb -{ - [DbContext(typeof(UAuthCredentialDbContext))] - [Migration("20260326121243_InitCredentials")] - partial class InitCredentials - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore.PasswordCredentialProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("DeletedAt") - .HasColumnType("TEXT"); - - b.Property("ExpiresAt") - .HasColumnType("TEXT"); - - b.Property("LastUsedAt") - .HasColumnType("TEXT"); - - b.Property("RevokedAt") - .HasColumnType("TEXT"); - - b.Property("SecretHash") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("TEXT"); - - b.Property("SecurityStamp") - .HasColumnType("TEXT"); - - b.Property("Source") - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("UpdatedAt") - .HasColumnType("TEXT"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "ExpiresAt"); - - b.HasIndex("Tenant", "Id") - .IsUnique(); - - b.HasIndex("Tenant", "RevokedAt"); - - b.HasIndex("Tenant", "UserKey"); - - b.HasIndex("Tenant", "UserKey", "DeletedAt"); - - b.ToTable("UAuth_PasswordCredentials", (string)null); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/20260326121243_InitCredentials.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/20260326121243_InitCredentials.cs deleted file mode 100644 index a206e802..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/20260326121243_InitCredentials.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthCredentialDb -{ - /// - public partial class InitCredentials : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "UAuth_PasswordCredentials", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), - UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), - SecretHash = table.Column(type: "TEXT", maxLength: 512, nullable: false), - RevokedAt = table.Column(type: "TEXT", nullable: true), - ExpiresAt = table.Column(type: "TEXT", nullable: true), - SecurityStamp = table.Column(type: "TEXT", nullable: false), - LastUsedAt = table.Column(type: "TEXT", nullable: true), - Source = table.Column(type: "TEXT", maxLength: 128, nullable: true), - CreatedAt = table.Column(type: "TEXT", nullable: false), - UpdatedAt = table.Column(type: "TEXT", nullable: true), - DeletedAt = table.Column(type: "TEXT", nullable: true), - Version = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UAuth_PasswordCredentials", x => x.Id); - }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_PasswordCredentials_Tenant_ExpiresAt", - table: "UAuth_PasswordCredentials", - columns: new[] { "Tenant", "ExpiresAt" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_PasswordCredentials_Tenant_Id", - table: "UAuth_PasswordCredentials", - columns: new[] { "Tenant", "Id" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_PasswordCredentials_Tenant_RevokedAt", - table: "UAuth_PasswordCredentials", - columns: new[] { "Tenant", "RevokedAt" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_PasswordCredentials_Tenant_UserKey", - table: "UAuth_PasswordCredentials", - columns: new[] { "Tenant", "UserKey" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_PasswordCredentials_Tenant_UserKey_DeletedAt", - table: "UAuth_PasswordCredentials", - columns: new[] { "Tenant", "UserKey", "DeletedAt" }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "UAuth_PasswordCredentials"); - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/UAuthCredentialDbContextModelSnapshot.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/UAuthCredentialDbContextModelSnapshot.cs deleted file mode 100644 index 858dbb27..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthCredentialDb/UAuthCredentialDbContextModelSnapshot.cs +++ /dev/null @@ -1,88 +0,0 @@ -// -using System; -using CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthCredentialDb -{ - [DbContext(typeof(UAuthCredentialDbContext))] - partial class UAuthCredentialDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore.PasswordCredentialProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("DeletedAt") - .HasColumnType("TEXT"); - - b.Property("ExpiresAt") - .HasColumnType("TEXT"); - - b.Property("LastUsedAt") - .HasColumnType("TEXT"); - - b.Property("RevokedAt") - .HasColumnType("TEXT"); - - b.Property("SecretHash") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("TEXT"); - - b.Property("SecurityStamp") - .HasColumnType("TEXT"); - - b.Property("Source") - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("UpdatedAt") - .HasColumnType("TEXT"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "ExpiresAt"); - - b.HasIndex("Tenant", "Id") - .IsUnique(); - - b.HasIndex("Tenant", "RevokedAt"); - - b.HasIndex("Tenant", "UserKey"); - - b.HasIndex("Tenant", "UserKey", "DeletedAt"); - - b.ToTable("UAuth_PasswordCredentials", (string)null); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthDbContextModelSnapshot.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthDbContextModelSnapshot.cs new file mode 100644 index 00000000..211ef12e --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthDbContextModelSnapshot.cs @@ -0,0 +1,707 @@ +// +using System; +using CodeBeam.UltimateAuth.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations +{ + [DbContext(typeof(UAuthDbContext))] + partial class UAuthDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore.AuthenticationSecurityStateProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CredentialType") + .HasColumnType("INTEGER"); + + b.Property("FailedAttempts") + .HasColumnType("INTEGER"); + + b.Property("LastFailedAt") + .HasColumnType("TEXT"); + + b.Property("LockedUntil") + .HasColumnType("TEXT"); + + b.Property("RequiresReauthentication") + .HasColumnType("INTEGER"); + + b.Property("ResetAttempts") + .HasColumnType("INTEGER"); + + b.Property("ResetConsumedAt") + .HasColumnType("TEXT"); + + b.Property("ResetExpiresAt") + .HasColumnType("TEXT"); + + b.Property("ResetRequestedAt") + .HasColumnType("TEXT"); + + b.Property("ResetTokenHash") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Scope") + .HasColumnType("INTEGER"); + + b.Property("SecurityVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "LockedUntil"); + + b.HasIndex("Tenant", "ResetRequestedAt"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "UserKey", "Scope"); + + b.HasIndex("Tenant", "UserKey", "Scope", "CredentialType") + .IsUnique(); + + b.ToTable("UAuth_Authentication", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.RolePermissionProjection", b => + { + b.Property("Tenant") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.Property("Permission") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Tenant", "RoleId", "Permission"); + + b.HasIndex("Tenant", "Permission"); + + b.HasIndex("Tenant", "RoleId"); + + b.ToTable("UAuth_RolePermissions", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.RoleProjection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "Id") + .IsUnique(); + + b.HasIndex("Tenant", "NormalizedName") + .IsUnique(); + + b.ToTable("UAuth_Roles", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.UserRoleProjection", b => + { + b.Property("Tenant") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UserKey") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.Property("AssignedAt") + .HasColumnType("TEXT"); + + b.HasKey("Tenant", "UserKey", "RoleId"); + + b.HasIndex("Tenant", "RoleId"); + + b.HasIndex("Tenant", "UserKey"); + + b.ToTable("UAuth_UserRoles", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore.PasswordCredentialProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("LastUsedAt") + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("SecretHash") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "ExpiresAt"); + + b.HasIndex("Tenant", "Id") + .IsUnique(); + + b.HasIndex("Tenant", "RevokedAt"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "UserKey", "DeletedAt"); + + b.ToTable("UAuth_PasswordCredentials", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AbsoluteExpiresAt") + .HasColumnType("TEXT"); + + b.Property("ActiveSessionId") + .HasColumnType("TEXT"); + + b.Property("ChainId") + .HasColumnType("TEXT"); + + b.Property("ClaimsSnapshot") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Device") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("LastSeenAt") + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("RootId") + .HasColumnType("TEXT"); + + b.Property("RotationCount") + .HasColumnType("INTEGER"); + + b.Property("SecurityVersionAtCreation") + .HasColumnType("INTEGER"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("TouchCount") + .HasColumnType("INTEGER"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "ChainId") + .IsUnique(); + + b.HasIndex("Tenant", "RootId"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "UserKey", "DeviceId"); + + b.ToTable("UAuth_SessionChains", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChainId") + .HasColumnType("TEXT"); + + b.Property("Claims") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Device") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("SecurityVersionAtCreation") + .HasColumnType("INTEGER"); + + b.Property("SessionId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "ChainId"); + + b.HasIndex("Tenant", "ExpiresAt"); + + b.HasIndex("Tenant", "RevokedAt"); + + b.HasIndex("Tenant", "SessionId") + .IsUnique(); + + b.HasIndex("Tenant", "ChainId", "RevokedAt"); + + b.HasIndex("Tenant", "UserKey", "RevokedAt"); + + b.ToTable("UAuth_Sessions", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionRootProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("RootId") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("SecurityVersion") + .HasColumnType("INTEGER"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "RootId") + .IsUnique(); + + b.HasIndex("Tenant", "UserKey") + .IsUnique(); + + b.ToTable("UAuth_SessionRoots", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore.RefreshTokenProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChainId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("ReplacedByTokenHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("TokenHash") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("TokenId") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "ChainId"); + + b.HasIndex("Tenant", "ExpiresAt"); + + b.HasIndex("Tenant", "ReplacedByTokenHash"); + + b.HasIndex("Tenant", "SessionId"); + + b.HasIndex("Tenant", "TokenHash") + .IsUnique(); + + b.HasIndex("Tenant", "TokenId"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "ExpiresAt", "RevokedAt"); + + b.HasIndex("Tenant", "TokenHash", "RevokedAt"); + + b.ToTable("UAuth_RefreshTokens", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserIdentifierProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("IsPrimary") + .HasColumnType("INTEGER"); + + b.Property("NormalizedValue") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("VerifiedAt") + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "NormalizedValue"); + + b.HasIndex("Tenant", "UserKey"); + + b.HasIndex("Tenant", "Type", "NormalizedValue") + .IsUnique(); + + b.HasIndex("Tenant", "UserKey", "IsPrimary"); + + b.HasIndex("Tenant", "UserKey", "Type", "IsPrimary"); + + b.ToTable("UAuth_UserIdentifiers", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserLifecycleProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("SecurityVersion") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "UserKey") + .IsUnique(); + + b.ToTable("UAuth_UserLifecycles", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserProfileProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Bio") + .HasColumnType("TEXT"); + + b.Property("BirthDate") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Culture") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("FirstName") + .HasColumnType("TEXT"); + + b.Property("Gender") + .HasColumnType("TEXT"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LastName") + .HasColumnType("TEXT"); + + b.Property("Metadata") + .HasColumnType("TEXT"); + + b.Property("Tenant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("TimeZone") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "UserKey"); + + b.ToTable("UAuth_UserProfiles", (string)null); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", b => + { + b.HasOne("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionRootProjection", null) + .WithMany() + .HasForeignKey("Tenant", "RootId") + .HasPrincipalKey("Tenant", "RootId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionProjection", b => + { + b.HasOne("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", null) + .WithMany() + .HasForeignKey("Tenant", "ChainId") + .HasPrincipalKey("Tenant", "ChainId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthSessionDbContextModelSnapshot.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthSessionDbContextModelSnapshot.cs deleted file mode 100644 index 7b2a1b2c..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthSessionDbContextModelSnapshot.cs +++ /dev/null @@ -1,235 +0,0 @@ -// -using System; -using CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations -{ - [DbContext(typeof(UAuthSessionDbContext))] - partial class UAuthSessionDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AbsoluteExpiresAt") - .HasColumnType("TEXT"); - - b.Property("ActiveSessionId") - .HasColumnType("TEXT"); - - b.Property("ChainId") - .HasColumnType("TEXT"); - - b.Property("ClaimsSnapshot") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("Device") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("DeviceId") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("TEXT"); - - b.Property("LastSeenAt") - .HasColumnType("TEXT"); - - b.Property("RevokedAt") - .HasColumnType("TEXT"); - - b.Property("RootId") - .HasColumnType("TEXT"); - - b.Property("RotationCount") - .HasColumnType("INTEGER"); - - b.Property("SecurityVersionAtCreation") - .HasColumnType("INTEGER"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("TouchCount") - .HasColumnType("INTEGER"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "ChainId") - .IsUnique(); - - b.HasIndex("Tenant", "RootId"); - - b.HasIndex("Tenant", "UserKey"); - - b.HasIndex("Tenant", "UserKey", "DeviceId"); - - b.ToTable("UAuth_SessionChains", (string)null); - }); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ChainId") - .HasColumnType("TEXT"); - - b.Property("Claims") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("Device") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("ExpiresAt") - .HasColumnType("TEXT"); - - b.Property("Metadata") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("RevokedAt") - .HasColumnType("TEXT"); - - b.Property("SecurityVersionAtCreation") - .HasColumnType("INTEGER"); - - b.Property("SessionId") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "ChainId"); - - b.HasIndex("Tenant", "ExpiresAt"); - - b.HasIndex("Tenant", "RevokedAt"); - - b.HasIndex("Tenant", "SessionId") - .IsUnique(); - - b.HasIndex("Tenant", "ChainId", "RevokedAt"); - - b.HasIndex("Tenant", "UserKey", "RevokedAt"); - - b.ToTable("UAuth_Sessions", (string)null); - }); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionRootProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("RevokedAt") - .HasColumnType("TEXT"); - - b.Property("RootId") - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("SecurityVersion") - .HasColumnType("INTEGER"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("UpdatedAt") - .HasColumnType("TEXT"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "RootId") - .IsUnique(); - - b.HasIndex("Tenant", "UserKey") - .IsUnique(); - - b.ToTable("UAuth_SessionRoots", (string)null); - }); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", b => - { - b.HasOne("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionRootProjection", null) - .WithMany() - .HasForeignKey("Tenant", "RootId") - .HasPrincipalKey("Tenant", "RootId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - }); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionProjection", b => - { - b.HasOne("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", null) - .WithMany() - .HasForeignKey("Tenant", "ChainId") - .HasPrincipalKey("Tenant", "ChainId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/20260326120856_InitTokens.Designer.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/20260326120856_InitTokens.Designer.cs deleted file mode 100644 index 95f4fed6..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/20260326120856_InitTokens.Designer.cs +++ /dev/null @@ -1,96 +0,0 @@ -// -using System; -using CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthTokenDb -{ - [DbContext(typeof(UAuthTokenDbContext))] - [Migration("20260326120856_InitTokens")] - partial class InitTokens - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore.RefreshTokenProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ChainId") - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("ExpiresAt") - .HasColumnType("TEXT"); - - b.Property("ReplacedByTokenHash") - .HasColumnType("TEXT"); - - b.Property("RevokedAt") - .HasColumnType("TEXT"); - - b.Property("SessionId") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("TokenId") - .HasColumnType("TEXT"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "ChainId"); - - b.HasIndex("Tenant", "ExpiresAt"); - - b.HasIndex("Tenant", "ReplacedByTokenHash"); - - b.HasIndex("Tenant", "SessionId"); - - b.HasIndex("Tenant", "TokenHash") - .IsUnique(); - - b.HasIndex("Tenant", "TokenId"); - - b.HasIndex("Tenant", "UserKey"); - - b.HasIndex("Tenant", "ExpiresAt", "RevokedAt"); - - b.HasIndex("Tenant", "TokenHash", "RevokedAt"); - - b.ToTable("UAuth_RefreshTokens", (string)null); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/20260326120856_InitTokens.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/20260326120856_InitTokens.cs deleted file mode 100644 index 474cec55..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/20260326120856_InitTokens.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthTokenDb -{ - /// - public partial class InitTokens : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "UAuth_RefreshTokens", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - TokenId = table.Column(type: "TEXT", nullable: false), - Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), - TokenHash = table.Column(type: "TEXT", maxLength: 128, nullable: false), - UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), - SessionId = table.Column(type: "TEXT", nullable: false), - ChainId = table.Column(type: "TEXT", nullable: true), - ReplacedByTokenHash = table.Column(type: "TEXT", nullable: true), - CreatedAt = table.Column(type: "TEXT", nullable: false), - ExpiresAt = table.Column(type: "TEXT", nullable: false), - RevokedAt = table.Column(type: "TEXT", nullable: true), - Version = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UAuth_RefreshTokens", x => x.Id); - }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_RefreshTokens_Tenant_ChainId", - table: "UAuth_RefreshTokens", - columns: new[] { "Tenant", "ChainId" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_RefreshTokens_Tenant_ExpiresAt", - table: "UAuth_RefreshTokens", - columns: new[] { "Tenant", "ExpiresAt" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_RefreshTokens_Tenant_ExpiresAt_RevokedAt", - table: "UAuth_RefreshTokens", - columns: new[] { "Tenant", "ExpiresAt", "RevokedAt" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_RefreshTokens_Tenant_ReplacedByTokenHash", - table: "UAuth_RefreshTokens", - columns: new[] { "Tenant", "ReplacedByTokenHash" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_RefreshTokens_Tenant_SessionId", - table: "UAuth_RefreshTokens", - columns: new[] { "Tenant", "SessionId" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_RefreshTokens_Tenant_TokenHash", - table: "UAuth_RefreshTokens", - columns: new[] { "Tenant", "TokenHash" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_RefreshTokens_Tenant_TokenHash_RevokedAt", - table: "UAuth_RefreshTokens", - columns: new[] { "Tenant", "TokenHash", "RevokedAt" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_RefreshTokens_Tenant_TokenId", - table: "UAuth_RefreshTokens", - columns: new[] { "Tenant", "TokenId" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_RefreshTokens_Tenant_UserKey", - table: "UAuth_RefreshTokens", - columns: new[] { "Tenant", "UserKey" }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "UAuth_RefreshTokens"); - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/UAuthTokenDbContextModelSnapshot.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/UAuthTokenDbContextModelSnapshot.cs deleted file mode 100644 index 1cb54a1b..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthTokenDb/UAuthTokenDbContextModelSnapshot.cs +++ /dev/null @@ -1,93 +0,0 @@ -// -using System; -using CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthTokenDb -{ - [DbContext(typeof(UAuthTokenDbContext))] - partial class UAuthTokenDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore.RefreshTokenProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ChainId") - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("ExpiresAt") - .HasColumnType("TEXT"); - - b.Property("ReplacedByTokenHash") - .HasColumnType("TEXT"); - - b.Property("RevokedAt") - .HasColumnType("TEXT"); - - b.Property("SessionId") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("TokenId") - .HasColumnType("TEXT"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "ChainId"); - - b.HasIndex("Tenant", "ExpiresAt"); - - b.HasIndex("Tenant", "ReplacedByTokenHash"); - - b.HasIndex("Tenant", "SessionId"); - - b.HasIndex("Tenant", "TokenHash") - .IsUnique(); - - b.HasIndex("Tenant", "TokenId"); - - b.HasIndex("Tenant", "UserKey"); - - b.HasIndex("Tenant", "ExpiresAt", "RevokedAt"); - - b.HasIndex("Tenant", "TokenHash", "RevokedAt"); - - b.ToTable("UAuth_RefreshTokens", (string)null); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/20260326121123_InitUsers.Designer.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/20260326121123_InitUsers.Designer.cs deleted file mode 100644 index cbef033f..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/20260326121123_InitUsers.Designer.cs +++ /dev/null @@ -1,198 +0,0 @@ -// -using System; -using CodeBeam.UltimateAuth.Users.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthUserDb -{ - [DbContext(typeof(UAuthUserDbContext))] - [Migration("20260326121123_InitUsers")] - partial class InitUsers - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserIdentifierProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("DeletedAt") - .HasColumnType("TEXT"); - - b.Property("IsPrimary") - .HasColumnType("INTEGER"); - - b.Property("NormalizedValue") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UpdatedAt") - .HasColumnType("TEXT"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("VerifiedAt") - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "NormalizedValue"); - - b.HasIndex("Tenant", "UserKey"); - - b.HasIndex("Tenant", "Type", "NormalizedValue") - .IsUnique(); - - b.HasIndex("Tenant", "UserKey", "IsPrimary"); - - b.HasIndex("Tenant", "UserKey", "Type", "IsPrimary"); - - b.ToTable("UAuth_UserIdentifiers", (string)null); - }); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserLifecycleProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("DeletedAt") - .HasColumnType("TEXT"); - - b.Property("SecurityVersion") - .HasColumnType("INTEGER"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("UpdatedAt") - .HasColumnType("TEXT"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "UserKey") - .IsUnique(); - - b.ToTable("UAuth_UserLifecycles", (string)null); - }); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserProfileProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Bio") - .HasColumnType("TEXT"); - - b.Property("BirthDate") - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("Culture") - .HasColumnType("TEXT"); - - b.Property("DeletedAt") - .HasColumnType("TEXT"); - - b.Property("DisplayName") - .HasColumnType("TEXT"); - - b.Property("FirstName") - .HasColumnType("TEXT"); - - b.Property("Gender") - .HasColumnType("TEXT"); - - b.Property("Language") - .HasColumnType("TEXT"); - - b.Property("LastName") - .HasColumnType("TEXT"); - - b.Property("Metadata") - .HasColumnType("TEXT"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("TimeZone") - .HasColumnType("TEXT"); - - b.Property("UpdatedAt") - .HasColumnType("TEXT"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "UserKey"); - - b.ToTable("UAuth_UserProfiles", (string)null); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/20260326121123_InitUsers.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/20260326121123_InitUsers.cs deleted file mode 100644 index 4e74b680..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/20260326121123_InitUsers.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthUserDb -{ - /// - public partial class InitUsers : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "UAuth_UserIdentifiers", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), - UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), - Type = table.Column(type: "INTEGER", nullable: false), - Value = table.Column(type: "TEXT", maxLength: 256, nullable: false), - NormalizedValue = table.Column(type: "TEXT", maxLength: 256, nullable: false), - IsPrimary = table.Column(type: "INTEGER", nullable: false), - CreatedAt = table.Column(type: "TEXT", nullable: false), - VerifiedAt = table.Column(type: "TEXT", nullable: true), - UpdatedAt = table.Column(type: "TEXT", nullable: true), - DeletedAt = table.Column(type: "TEXT", nullable: true), - Version = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UAuth_UserIdentifiers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "UAuth_UserLifecycles", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), - UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), - Status = table.Column(type: "INTEGER", nullable: false), - SecurityVersion = table.Column(type: "INTEGER", nullable: false), - CreatedAt = table.Column(type: "TEXT", nullable: false), - UpdatedAt = table.Column(type: "TEXT", nullable: true), - DeletedAt = table.Column(type: "TEXT", nullable: true), - Version = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UAuth_UserLifecycles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "UAuth_UserProfiles", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Tenant = table.Column(type: "TEXT", maxLength: 128, nullable: false), - UserKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), - FirstName = table.Column(type: "TEXT", nullable: true), - LastName = table.Column(type: "TEXT", nullable: true), - DisplayName = table.Column(type: "TEXT", nullable: true), - BirthDate = table.Column(type: "TEXT", nullable: true), - Gender = table.Column(type: "TEXT", nullable: true), - Bio = table.Column(type: "TEXT", nullable: true), - Language = table.Column(type: "TEXT", nullable: true), - TimeZone = table.Column(type: "TEXT", nullable: true), - Culture = table.Column(type: "TEXT", nullable: true), - Metadata = table.Column(type: "TEXT", nullable: true), - CreatedAt = table.Column(type: "TEXT", nullable: false), - UpdatedAt = table.Column(type: "TEXT", nullable: true), - DeletedAt = table.Column(type: "TEXT", nullable: true), - Version = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UAuth_UserProfiles", x => x.Id); - }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_UserIdentifiers_Tenant_NormalizedValue", - table: "UAuth_UserIdentifiers", - columns: new[] { "Tenant", "NormalizedValue" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_UserIdentifiers_Tenant_Type_NormalizedValue", - table: "UAuth_UserIdentifiers", - columns: new[] { "Tenant", "Type", "NormalizedValue" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_UserIdentifiers_Tenant_UserKey", - table: "UAuth_UserIdentifiers", - columns: new[] { "Tenant", "UserKey" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_UserIdentifiers_Tenant_UserKey_IsPrimary", - table: "UAuth_UserIdentifiers", - columns: new[] { "Tenant", "UserKey", "IsPrimary" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_UserIdentifiers_Tenant_UserKey_Type_IsPrimary", - table: "UAuth_UserIdentifiers", - columns: new[] { "Tenant", "UserKey", "Type", "IsPrimary" }); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_UserLifecycles_Tenant_UserKey", - table: "UAuth_UserLifecycles", - columns: new[] { "Tenant", "UserKey" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_UAuth_UserProfiles_Tenant_UserKey", - table: "UAuth_UserProfiles", - columns: new[] { "Tenant", "UserKey" }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "UAuth_UserIdentifiers"); - - migrationBuilder.DropTable( - name: "UAuth_UserLifecycles"); - - migrationBuilder.DropTable( - name: "UAuth_UserProfiles"); - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/UAuthUserDbContextModelSnapshot.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/UAuthUserDbContextModelSnapshot.cs deleted file mode 100644 index cfe195d3..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Migrations/UAuthUserDb/UAuthUserDbContextModelSnapshot.cs +++ /dev/null @@ -1,195 +0,0 @@ -// -using System; -using CodeBeam.UltimateAuth.Users.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Migrations.UAuthUserDb -{ - [DbContext(typeof(UAuthUserDbContext))] - partial class UAuthUserDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserIdentifierProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("DeletedAt") - .HasColumnType("TEXT"); - - b.Property("IsPrimary") - .HasColumnType("INTEGER"); - - b.Property("NormalizedValue") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UpdatedAt") - .HasColumnType("TEXT"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("VerifiedAt") - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "NormalizedValue"); - - b.HasIndex("Tenant", "UserKey"); - - b.HasIndex("Tenant", "Type", "NormalizedValue") - .IsUnique(); - - b.HasIndex("Tenant", "UserKey", "IsPrimary"); - - b.HasIndex("Tenant", "UserKey", "Type", "IsPrimary"); - - b.ToTable("UAuth_UserIdentifiers", (string)null); - }); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserLifecycleProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("DeletedAt") - .HasColumnType("TEXT"); - - b.Property("SecurityVersion") - .HasColumnType("INTEGER"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("UpdatedAt") - .HasColumnType("TEXT"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "UserKey") - .IsUnique(); - - b.ToTable("UAuth_UserLifecycles", (string)null); - }); - - modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserProfileProjection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Bio") - .HasColumnType("TEXT"); - - b.Property("BirthDate") - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("Culture") - .HasColumnType("TEXT"); - - b.Property("DeletedAt") - .HasColumnType("TEXT"); - - b.Property("DisplayName") - .HasColumnType("TEXT"); - - b.Property("FirstName") - .HasColumnType("TEXT"); - - b.Property("Gender") - .HasColumnType("TEXT"); - - b.Property("Language") - .HasColumnType("TEXT"); - - b.Property("LastName") - .HasColumnType("TEXT"); - - b.Property("Metadata") - .HasColumnType("TEXT"); - - b.Property("Tenant") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("TimeZone") - .HasColumnType("TEXT"); - - b.Property("UpdatedAt") - .HasColumnType("TEXT"); - - b.Property("UserKey") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("Version") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Tenant", "UserKey"); - - b.ToTable("UAuth_UserProfiles", (string)null); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Program.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Program.cs index 0cf248cd..0a00a45b 100644 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Program.cs +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Program.cs @@ -6,6 +6,7 @@ using CodeBeam.UltimateAuth.EntityFrameworkCore; using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components; using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Infrastructure; +using CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore; using CodeBeam.UltimateAuth.Sample.Seed.Extensions; using CodeBeam.UltimateAuth.Server.Extensions; using Microsoft.AspNetCore.HttpOverrides; @@ -86,9 +87,17 @@ app.MapOpenApi(); app.MapScalarApiReference(); - using var scope = app.Services.CreateScope(); - var seedRunner = scope.ServiceProvider.GetRequiredService(); - await seedRunner.RunAsync(null); + //using var scope = app.Services.CreateScope(); + //var seedRunner = scope.ServiceProvider.GetRequiredService(); + //await seedRunner.RunAsync(null); + + using (var scope = app.Services.CreateScope()) + { + await UAuthDbInitializer.InitializeAsync(app.Services, reset: true); + + var seedRunner = scope.ServiceProvider.GetRequiredService(); + await seedRunner.RunAsync(null); + } } app.UseForwardedHeaders(); diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Seed/UAuthDbInitializer.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Seed/UAuthDbInitializer.cs new file mode 100644 index 00000000..4b4e23c4 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Seed/UAuthDbInitializer.cs @@ -0,0 +1,46 @@ +using CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore; +using CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore; +using CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore; +using CodeBeam.UltimateAuth.EntityFrameworkCore; +using CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; +using CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore; +using CodeBeam.UltimateAuth.Users.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore; + +public static class UAuthDbInitializer +{ + public static async Task InitializeAsync(IServiceProvider services, bool reset = false) + { + using var scope = services.CreateScope(); + var sp = scope.ServiceProvider; + + var bundleDb = sp.GetService(); + + if (bundleDb != null) + { + if (reset) + await bundleDb.Database.EnsureDeletedAsync(); + + await bundleDb.Database.MigrateAsync(); + return; + } + + var contexts = new DbContext[] + { + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService() + }; + + if (reset) + await contexts[0].Database.EnsureDeletedAsync(); + + foreach (var db in contexts) + await db.Database.MigrateAsync(); + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/uauth.db b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/uauth.db index 13c7c594e3a35facc7e77d8d28c4afbce5b402bd..4e86411b5803e34b1e4767ce981907694f15c1ed 100644 GIT binary patch delta 53 zcmZoTAl#s!5FF@}S(2)dmS2>cSfXIeAi%)H#NeQyz`(%32*Q&E1?*WF8T8sTw>2=% G;RgU@g$s}X literal 319488 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 O2TVa2|36FP|Njp@n|^cv diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/uauth.db-shm b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/uauth.db-shm new file mode 100644 index 0000000000000000000000000000000000000000..ad804a2a9e406286bc8a42c686f5be34d684450e GIT binary patch literal 32768 zcmeI5S94ZX6owyAKq)5lNDVD?yMojZLJc;o*t;P1iV8MR5exR-z=C4Ih6vbD?7d@w z3vQX=0)K$MJHv4@4$9tMe2X(@t(j+%FK3SoBdU6989Qgq7jsD(9bENcFo%P zf6T1gJZtr)jWd?5-?C}Ny6H=MFQ}?W?AN2}D?Inl2DSCifB#T6jkgkh-%0E&8vfoy zpPPziVi&QiXf9famZFu|O_Ygp(OR?-ZAClLUUU#0MTOX1bP}CK7tvJ&58Xwj=plNF zUZS_?BdSDS(NFXj1H?cvNCcC6h&{z#VsEjJ*jMZ)_7?|;A!4XFPz)2p#RxG{j1r?o zwHPC6#8^=)#)_>X$87z3R-g7ae<&3nf473&SBXrwbH!s!7!euc9nykZP=;V3SQX*BPdz}I}w z2D3RD>y(wLX6Y%x;nWVV7z157nu}r@&3h(t9H%ma%b3GFgK3)7pCM!roE#a>5AupJ z(2XZq!t*h$#yyibp3^v+%ek5x3}$Ise|nZdaB^fgIm;`?KzE+vSzd@~HSU?r37pPM zW^oNS8qCt1WxT=L{J@U}=_!%nTr#g11C^}d=a@$Go;ptC49?*SuH`0!Xtjxvg4>g{$ocoO@h>ai4V=RtsE_7cNudyl@ z1`gvHmhzI>tgU&#EITcD1Z&f7C~S9MUT1Y-SD&)*Hay7VDL0t)j^snL?0g0HV>xf- zYh;&9H3X*BN{$CrG^?`CuS1yK64^Z)<= literal 0 HcmV?d00001 diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/uauth.db-wal b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/uauth.db-wal new file mode 100644 index 0000000000000000000000000000000000000000..2926ac81f2b39f1709a5f62111d6b78f59cc1ff9 GIT binary patch literal 914672 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 literal 0 HcmV?d00001 diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Seed/UserSeedContributor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Seed/UserSeedContributor.cs deleted file mode 100644 index 72a60172..00000000 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Seed/UserSeedContributor.cs +++ /dev/null @@ -1,102 +0,0 @@ -using CodeBeam.UltimateAuth.Core.Abstractions; -using CodeBeam.UltimateAuth.Core.Domain; -using CodeBeam.UltimateAuth.Core.MultiTenancy; -using CodeBeam.UltimateAuth.InMemory; -using CodeBeam.UltimateAuth.Server.Infrastructure; -using CodeBeam.UltimateAuth.Users.Contracts; -using CodeBeam.UltimateAuth.Users.Reference; - -namespace CodeBeam.UltimateAuth.Sample.Seed; - -public sealed class UserSeedContributor : ISeedContributor -{ - public int Order => 0; - - private readonly IUserLifecycleStoreFactory _lifecycleFactory; - private readonly IUserIdentifierStoreFactory _identifierFactory; - private readonly IUserProfileStoreFactory _profileFactory; - private readonly IInMemoryUserIdProvider _ids; - private readonly IIdentifierNormalizer _identifierNormalizer; - private readonly IClock _clock; - - public UserSeedContributor( - IUserLifecycleStoreFactory lifecycleFactory, - IUserProfileStoreFactory profileFactory, - IUserIdentifierStoreFactory identifierFactory, - IInMemoryUserIdProvider ids, - IIdentifierNormalizer identifierNormalizer, - IClock clock) - { - _lifecycleFactory = lifecycleFactory; - _identifierFactory = identifierFactory; - _profileFactory = profileFactory; - _ids = ids; - _identifierNormalizer = identifierNormalizer; - _clock = clock; - } - - public async Task SeedAsync(TenantKey tenant, CancellationToken ct = default) - { - await SeedUserAsync(tenant, _ids.GetAdminUserId(), "Administrator", "admin", "admin@ultimateauth.com", "1234567890", ct); - await SeedUserAsync(tenant, _ids.GetUserUserId(), "Standard User", "user", "user@ultimateauth.com", "9876543210", ct); - } - - private async Task SeedUserAsync(TenantKey tenant, UserKey userKey, string displayName, string username, string email, string phone, CancellationToken ct) - { - var now = _clock.UtcNow; - - var lifecycleStore = _lifecycleFactory.Create(tenant); - var profileStore = _profileFactory.Create(tenant); - var identifierStore = _identifierFactory.Create(tenant); - - var lifecycleKey = new UserLifecycleKey(tenant, userKey); - - var exists = await lifecycleStore.ExistsAsync(lifecycleKey, ct); - - if (!exists) - { - await lifecycleStore.AddAsync( - UserLifecycle.Create(tenant, userKey, now), - ct); - } - - var profileKey = new UserProfileKey(tenant, userKey); - if (!await profileStore.ExistsAsync(profileKey, ct)) - { - await profileStore.AddAsync( - UserProfile.Create(Guid.NewGuid(), tenant, userKey, now, displayName: displayName), - ct); - } - - async Task EnsureIdentifier( - UserIdentifierType type, - string value, - bool isPrimary) - { - var normalized = _identifierNormalizer - .Normalize(type, value).Normalized; - - var existing = await identifierStore.GetAsync(type, normalized, ct); - - if (existing is not null) - return; - - await identifierStore.AddAsync( - UserIdentifier.Create( - Guid.NewGuid(), - tenant, - userKey, - type, - value, - normalized, - now, - isPrimary, - now), - ct); - } - - await EnsureIdentifier(UserIdentifierType.Username, username, true); - await EnsureIdentifier(UserIdentifierType.Email, email, true); - await EnsureIdentifier(UserIdentifierType.Phone, phone, true); - } -} diff --git a/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Data/UAuthAuthenticationDbContext.cs b/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Data/UAuthAuthenticationDbContext.cs index d08975b7..c7c19cc1 100644 --- a/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Data/UAuthAuthenticationDbContext.cs +++ b/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Data/UAuthAuthenticationDbContext.cs @@ -1,6 +1,4 @@ -using CodeBeam.UltimateAuth.Core.Domain; -using CodeBeam.UltimateAuth.Core.MultiTenancy; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; namespace CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore; @@ -14,65 +12,8 @@ public UAuthAuthenticationDbContext(DbContextOptions(e => - { - e.ToTable("UAuth_Authentication"); - e.HasKey(x => x.Id); - - e.Property(x => x.SecurityVersion).IsConcurrencyToken(); - - e.Property(x => x.Tenant) - .HasConversion( - v => v.Value, - v => TenantKey.FromInternal(v)) - .HasMaxLength(128) - .IsRequired(); - - e.Property(x => x.UserKey) - .HasConversion( - v => v.Value, - v => UserKey.FromString(v)) - .HasMaxLength(128) - .IsRequired(); - - e.Property(x => x.Scope) - .IsRequired(); - - e.Property(x => x.CredentialType); - - e.Property(x => x.FailedAttempts) - .IsRequired(); - - e.Property(x => x.LastFailedAt); - - e.Property(x => x.LockedUntil); - - e.Property(x => x.RequiresReauthentication) - .IsRequired(); - - e.Property(x => x.ResetRequestedAt); - e.Property(x => x.ResetExpiresAt); - e.Property(x => x.ResetConsumedAt); - - e.Property(x => x.ResetTokenHash) - .HasMaxLength(512); - - e.Property(x => x.ResetAttempts) - .IsRequired(); - - e.HasIndex(x => new { x.Tenant, x.UserKey, x.Scope, x.CredentialType }).IsUnique(); - e.HasIndex(x => new { x.Tenant, x.UserKey }); - e.HasIndex(x => new { x.Tenant, x.LockedUntil }); - e.HasIndex(x => new { x.Tenant, x.ResetRequestedAt }); - e.HasIndex(x => new { x.Tenant, x.UserKey, x.Scope }); - - }); + UAuthAuthenticationModelBuilder.Configure(modelBuilder); } } diff --git a/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Data/UAuthAuthenticationModelBuilder.cs b/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Data/UAuthAuthenticationModelBuilder.cs new file mode 100644 index 00000000..4689db06 --- /dev/null +++ b/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Data/UAuthAuthenticationModelBuilder.cs @@ -0,0 +1,75 @@ +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.MultiTenancy; +using CodeBeam.UltimateAuth.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore; + +public static class UAuthAuthenticationModelBuilder +{ + public static void Configure(ModelBuilder b) + { + ConfigureAuthenticationSecurityState(b); + } + + private static void ConfigureAuthenticationSecurityState(ModelBuilder b) + { + b.Entity(e => + { + e.ToTable("UAuth_Authentication"); + + e.HasKey(x => x.Id); + + e.Property(x => x.SecurityVersion) + .IsConcurrencyToken(); + + e.Property(x => x.Tenant) + .HasConversion(v => v.Value, v => TenantKey.FromInternal(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.UserKey) + .HasConversion(v => v.Value, v => UserKey.FromString(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.Scope) + .IsRequired(); + + e.Property(x => x.CredentialType); + + e.Property(x => x.FailedAttempts) + .IsRequired(); + + e.Property(x => x.LastFailedAt) + .HasNullableUtcDateTimeOffsetConverter(); + + e.Property(x => x.LockedUntil) + .HasNullableUtcDateTimeOffsetConverter(); + + e.Property(x => x.RequiresReauthentication) + .IsRequired(); + + e.Property(x => x.ResetRequestedAt) + .HasNullableUtcDateTimeOffsetConverter(); + + e.Property(x => x.ResetExpiresAt) + .HasNullableUtcDateTimeOffsetConverter(); + + e.Property(x => x.ResetConsumedAt) + .HasNullableUtcDateTimeOffsetConverter(); + + e.Property(x => x.ResetTokenHash) + .HasMaxLength(512); + + e.Property(x => x.ResetAttempts) + .IsRequired(); + + e.HasIndex(x => new { x.Tenant, x.UserKey, x.Scope, x.CredentialType }).IsUnique(); + e.HasIndex(x => new { x.Tenant, x.UserKey }); + e.HasIndex(x => new { x.Tenant, x.LockedUntil }); + e.HasIndex(x => new { x.Tenant, x.ResetRequestedAt }); + e.HasIndex(x => new { x.Tenant, x.UserKey, x.Scope }); + }); + } +} \ No newline at end of file diff --git a/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs b/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs index 956212fa..1a9db677 100644 --- a/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs +++ b/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs @@ -6,10 +6,14 @@ namespace CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore.Extensions; public static class ServiceCollectionExtensions { - public static IServiceCollection AddUltimateAuthAuthenticationEntityFrameworkCore(this IServiceCollection services, Action configureDb) + public static IServiceCollection AddUltimateAuthAuthenticationEntityFrameworkCore(this IServiceCollection services, Action? configureDb = null) where TDbContext : DbContext { - services.AddDbContext(configureDb); - services.AddScoped(); + if (configureDb != null) + { + services.AddDbContext(configureDb); + } + + services.AddScoped>(); return services; } } diff --git a/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Stores/EfCoreAuthenticationSecurityStateStore.cs b/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Stores/EfCoreAuthenticationSecurityStateStore.cs index ac247165..fe12bfcd 100644 --- a/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Stores/EfCoreAuthenticationSecurityStateStore.cs +++ b/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Stores/EfCoreAuthenticationSecurityStateStore.cs @@ -7,20 +7,22 @@ namespace CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore; -internal sealed class EfCoreAuthenticationSecurityStateStore : IAuthenticationSecurityStateStore +internal sealed class EfCoreAuthenticationSecurityStateStore : IAuthenticationSecurityStateStore where TDbContext : DbContext { - private readonly UAuthAuthenticationDbContext _db; + private readonly TDbContext _db; private readonly TenantKey _tenant; - public EfCoreAuthenticationSecurityStateStore(UAuthAuthenticationDbContext db, TenantContext tenant) + public EfCoreAuthenticationSecurityStateStore(TDbContext db, TenantContext tenant) { _db = db; _tenant = tenant.Tenant; } + private DbSet DbSet => _db.Set(); + public async Task GetAsync(UserKey userKey, AuthenticationSecurityScope scope, CredentialType? credentialType, CancellationToken ct = default) { - var entity = await _db.AuthenticationSecurityStates + var entity = await DbSet .AsNoTracking() .SingleOrDefaultAsync(x => x.Tenant == _tenant && @@ -38,14 +40,14 @@ public async Task AddAsync(AuthenticationSecurityState state, CancellationToken { var entity = AuthenticationSecurityStateMapper.ToProjection(state); - _db.AuthenticationSecurityStates.Add(entity); + DbSet.Add(entity); await _db.SaveChangesAsync(ct); } public async Task UpdateAsync(AuthenticationSecurityState state, long expectedVersion, CancellationToken ct = default) { - var entity = await _db.AuthenticationSecurityStates + var entity = await DbSet .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.Id == state.Id, @@ -64,7 +66,7 @@ public async Task UpdateAsync(AuthenticationSecurityState state, long expectedVe public async Task DeleteAsync(UserKey userKey, AuthenticationSecurityScope scope, CredentialType? credentialType, CancellationToken ct = default) { - var entity = await _db.AuthenticationSecurityStates + var entity = await DbSet .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.UserKey == userKey && @@ -75,7 +77,7 @@ public async Task DeleteAsync(UserKey userKey, AuthenticationSecurityScope scope if (entity is null) return; - _db.AuthenticationSecurityStates.Remove(entity); + DbSet.Remove(entity); await _db.SaveChangesAsync(ct); } diff --git a/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Stores/EfCoreAuthenticationSecurityStateStoreFactory.cs b/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Stores/EfCoreAuthenticationSecurityStateStoreFactory.cs index 74ab7382..5f897cf7 100644 --- a/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Stores/EfCoreAuthenticationSecurityStateStoreFactory.cs +++ b/src/authentication/CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore/Stores/EfCoreAuthenticationSecurityStateStoreFactory.cs @@ -1,19 +1,20 @@ using CodeBeam.UltimateAuth.Core.Abstractions; using CodeBeam.UltimateAuth.Core.MultiTenancy; +using Microsoft.EntityFrameworkCore; namespace CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore; -internal sealed class EfCoreAuthenticationSecurityStateStoreFactory : IAuthenticationSecurityStateStoreFactory +internal sealed class EfCoreAuthenticationSecurityStateStoreFactory : IAuthenticationSecurityStateStoreFactory where TDbContext : DbContext { - private readonly UAuthAuthenticationDbContext _db; + private readonly TDbContext _db; - public EfCoreAuthenticationSecurityStateStoreFactory(UAuthAuthenticationDbContext db) + public EfCoreAuthenticationSecurityStateStoreFactory(TDbContext db) { _db = db; } public IAuthenticationSecurityStateStore Create(TenantKey tenant) { - return new EfCoreAuthenticationSecurityStateStore(_db, new TenantContext(tenant)); + return new EfCoreAuthenticationSecurityStateStore(_db, new TenantContext(tenant)); } } diff --git a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Data/UAuthAuthorizationDbContext.cs b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Data/UAuthAuthorizationDbContext.cs index dc322533..187e1d4d 100644 --- a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Data/UAuthAuthorizationDbContext.cs +++ b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Data/UAuthAuthorizationDbContext.cs @@ -1,7 +1,4 @@ -using CodeBeam.UltimateAuth.Authorization.Contracts; -using CodeBeam.UltimateAuth.Core.Domain; -using CodeBeam.UltimateAuth.Core.MultiTenancy; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; namespace CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore; @@ -16,115 +13,8 @@ public UAuthAuthorizationDbContext(DbContextOptions { } - protected override void OnModelCreating(ModelBuilder b) + protected override void OnModelCreating(ModelBuilder modelBuilder) { - ConfigureRole(b); - ConfigureRolePermission(b); - ConfigureUserRole(b); + UAuthAuthorizationModelBuilder.Configure(modelBuilder); } - - private void ConfigureRole(ModelBuilder b) - { - b.Entity(e => - { - e.ToTable("UAuth_Roles"); - e.HasKey(x => x.Id); - - e.Property(x => x.Version) - .IsConcurrencyToken(); - - e.Property(x => x.Tenant) - .HasConversion( - v => v.Value, - v => TenantKey.FromInternal(v)) - .HasMaxLength(128) - .IsRequired(); - - e.Property(x => x.Id) - .HasConversion( - v => v.Value, - v => RoleId.From(v)) - .IsRequired(); - - e.Property(x => x.Name) - .HasMaxLength(128) - .IsRequired(); - - e.Property(x => x.NormalizedName) - .HasMaxLength(128) - .IsRequired(); - - e.Property(x => x.CreatedAt) - .HasConversion( - v => v.UtcDateTime, - v => new DateTimeOffset(v, TimeSpan.Zero)); - - e.HasIndex(x => new { x.Tenant, x.Id }).IsUnique(); - e.HasIndex(x => new { x.Tenant, x.NormalizedName }).IsUnique(); - }); - } - - private void ConfigureRolePermission(ModelBuilder b) - { - b.Entity(e => - { - e.ToTable("UAuth_RolePermissions"); - e.HasKey(x => new { x.Tenant, x.RoleId, x.Permission }); - - e.Property(x => x.Tenant) - .HasConversion( - v => v.Value, - v => TenantKey.FromInternal(v)) - .HasMaxLength(128) - .IsRequired(); - - e.Property(x => x.RoleId) - .HasConversion( - v => v.Value, - v => RoleId.From(v)) - .IsRequired(); - - e.Property(x => x.Permission) - .HasMaxLength(256) - .IsRequired(); - - e.HasIndex(x => new { x.Tenant, x.RoleId }); - e.HasIndex(x => new { x.Tenant, x.Permission }); - }); - } - - private void ConfigureUserRole(ModelBuilder b) - { - b.Entity(e => - { - e.ToTable("UAuth_UserRoles"); - e.HasKey(x => new { x.Tenant, x.UserKey, x.RoleId }); - - e.Property(x => x.Tenant) - .HasConversion( - v => v.Value, - v => TenantKey.FromInternal(v)) - .HasMaxLength(128) - .IsRequired(); - - e.Property(x => x.UserKey) - .HasConversion( - v => v.Value, - v => UserKey.FromString(v)) - .HasMaxLength(128) - .IsRequired(); - - e.Property(x => x.RoleId) - .HasConversion( - v => v.Value, - v => RoleId.From(v)) - .IsRequired(); - - e.Property(x => x.AssignedAt) - .IsRequired(); - - e.HasIndex(x => new { x.Tenant, x.UserKey }); - e.HasIndex(x => new { x.Tenant, x.RoleId }); - }); - } -} \ No newline at end of file +} diff --git a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Data/UAuthAuthorizationModelBuilder.cs b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Data/UAuthAuthorizationModelBuilder.cs new file mode 100644 index 00000000..1d50fdbb --- /dev/null +++ b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Data/UAuthAuthorizationModelBuilder.cs @@ -0,0 +1,109 @@ +using CodeBeam.UltimateAuth.Authorization.Contracts; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.MultiTenancy; +using CodeBeam.UltimateAuth.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore; + +public static class UAuthAuthorizationModelBuilder +{ + public static void Configure(ModelBuilder b) + { + ConfigureRoles(b); + ConfigureRolePermissions(b); + ConfigureUserRoles(b); + } + + private static void ConfigureRoles(ModelBuilder b) + { + b.Entity(e => + { + e.ToTable("UAuth_Roles"); + + e.HasKey(x => x.Id); + + e.Property(x => x.Version) + .IsConcurrencyToken(); + + e.Property(x => x.Tenant) + .HasConversion(v => v.Value, v => TenantKey.FromInternal(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.Id) + .HasConversion(v => v.Value, v => RoleId.From(v)) + .IsRequired(); + + e.Property(x => x.Name) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.NormalizedName) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.CreatedAt).HasUtcDateTimeOffsetConverter().IsRequired(); + e.Property(x => x.UpdatedAt).HasNullableUtcDateTimeOffsetConverter(); + e.Property(x => x.DeletedAt).HasNullableUtcDateTimeOffsetConverter(); + + e.HasIndex(x => new { x.Tenant, x.Id }).IsUnique(); + e.HasIndex(x => new { x.Tenant, x.NormalizedName }).IsUnique(); + }); + } + + private static void ConfigureRolePermissions(ModelBuilder b) + { + b.Entity(e => + { + e.ToTable("UAuth_RolePermissions"); + + e.HasKey(x => new { x.Tenant, x.RoleId, x.Permission }); + + e.Property(x => x.Tenant) + .HasConversion(v => v.Value, v => TenantKey.FromInternal(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.RoleId) + .HasConversion(v => v.Value, v => RoleId.From(v)) + .IsRequired(); + + e.Property(x => x.Permission) + .HasMaxLength(256) + .IsRequired(); + + e.HasIndex(x => new { x.Tenant, x.RoleId }); + e.HasIndex(x => new { x.Tenant, x.Permission }); + }); + } + + private static void ConfigureUserRoles(ModelBuilder b) + { + b.Entity(e => + { + e.ToTable("UAuth_UserRoles"); + + e.HasKey(x => new { x.Tenant, x.UserKey, x.RoleId }); + + e.Property(x => x.Tenant) + .HasConversion(v => v.Value, v => TenantKey.FromInternal(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.UserKey) + .HasConversion(v => v.Value, v => UserKey.FromString(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.RoleId) + .HasConversion(v => v.Value, v => RoleId.From(v)) + .IsRequired(); + + e.Property(x => x.AssignedAt).HasUtcDateTimeOffsetConverter().IsRequired(); + + e.HasIndex(x => new { x.Tenant, x.UserKey }); + e.HasIndex(x => new { x.Tenant, x.RoleId }); + }); + } +} diff --git a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs index b4554cd4..8ed556c8 100644 --- a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs +++ b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs @@ -5,11 +5,15 @@ namespace CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.Extensions; public static class ServiceCollectionExtensions { - public static IServiceCollection AddUltimateAuthAuthorizationEntityFrameworkCore(this IServiceCollection services, Action configureDb) + public static IServiceCollection AddUltimateAuthAuthorizationEntityFrameworkCore(this IServiceCollection services, Action? configureDb = null) where TDbContext : DbContext { - services.AddDbContext(configureDb); - services.AddScoped(); - services.AddScoped(); + if (configureDb != null) + { + services.AddDbContext(configureDb); + } + + services.AddScoped>(); + services.AddScoped>(); return services; } } diff --git a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Projections/RoleProjection.cs b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Projections/RoleProjection.cs index 71976cbb..e4e5e80e 100644 --- a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Projections/RoleProjection.cs +++ b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Projections/RoleProjection.cs @@ -14,9 +14,7 @@ public sealed class RoleProjection public string NormalizedName { get; set; } = default!; public DateTimeOffset CreatedAt { get; set; } - public DateTimeOffset? UpdatedAt { get; set; } - public DateTimeOffset? DeletedAt { get; set; } public long Version { get; set; } diff --git a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Stores/EfCoreRoleStore.cs b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Stores/EfCoreRoleStore.cs index 8c58de33..f5151afa 100644 --- a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Stores/EfCoreRoleStore.cs +++ b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Stores/EfCoreRoleStore.cs @@ -6,20 +6,23 @@ namespace CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore; -internal sealed class EfCoreRoleStore : IRoleStore +internal sealed class EfCoreRoleStore : IRoleStore where TDbContext : DbContext { - private readonly UAuthAuthorizationDbContext _db; + private readonly TDbContext _db; private readonly TenantKey _tenant; - public EfCoreRoleStore(UAuthAuthorizationDbContext db, TenantContext tenant) + public EfCoreRoleStore(TDbContext db, TenantContext tenant) { _db = db; _tenant = tenant.Tenant; } + private DbSet DbSetRole => _db.Set(); + private DbSet DbSetPermission => _db.Set(); + public async Task ExistsAsync(RoleKey key, CancellationToken ct = default) { - return await _db.Roles + return await DbSetRole .AnyAsync(x => x.Tenant == _tenant && x.Id == key.RoleId, @@ -28,7 +31,7 @@ public async Task ExistsAsync(RoleKey key, CancellationToken ct = default) public async Task AddAsync(Role role, CancellationToken ct = default) { - var exists = await _db.Roles + var exists = await DbSetRole .AnyAsync(x => x.Tenant == _tenant && x.NormalizedName == role.NormalizedName && @@ -40,19 +43,19 @@ public async Task AddAsync(Role role, CancellationToken ct = default) var entity = RoleMapper.ToProjection(role); - _db.Roles.Add(entity); + DbSetRole.Add(entity); var permissionEntities = role.Permissions .Select(p => RolePermissionMapper.ToProjection(_tenant, role.Id, p)); - _db.RolePermissions.AddRange(permissionEntities); + DbSetPermission.AddRange(permissionEntities); await _db.SaveChangesAsync(ct); } public async Task GetAsync(RoleKey key, CancellationToken ct = default) { - var entity = await _db.Roles + var entity = await DbSetRole .AsNoTracking() .SingleOrDefaultAsync(x => x.Tenant == _tenant && @@ -62,7 +65,7 @@ public async Task AddAsync(Role role, CancellationToken ct = default) if (entity is null) return null; - var permissions = await _db.RolePermissions + var permissions = await DbSetPermission .AsNoTracking() .Where(x => x.Tenant == _tenant && @@ -74,7 +77,7 @@ public async Task AddAsync(Role role, CancellationToken ct = default) public async Task SaveAsync(Role role, long expectedVersion, CancellationToken ct = default) { - var entity = await _db.Roles + var entity = await DbSetRole .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.Id == role.Id, @@ -88,7 +91,7 @@ public async Task SaveAsync(Role role, long expectedVersion, CancellationToken c if (entity.NormalizedName != role.NormalizedName) { - var exists = await _db.Roles + var exists = await DbSetRole .AnyAsync(x => x.Tenant == _tenant && x.NormalizedName == role.NormalizedName && @@ -103,25 +106,25 @@ public async Task SaveAsync(Role role, long expectedVersion, CancellationToken c RoleMapper.UpdateProjection(role, entity); entity.Version++; - var existingPermissions = await _db.RolePermissions + var existingPermissions = await DbSetPermission .Where(x => x.Tenant == _tenant && x.RoleId == role.Id) .ToListAsync(ct); - _db.RolePermissions.RemoveRange(existingPermissions); + DbSetPermission.RemoveRange(existingPermissions); var newPermissions = role.Permissions .Select(p => RolePermissionMapper.ToProjection(_tenant, role.Id, p)); - _db.RolePermissions.AddRange(newPermissions); + DbSetPermission.AddRange(newPermissions); await _db.SaveChangesAsync(ct); } public async Task DeleteAsync(RoleKey key, long expectedVersion, DeleteMode mode, DateTimeOffset now, CancellationToken ct = default) { - var entity = await _db.Roles + var entity = await DbSetRole .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.Id == key.RoleId, @@ -135,13 +138,13 @@ public async Task DeleteAsync(RoleKey key, long expectedVersion, DeleteMode mode if (mode == DeleteMode.Hard) { - await _db.RolePermissions + await DbSetPermission .Where(x => x.Tenant == _tenant && x.RoleId == key.RoleId) .ExecuteDeleteAsync(ct); - _db.Roles.Remove(entity); + DbSetRole.Remove(entity); } else { @@ -154,7 +157,7 @@ await _db.RolePermissions public async Task GetByNameAsync(string normalizedName, CancellationToken ct = default) { - var entity = await _db.Roles + var entity = await DbSetRole .AsNoTracking() .SingleOrDefaultAsync(x => x.Tenant == _tenant && @@ -165,7 +168,7 @@ await _db.RolePermissions if (entity is null) return null; - var permissions = await _db.RolePermissions + var permissions = await DbSetPermission .AsNoTracking() .Where(x => x.Tenant == _tenant && @@ -179,7 +182,7 @@ public async Task> GetByIdsAsync( IReadOnlyCollection roleIds, CancellationToken ct = default) { - var entities = await _db.Roles + var entities = await DbSetRole .AsNoTracking() .Where(x => x.Tenant == _tenant && @@ -188,7 +191,7 @@ public async Task> GetByIdsAsync( var roleIdsSet = entities.Select(x => x.Id).ToList(); - var permissions = await _db.RolePermissions + var permissions = await DbSetPermission .AsNoTracking() .Where(x => x.Tenant == _tenant && @@ -217,7 +220,7 @@ public async Task> QueryAsync( { var normalized = query.Normalize(); - var baseQuery = _db.Roles + var baseQuery = DbSetRole .AsNoTracking() .Where(x => x.Tenant == _tenant); diff --git a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Stores/EfCoreRoleStoreFactory.cs b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Stores/EfCoreRoleStoreFactory.cs index 975dc443..ed02cd5e 100644 --- a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Stores/EfCoreRoleStoreFactory.cs +++ b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Stores/EfCoreRoleStoreFactory.cs @@ -1,18 +1,19 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; +using Microsoft.EntityFrameworkCore; namespace CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore; -internal sealed class EfCoreRoleStoreFactory : IRoleStoreFactory +internal sealed class EfCoreRoleStoreFactory : IRoleStoreFactory where TDbContext : DbContext { - private readonly UAuthAuthorizationDbContext _db; + private readonly TDbContext _db; - public EfCoreRoleStoreFactory(UAuthAuthorizationDbContext db) + public EfCoreRoleStoreFactory(TDbContext db) { _db = db; } public IRoleStore Create(TenantKey tenant) { - return new EfCoreRoleStore(_db, new TenantContext(tenant)); + return new EfCoreRoleStore(_db, new TenantContext(tenant)); } } diff --git a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Stores/EfCoreUserRoleStore.cs b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Stores/EfCoreUserRoleStore.cs index 24353a98..a99234f0 100644 --- a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Stores/EfCoreUserRoleStore.cs +++ b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Stores/EfCoreUserRoleStore.cs @@ -6,20 +6,22 @@ namespace CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore; -internal sealed class EfCoreUserRoleStore : IUserRoleStore +internal sealed class EfCoreUserRoleStore : IUserRoleStore where TDbContext : DbContext { - private readonly UAuthAuthorizationDbContext _db; + private readonly TDbContext _db; private readonly TenantKey _tenant; - public EfCoreUserRoleStore(UAuthAuthorizationDbContext db, TenantContext tenant) + public EfCoreUserRoleStore(TDbContext db, TenantContext tenant) { _db = db; _tenant = tenant.Tenant; } + private DbSet DbSet => _db.Set(); + public async Task> GetAssignmentsAsync(UserKey userKey, CancellationToken ct = default) { - var entities = await _db.UserRoles + var entities = await DbSet .AsNoTracking() .Where(x => x.Tenant == _tenant && @@ -31,7 +33,7 @@ public async Task> GetAssignmentsAsync(UserKey use public async Task AssignAsync(UserKey userKey, RoleId roleId, DateTimeOffset assignedAt, CancellationToken ct = default) { - var exists = await _db.UserRoles + var exists = await DbSet .AnyAsync(x => x.Tenant == _tenant && x.UserKey == userKey && @@ -49,13 +51,13 @@ public async Task AssignAsync(UserKey userKey, RoleId roleId, DateTimeOffset ass AssignedAt = assignedAt }; - _db.UserRoles.Add(entity); + DbSet.Add(entity); await _db.SaveChangesAsync(ct); } public async Task RemoveAsync(UserKey userKey, RoleId roleId, CancellationToken ct = default) { - var entity = await _db.UserRoles + var entity = await DbSet .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.UserKey == userKey && @@ -65,13 +67,13 @@ public async Task RemoveAsync(UserKey userKey, RoleId roleId, CancellationToken if (entity is null) return; - _db.UserRoles.Remove(entity); + DbSet.Remove(entity); await _db.SaveChangesAsync(ct); } public async Task RemoveAssignmentsByRoleAsync(RoleId roleId, CancellationToken ct = default) { - await _db.UserRoles + await DbSet .Where(x => x.Tenant == _tenant && x.RoleId == roleId) @@ -80,7 +82,7 @@ await _db.UserRoles public async Task CountAssignmentsAsync(RoleId roleId, CancellationToken ct = default) { - return await _db.UserRoles + return await DbSet .CountAsync(x => x.Tenant == _tenant && x.RoleId == roleId, diff --git a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Stores/EfCoreUserRoleStoreFactory.cs b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Stores/EfCoreUserRoleStoreFactory.cs index 516b5e9b..74132289 100644 --- a/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Stores/EfCoreUserRoleStoreFactory.cs +++ b/src/authorization/CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore/Stores/EfCoreUserRoleStoreFactory.cs @@ -1,18 +1,19 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; +using Microsoft.EntityFrameworkCore; namespace CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore; -internal sealed class EfCoreUserRoleStoreFactory : IUserRoleStoreFactory +internal sealed class EfCoreUserRoleStoreFactory : IUserRoleStoreFactory where TDbContext : DbContext { - private readonly UAuthAuthorizationDbContext _db; + private readonly TDbContext _db; - public EfCoreUserRoleStoreFactory(UAuthAuthorizationDbContext db) + public EfCoreUserRoleStoreFactory(TDbContext db) { _db = db; } public IUserRoleStore Create(TenantKey tenant) { - return new EfCoreUserRoleStore(_db, new TenantContext(tenant)); + return new EfCoreUserRoleStore(_db, new TenantContext(tenant)); } } diff --git a/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Data/UAuthCredentialDbContext.cs b/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Data/UAuthCredentialDbContext.cs index c97910f9..fb5a975d 100644 --- a/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Data/UAuthCredentialDbContext.cs +++ b/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Data/UAuthCredentialDbContext.cs @@ -1,6 +1,4 @@ -using CodeBeam.UltimateAuth.Core.Domain; -using CodeBeam.UltimateAuth.Core.MultiTenancy; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; namespace CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore; @@ -13,52 +11,8 @@ public UAuthCredentialDbContext(DbContextOptions optio { } - protected override void OnModelCreating(ModelBuilder b) + protected override void OnModelCreating(ModelBuilder modelBuilder) { - ConfigurePasswordCredential(b); + UAuthCredentialsModelBuilder.Configure(modelBuilder); } - - private void ConfigurePasswordCredential(ModelBuilder b) - { - b.Entity(e => - { - e.ToTable("UAuth_PasswordCredentials"); - e.HasKey(x => x.Id); - - e.Property(x => x.Version).IsConcurrencyToken(); - - e.Property(x => x.Tenant) - .HasConversion( - v => v.Value, - v => TenantKey.FromInternal(v)) - .HasMaxLength(128) - .IsRequired(); - - e.Property(x => x.UserKey) - .HasConversion( - v => v.Value, - v => UserKey.FromString(v)) - .HasMaxLength(128) - .IsRequired(); - - e.Property(x => x.SecretHash) - .HasMaxLength(512) - .IsRequired(); - - e.Property(x => x.SecurityStamp).IsRequired(); - e.Property(x => x.RevokedAt); - e.Property(x => x.ExpiresAt); - e.Property(x => x.LastUsedAt); - e.Property(x => x.Source).HasMaxLength(128); - e.Property(x => x.CreatedAt).IsRequired(); - e.Property(x => x.UpdatedAt); - e.Property(x => x.DeletedAt); - - e.HasIndex(x => new { x.Tenant, x.Id }).IsUnique(); - e.HasIndex(x => new { x.Tenant, x.UserKey }); - e.HasIndex(x => new { x.Tenant, x.UserKey, x.DeletedAt }); - e.HasIndex(x => new { x.Tenant, x.RevokedAt }); - e.HasIndex(x => new { x.Tenant, x.ExpiresAt }); - }); - } -} \ No newline at end of file +} diff --git a/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Data/UAuthCredentialsModelBuilder.cs b/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Data/UAuthCredentialsModelBuilder.cs new file mode 100644 index 00000000..67415b62 --- /dev/null +++ b/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Data/UAuthCredentialsModelBuilder.cs @@ -0,0 +1,60 @@ +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.MultiTenancy; +using CodeBeam.UltimateAuth.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore; + +public static class UAuthCredentialsModelBuilder +{ + public static void Configure(ModelBuilder b) + { + ConfigurePasswordCredentials(b); + } + + private static void ConfigurePasswordCredentials(ModelBuilder b) + { + b.Entity(e => + { + e.ToTable("UAuth_PasswordCredentials"); + + e.HasKey(x => x.Id); + + e.Property(x => x.Version) + .IsConcurrencyToken(); + + e.Property(x => x.Tenant) + .HasConversion(v => v.Value, v => TenantKey.FromInternal(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.UserKey) + .HasConversion(v => v.Value, v => UserKey.FromString(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.SecretHash) + .HasMaxLength(512) + .IsRequired(); + + e.Property(x => x.SecurityStamp) + .IsRequired(); + + e.Property(x => x.Source) + .HasMaxLength(128); + + e.Property(x => x.CreatedAt).HasUtcDateTimeOffsetConverter().IsRequired(); + e.Property(x => x.UpdatedAt).HasNullableUtcDateTimeOffsetConverter(); + e.Property(x => x.DeletedAt).HasNullableUtcDateTimeOffsetConverter(); + e.Property(x => x.RevokedAt).HasNullableUtcDateTimeOffsetConverter(); + e.Property(x => x.ExpiresAt).HasNullableUtcDateTimeOffsetConverter(); + e.Property(x => x.LastUsedAt).HasNullableUtcDateTimeOffsetConverter(); + + e.HasIndex(x => new { x.Tenant, x.Id }).IsUnique(); + e.HasIndex(x => new { x.Tenant, x.UserKey }); + e.HasIndex(x => new { x.Tenant, x.UserKey, x.DeletedAt }); + e.HasIndex(x => new { x.Tenant, x.RevokedAt }); + e.HasIndex(x => new { x.Tenant, x.ExpiresAt }); + }); + } +} diff --git a/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs b/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs index 23f8b844..7c390a43 100644 --- a/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs +++ b/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs @@ -6,10 +6,14 @@ namespace CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore.Extensions; public static class ServiceCollectionExtensions { - public static IServiceCollection AddUltimateAuthCredentialsEntityFrameworkCore(this IServiceCollection services, Action configureDb) + public static IServiceCollection AddUltimateAuthCredentialsEntityFrameworkCore(this IServiceCollection services, Action? configureDb = null) where TDbContext : DbContext { - services.AddDbContext(configureDb); - services.AddScoped(); + if (configureDb != null) + { + services.AddDbContext(configureDb); + } + + services.AddScoped>(); return services; } } diff --git a/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Stores/EfCorePasswordCredentialStore.cs b/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Stores/EfCorePasswordCredentialStore.cs index bf533435..383bd7e0 100644 --- a/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Stores/EfCorePasswordCredentialStore.cs +++ b/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Stores/EfCorePasswordCredentialStore.cs @@ -8,20 +8,22 @@ namespace CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore; -internal sealed class EfCorePasswordCredentialStore : IPasswordCredentialStore +internal sealed class EfCorePasswordCredentialStore : IPasswordCredentialStore where TDbContext : DbContext { - private readonly UAuthCredentialDbContext _db; + private readonly TDbContext _db; private readonly TenantKey _tenant; - public EfCorePasswordCredentialStore(UAuthCredentialDbContext db, TenantContext tenant) + public EfCorePasswordCredentialStore(TDbContext db, TenantContext tenant) { _db = db; _tenant = tenant.Tenant; } + private DbSet DbSet => _db.Set(); + public async Task ExistsAsync(CredentialKey key, CancellationToken ct = default) { - return await _db.PasswordCredentials + return await DbSet .AnyAsync(x => x.Id == key.Id && x.Tenant == _tenant, @@ -32,14 +34,14 @@ public async Task AddAsync(PasswordCredential credential, CancellationToken ct = { var entity = credential.ToProjection(); - _db.PasswordCredentials.Add(entity); + DbSet.Add(entity); await _db.SaveChangesAsync(ct); } public async Task GetAsync(CredentialKey key, CancellationToken ct = default) { - var entity = await _db.PasswordCredentials + var entity = await DbSet .AsNoTracking() .SingleOrDefaultAsync( x => x.Id == key.Id && @@ -51,7 +53,7 @@ public async Task AddAsync(PasswordCredential credential, CancellationToken ct = public async Task SaveAsync(PasswordCredential credential, long expectedVersion, CancellationToken ct = default) { - var entity = await _db.PasswordCredentials + var entity = await DbSet .SingleOrDefaultAsync(x => x.Id == credential.Id && x.Tenant == _tenant, @@ -71,7 +73,7 @@ public async Task SaveAsync(PasswordCredential credential, long expectedVersion, public async Task RevokeAsync(CredentialKey key, DateTimeOffset revokedAt, long expectedVersion, CancellationToken ct = default) { - var entity = await _db.PasswordCredentials + var entity = await DbSet .SingleOrDefaultAsync(x => x.Id == key.Id && x.Tenant == _tenant, @@ -93,7 +95,7 @@ public async Task RevokeAsync(CredentialKey key, DateTimeOffset revokedAt, long public async Task DeleteAsync(CredentialKey key, long expectedVersion, DeleteMode mode, DateTimeOffset now, CancellationToken ct = default) { - var entity = await _db.PasswordCredentials + var entity = await DbSet .SingleOrDefaultAsync(x => x.Id == key.Id && x.Tenant == _tenant, @@ -107,7 +109,7 @@ public async Task DeleteAsync(CredentialKey key, long expectedVersion, DeleteMod if (mode == DeleteMode.Hard) { - _db.PasswordCredentials.Remove(entity); + DbSet.Remove(entity); } else { @@ -121,7 +123,7 @@ public async Task DeleteAsync(CredentialKey key, long expectedVersion, DeleteMod public async Task> GetByUserAsync(UserKey userKey, CancellationToken ct = default) { - var entities = await _db.PasswordCredentials + var entities = await DbSet .AsNoTracking() .Where(x => x.Tenant == _tenant && @@ -139,7 +141,7 @@ public async Task DeleteByUserAsync(UserKey userKey, DeleteMode mode, DateTimeOf { if (mode == DeleteMode.Hard) { - await _db.PasswordCredentials + await DbSet .Where(x => x.Tenant == _tenant && x.UserKey == userKey) @@ -148,7 +150,7 @@ await _db.PasswordCredentials return; } - await _db.PasswordCredentials + await DbSet .Where(x => x.Tenant == _tenant && x.UserKey == userKey && diff --git a/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Stores/EfCorePasswordCredentialStoreFactory.cs b/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Stores/EfCorePasswordCredentialStoreFactory.cs index ba037a79..13a0a4a7 100644 --- a/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Stores/EfCorePasswordCredentialStoreFactory.cs +++ b/src/credentials/CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore/Stores/EfCorePasswordCredentialStoreFactory.cs @@ -1,19 +1,20 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Credentials.Reference; +using Microsoft.EntityFrameworkCore; namespace CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore; -internal sealed class EfCorePasswordCredentialStoreFactory : IPasswordCredentialStoreFactory +internal sealed class EfCorePasswordCredentialStoreFactory : IPasswordCredentialStoreFactory where TDbContext : DbContext { - private readonly UAuthCredentialDbContext _db; + private readonly TDbContext _db; - public EfCorePasswordCredentialStoreFactory(UAuthCredentialDbContext db) + public EfCorePasswordCredentialStoreFactory(TDbContext db) { _db = db; } public IPasswordCredentialStore Create(TenantKey tenant) { - return new EfCorePasswordCredentialStore(_db, new TenantContext(tenant)); + return new EfCorePasswordCredentialStore(_db, new TenantContext(tenant)); } } diff --git a/src/persistence/CodeBeam.UltimateAuth.EntityFrameworkCore/Infrastructure/DateTimeOffsetConverter.cs b/src/persistence/CodeBeam.UltimateAuth.EntityFrameworkCore/Infrastructure/DateTimeOffsetConverter.cs new file mode 100644 index 00000000..6fd9019e --- /dev/null +++ b/src/persistence/CodeBeam.UltimateAuth.EntityFrameworkCore/Infrastructure/DateTimeOffsetConverter.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace CodeBeam.UltimateAuth.EntityFrameworkCore; + +public static class DateTimeOffsetConverter +{ + public static PropertyBuilder HasUtcDateTimeOffsetConverter(this PropertyBuilder property) + { + return property.HasConversion(UtcDateTimeOffsetConverter); + } + + public static PropertyBuilder HasNullableUtcDateTimeOffsetConverter(this PropertyBuilder property) + { + return property.HasConversion(NullableUtcDateTimeOffsetConverter); + } + + private static readonly ValueConverter UtcDateTimeOffsetConverter = + new( + v => v.UtcDateTime, + v => new DateTimeOffset(DateTime.SpecifyKind(v, DateTimeKind.Utc)) + ); + + private static readonly ValueConverter NullableUtcDateTimeOffsetConverter = + new( + v => v.HasValue ? v.Value.UtcDateTime : null, + v => v.HasValue + ? new DateTimeOffset(DateTime.SpecifyKind(v.Value, DateTimeKind.Utc)) + : null + ); +} diff --git a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Data/UAuthSessionDbContext.cs b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Data/UAuthSessionDbContext.cs index 102d90d5..93fcd842 100644 --- a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Data/UAuthSessionDbContext.cs +++ b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Data/UAuthSessionDbContext.cs @@ -1,7 +1,4 @@ -using CodeBeam.UltimateAuth.Core.Domain; -using CodeBeam.UltimateAuth.Core.MultiTenancy; -using CodeBeam.UltimateAuth.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; namespace CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; @@ -16,170 +13,8 @@ public UAuthSessionDbContext(DbContextOptions options) : { } - protected override void OnModelCreating(ModelBuilder b) + protected override void OnModelCreating(ModelBuilder modelBuilder) { - b.Entity(e => - { - e.ToTable("UAuth_SessionRoots"); - e.HasKey(x => x.Id); - - e.Property(x => x.Version).IsConcurrencyToken(); - - e.Property(x => x.CreatedAt).IsRequired() - .HasConversion( - v => v.UtcDateTime, - v => new DateTimeOffset(v, TimeSpan.Zero)); - - e.Property(x => x.UserKey) - .HasConversion( - v => v.Value, - v => UserKey.FromString(v)) - .HasMaxLength(128) - .IsRequired(); - e.Property(x => x.Tenant) - .HasConversion( - v => v.Value, - v => TenantKey.FromInternal(v)) - .HasMaxLength(128) - .IsRequired(); - - e.HasIndex(x => new { x.Tenant, x.UserKey }).IsUnique(); - e.HasIndex(x => new { x.Tenant, x.RootId }).IsUnique(); - - e.Property(x => x.SecurityVersion) - .IsRequired(); - - e.Property(x => x.RootId) - .HasConversion( - v => v.Value, - v => SessionRootId.From(v)) - .HasMaxLength(128) - .IsRequired(); - }); - - b.Entity(e => - { - e.ToTable("UAuth_SessionChains"); - e.HasKey(x => x.Id); - - e.Property(x => x.Version).IsConcurrencyToken(); - e.Property(x => x.CreatedAt).IsRequired() - .HasConversion( - v => v.UtcDateTime, - v => new DateTimeOffset(v, TimeSpan.Zero)); - - e.Property(x => x.UserKey) - .HasConversion( - v => v.Value, - v => UserKey.FromString(v)) - .HasMaxLength(128) - .IsRequired(); - e.Property(x => x.Tenant) - .HasConversion( - v => v.Value, - v => TenantKey.FromInternal(v)) - .HasMaxLength(128) - .IsRequired(); - - e.HasIndex(x => new { x.Tenant, x.ChainId }).IsUnique(); - e.HasIndex(x => new { x.Tenant, x.UserKey }); - e.HasIndex(x => new { x.Tenant, x.UserKey, x.DeviceId }); - e.HasIndex(x => new { x.Tenant, x.RootId }); - - e.HasOne() - .WithMany() - .HasForeignKey(x => new { x.Tenant, x.RootId }) - .HasPrincipalKey(x => new { x.Tenant, x.RootId }) - .OnDelete(DeleteBehavior.Restrict); - - e.Property(x => x.ChainId) - .HasConversion( - v => v.Value, - v => SessionChainId.From(v)) - .IsRequired(); - - e.Property(x => x.DeviceId) - .HasConversion( - v => v.Value, - v => DeviceId.Create(v)) - .HasMaxLength(64) - .IsRequired(); - - e.Property(x => x.Device) - .HasConversion(new JsonValueConverter()) - .IsRequired(); - - e.Property(x => x.ActiveSessionId) - .HasConversion(new NullableAuthSessionIdConverter()); - - e.Property(x => x.ClaimsSnapshot) - .HasConversion(new JsonValueConverter()) - .IsRequired(); - - e.Property(x => x.SecurityVersionAtCreation) - .IsRequired(); - }); - - b.Entity(e => - { - e.ToTable("UAuth_Sessions"); - e.HasKey(x => x.Id); - e.Property(x => x.Version).IsConcurrencyToken(); - e.Property(x => x.CreatedAt).IsRequired(); - - e.Property(x => x.CreatedAt).IsRequired() - .HasConversion( - v => v.UtcDateTime, - v => new DateTimeOffset(v, TimeSpan.Zero)); - - e.Property(x => x.UserKey) - .HasConversion( - v => v.Value, - v => UserKey.FromString(v)) - .HasMaxLength(128) - .IsRequired(); - e.Property(x => x.Tenant) - .HasConversion( - v => v.Value, - v => TenantKey.FromInternal(v)) - .HasMaxLength(128) - .IsRequired(); - - e.HasIndex(x => new { x.Tenant, x.SessionId }).IsUnique(); - e.HasIndex(x => new { x.Tenant, x.ChainId }); - e.HasIndex(x => new { x.Tenant, x.ChainId, x.RevokedAt }); - e.HasIndex(x => new { x.Tenant, x.UserKey, x.RevokedAt }); - e.HasIndex(x => new { x.Tenant, x.ExpiresAt }); - e.HasIndex(x => new { x.Tenant, x.RevokedAt }); - - e.HasOne() - .WithMany() - .HasForeignKey(x => new { x.Tenant, x.ChainId }) - .HasPrincipalKey(x => new { x.Tenant, x.ChainId }) - .OnDelete(DeleteBehavior.Restrict); - - e.Property(x => x.SessionId) - .HasConversion(new AuthSessionIdConverter()) - .IsRequired(); - - e.Property(x => x.ChainId) - .HasConversion( - v => v.Value, - v => SessionChainId.From(v)) - .IsRequired(); - - e.Property(x => x.Device) - .HasConversion(new JsonValueConverter()) - .IsRequired(); - - e.Property(x => x.Claims) - .HasConversion(new JsonValueConverter()) - .IsRequired(); - - e.Property(x => x.Metadata) - .HasConversion(new JsonValueConverter()) - .IsRequired(); - }); + UAuthSessionsModelBuilder.Configure(modelBuilder); } - } diff --git a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Data/UAuthSessionsModelBuilder.cs b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Data/UAuthSessionsModelBuilder.cs new file mode 100644 index 00000000..e6d98900 --- /dev/null +++ b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Data/UAuthSessionsModelBuilder.cs @@ -0,0 +1,170 @@ +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.MultiTenancy; +using CodeBeam.UltimateAuth.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; + +public static class UAuthSessionsModelBuilder +{ + public static void Configure(ModelBuilder b) + { + ConfigureRoots(b); + ConfigureChains(b); + ConfigureSessions(b); + } + + private static void ConfigureRoots(ModelBuilder b) + { + b.Entity(e => + { + e.ToTable("UAuth_SessionRoots"); + e.HasKey(x => x.Id); + + e.Property(x => x.Version).IsConcurrencyToken(); + + e.Property(x => x.CreatedAt).HasUtcDateTimeOffsetConverter().IsRequired(); + e.Property(x => x.UpdatedAt).HasNullableUtcDateTimeOffsetConverter(); + e.Property(x => x.RevokedAt).HasNullableUtcDateTimeOffsetConverter(); + + e.Property(x => x.UserKey) + .HasConversion(v => v.Value, v => UserKey.FromString(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.Tenant) + .HasConversion(v => v.Value, v => TenantKey.FromInternal(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.RootId) + .HasConversion(v => v.Value, v => SessionRootId.From(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.SecurityVersion) + .IsRequired(); + + e.HasIndex(x => new { x.Tenant, x.UserKey }).IsUnique(); + e.HasIndex(x => new { x.Tenant, x.RootId }).IsUnique(); + }); + } + + private static void ConfigureChains(ModelBuilder b) + { + b.Entity(e => + { + e.ToTable("UAuth_SessionChains"); + e.HasKey(x => x.Id); + + e.Property(x => x.Version).IsConcurrencyToken(); + + e.Property(x => x.CreatedAt).HasUtcDateTimeOffsetConverter().IsRequired(); + e.Property(x => x.LastSeenAt).HasUtcDateTimeOffsetConverter(); + e.Property(x => x.RevokedAt).HasNullableUtcDateTimeOffsetConverter(); + e.Property(x => x.AbsoluteExpiresAt).HasNullableUtcDateTimeOffsetConverter(); + + e.Property(x => x.UserKey) + .HasConversion(v => v.Value, v => UserKey.FromString(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.Tenant) + .HasConversion(v => v.Value, v => TenantKey.FromInternal(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.ChainId) + .HasConversion(v => v.Value, v => SessionChainId.From(v)) + .IsRequired(); + + e.Property(x => x.DeviceId) + .HasConversion(v => v.Value, v => DeviceId.Create(v)) + .HasMaxLength(64) + .IsRequired(); + + e.Property(x => x.Device) + .HasConversion(new JsonValueConverter()) + .IsRequired(); + + e.Property(x => x.ActiveSessionId) + .HasConversion(new NullableAuthSessionIdConverter()); + + e.Property(x => x.ClaimsSnapshot) + .HasConversion(new JsonValueConverter()) + .IsRequired(); + + e.Property(x => x.SecurityVersionAtCreation) + .IsRequired(); + + e.HasIndex(x => new { x.Tenant, x.ChainId }).IsUnique(); + e.HasIndex(x => new { x.Tenant, x.UserKey }); + e.HasIndex(x => new { x.Tenant, x.UserKey, x.DeviceId }); + e.HasIndex(x => new { x.Tenant, x.RootId }); + + e.HasOne() + .WithMany() + .HasForeignKey(x => new { x.Tenant, x.RootId }) + .HasPrincipalKey(x => new { x.Tenant, x.RootId }) + .OnDelete(DeleteBehavior.Restrict); + }); + } + + private static void ConfigureSessions(ModelBuilder b) + { + b.Entity(e => + { + e.ToTable("UAuth_Sessions"); + e.HasKey(x => x.Id); + + e.Property(x => x.Version).IsConcurrencyToken(); + + e.Property(x => x.CreatedAt).HasUtcDateTimeOffsetConverter().IsRequired(); + e.Property(x => x.ExpiresAt).HasUtcDateTimeOffsetConverter().IsRequired(); + e.Property(x => x.RevokedAt).HasNullableUtcDateTimeOffsetConverter(); + + e.Property(x => x.UserKey) + .HasConversion(v => v.Value, v => UserKey.FromString(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.Tenant) + .HasConversion(v => v.Value, v => TenantKey.FromInternal(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.SessionId) + .HasConversion(new AuthSessionIdConverter()) + .IsRequired(); + + e.Property(x => x.ChainId) + .HasConversion(v => v.Value, v => SessionChainId.From(v)) + .IsRequired(); + + e.Property(x => x.Device) + .HasConversion(new JsonValueConverter()) + .IsRequired(); + + e.Property(x => x.Claims) + .HasConversion(new JsonValueConverter()) + .IsRequired(); + + e.Property(x => x.Metadata) + .HasConversion(new JsonValueConverter()) + .IsRequired(); + + e.HasIndex(x => new { x.Tenant, x.SessionId }).IsUnique(); + e.HasIndex(x => new { x.Tenant, x.ChainId }); + e.HasIndex(x => new { x.Tenant, x.ChainId, x.RevokedAt }); + e.HasIndex(x => new { x.Tenant, x.UserKey, x.RevokedAt }); + e.HasIndex(x => new { x.Tenant, x.ExpiresAt }); + e.HasIndex(x => new { x.Tenant, x.RevokedAt }); + + e.HasOne() + .WithMany() + .HasForeignKey(x => new { x.Tenant, x.ChainId }) + .HasPrincipalKey(x => new { x.Tenant, x.ChainId }) + .OnDelete(DeleteBehavior.Restrict); + }); + } +} diff --git a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs index 69762efe..2dc070af 100644 --- a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs +++ b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs @@ -6,10 +6,14 @@ namespace CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.Extensions; public static class ServiceCollectionExtensions { - public static IServiceCollection AddUltimateAuthSessionsEntityFrameworkCore(this IServiceCollection services,Action configureDb) + public static IServiceCollection AddUltimateAuthSessionsEntityFrameworkCore(this IServiceCollection services, Action? configureDb = null) where TDbContext : DbContext { - services.AddDbContext(configureDb); - services.AddScoped(); + if (configureDb != null) + { + services.AddDbContext(configureDb); + } + + services.AddScoped>(); return services; } diff --git a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Projections/SessionProjection.cs b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Projections/SessionProjection.cs index 3f815941..7860d578 100644 --- a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Projections/SessionProjection.cs +++ b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Projections/SessionProjection.cs @@ -9,14 +9,11 @@ public sealed class SessionProjection public AuthSessionId SessionId { get; set; } = default!; public SessionChainId ChainId { get; set; } = default!; - public TenantKey Tenant { get; set; } public UserKey UserKey { get; set; } = default!; public DateTimeOffset CreatedAt { get; set; } public DateTimeOffset ExpiresAt { get; set; } - - public DateTimeOffset? RevokedAt { get; set; } public long SecurityVersionAtCreation { get; set; } diff --git a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Stores/EfCoreSessionStore.cs b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Stores/EfCoreSessionStore.cs index d1fcd845..ca86ff54 100644 --- a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Stores/EfCoreSessionStore.cs +++ b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Stores/EfCoreSessionStore.cs @@ -7,17 +7,21 @@ namespace CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; -internal sealed class EfCoreSessionStore : ISessionStore +internal sealed class EfCoreSessionStore : ISessionStore where TDbContext : DbContext { - private readonly UAuthSessionDbContext _db; + private readonly TDbContext _db; private readonly TenantKey _tenant; - public EfCoreSessionStore(UAuthSessionDbContext db, TenantContext tenant) + public EfCoreSessionStore(TDbContext db, TenantContext tenant) { _db = db; _tenant = tenant.Tenant; } + private DbSet DbSetSession => _db.Set(); + private DbSet DbSetChain => _db.Set(); + private DbSet DbSetRoot => _db.Set(); + public async Task ExecuteAsync(Func action, CancellationToken ct = default) { var strategy = _db.Database.CreateExecutionStrategy(); @@ -77,12 +81,12 @@ public async Task ExecuteAsync(Func x.Tenant == _tenant && x.SessionId == sessionId); + var local = DbSetSession.Local.FirstOrDefault(x => x.Tenant == _tenant && x.SessionId == sessionId); if (local != null) return local.ToDomain(); - var projection = await _db.Sessions + var projection = await DbSetSession .AsNoTracking() .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.SessionId == sessionId); @@ -93,11 +97,11 @@ public async Task SaveSessionAsync(UAuthSession session, long expectedVersion, C { ct.ThrowIfCancellationRequested(); - var projection = _db.Sessions.Local.FirstOrDefault(x => x.Tenant == _tenant && x.SessionId == session.SessionId); + var projection = DbSetSession.Local.FirstOrDefault(x => x.Tenant == _tenant && x.SessionId == session.SessionId); if (projection == null) { - projection = await _db.Sessions + projection = await DbSetSession .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.SessionId == session.SessionId, @@ -123,7 +127,7 @@ public Task CreateSessionAsync(UAuthSession session, CancellationToken ct = defa if (session.Version != 0) throw new InvalidOperationException("New session must have version 0."); - _db.Sessions.Add(projection); + DbSetSession.Add(projection); return Task.CompletedTask; } @@ -132,7 +136,7 @@ public async Task RevokeSessionAsync(AuthSessionId sessionId, DateTimeOffs { ct.ThrowIfCancellationRequested(); - var projection = await _db.Sessions.SingleOrDefaultAsync(x => x.Tenant == _tenant && x.SessionId == sessionId, ct); + var projection = await DbSetSession.SingleOrDefaultAsync(x => x.Tenant == _tenant && x.SessionId == sessionId, ct); if (projection is null || projection.RevokedAt is not null) return false; @@ -148,13 +152,13 @@ public async Task RevokeAllSessionsAsync(UserKey user, DateTimeOffset at, Cancel { ct.ThrowIfCancellationRequested(); - var chains = await _db.Chains + var chains = await DbSetChain .Where(x => x.Tenant == _tenant && x.UserKey == user) .ToListAsync(ct); var chainIds = chains.Select(x => x.ChainId).ToList(); - var sessions = await _db.Sessions + var sessions = await DbSetSession .Where(x => x.Tenant == _tenant && chainIds.Contains(x.ChainId)) .ToListAsync(ct); @@ -184,13 +188,13 @@ public async Task RevokeOtherSessionsAsync(UserKey user, SessionChainId keepChai { ct.ThrowIfCancellationRequested(); - var chains = await _db.Chains + var chains = await DbSetChain .Where(x => x.Tenant == _tenant && x.UserKey == user && x.ChainId != keepChain) .ToListAsync(ct); var chainIds = chains.Select(x => x.ChainId).ToList(); - var sessions = await _db.Sessions + var sessions = await DbSetSession .Where(x => x.Tenant == _tenant && chainIds.Contains(x.ChainId)) .ToListAsync(ct); @@ -220,12 +224,12 @@ public async Task RevokeOtherSessionsAsync(UserKey user, SessionChainId keepChai { ct.ThrowIfCancellationRequested(); - var local = _db.Chains.Local.FirstOrDefault(x => x.Tenant == _tenant && x.ChainId == chainId); + var local = DbSetChain.Local.FirstOrDefault(x => x.Tenant == _tenant && x.ChainId == chainId); if (local is not null) return local.ToDomain(); - var projection = await _db.Chains + var projection = await DbSetChain .AsNoTracking() .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.ChainId == chainId, ct); @@ -236,7 +240,7 @@ public async Task RevokeOtherSessionsAsync(UserKey user, SessionChainId keepChai { ct.ThrowIfCancellationRequested(); - var local = _db.Chains.Local + var local = DbSetChain.Local .Where(x => x.Tenant == _tenant && x.UserKey == userKey && @@ -247,7 +251,7 @@ public async Task RevokeOtherSessionsAsync(UserKey user, SessionChainId keepChai if (local != null) return local.ToDomain(); - var projection = await _db.Chains + var projection = await DbSetChain .AsNoTracking() .Where(x => x.Tenant == _tenant && @@ -263,11 +267,11 @@ public async Task SaveChainAsync(UAuthSessionChain chain, long expectedVersion, { ct.ThrowIfCancellationRequested(); - var projection = _db.Chains.Local.FirstOrDefault(x => x.Tenant == _tenant && x.ChainId == chain.ChainId); + var projection = DbSetChain.Local.FirstOrDefault(x => x.Tenant == _tenant && x.ChainId == chain.ChainId); if (projection is null) { - projection = await _db.Chains + projection = await DbSetChain .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.ChainId == chain.ChainId, ct); } @@ -290,7 +294,7 @@ public Task CreateChainAsync(UAuthSessionChain chain, CancellationToken ct = def var projection = chain.ToProjection(); - _db.Chains.Add(projection); + DbSetChain.Add(projection); _db.Entry(projection).State = EntityState.Added; return Task.CompletedTask; @@ -300,7 +304,7 @@ public async Task RevokeChainAsync(SessionChainId chainId, DateTimeOffset at, Ca { ct.ThrowIfCancellationRequested(); - var projection = await _db.Chains + var projection = await DbSetChain .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.ChainId == chainId, ct); if (projection is null || projection.RevokedAt is not null) @@ -315,13 +319,13 @@ public async Task LogoutChainAsync(SessionChainId chainId, DateTimeOffset at, Ca { ct.ThrowIfCancellationRequested(); - var chainProjection = await _db.Chains + var chainProjection = await DbSetChain .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.ChainId == chainId, ct); if (chainProjection is null || chainProjection.RevokedAt is not null) return; - var sessions = await _db.Sessions + var sessions = await DbSetSession .Where(x => x.Tenant == _tenant && x.ChainId == chainId) .ToListAsync(ct); @@ -348,7 +352,7 @@ public async Task RevokeOtherChainsAsync(UserKey userKey, SessionChainId current { ct.ThrowIfCancellationRequested(); - var projections = await _db.Chains + var projections = await DbSetChain .Where(x => x.Tenant == _tenant && x.UserKey == userKey && @@ -368,7 +372,7 @@ public async Task RevokeAllChainsAsync(UserKey userKey, DateTimeOffset at, Cance { ct.ThrowIfCancellationRequested(); - var projections = await _db.Chains + var projections = await DbSetChain .Where(x => x.Tenant == _tenant && x.UserKey == userKey && @@ -387,7 +391,7 @@ public async Task RevokeAllChainsAsync(UserKey userKey, DateTimeOffset at, Cance { ct.ThrowIfCancellationRequested(); - return await _db.Chains + return await DbSetChain .AsNoTracking() .Where(x => x.Tenant == _tenant && x.ChainId == chainId) .Select(x => x.ActiveSessionId) @@ -398,11 +402,11 @@ public async Task SetActiveSessionIdAsync(SessionChainId chainId, AuthSessionId { ct.ThrowIfCancellationRequested(); - var projection = _db.Chains.Local.FirstOrDefault(x => x.Tenant == _tenant && x.ChainId == chainId); + var projection = DbSetChain.Local.FirstOrDefault(x => x.Tenant == _tenant && x.ChainId == chainId); if (projection is null) { - projection = await _db.Chains.SingleOrDefaultAsync(x => x.Tenant == _tenant && x.ChainId == chainId, ct); + projection = await DbSetChain.SingleOrDefaultAsync(x => x.Tenant == _tenant && x.ChainId == chainId, ct); } if (projection is null) @@ -416,7 +420,7 @@ public async Task SetActiveSessionIdAsync(SessionChainId chainId, AuthSessionId { ct.ThrowIfCancellationRequested(); - var rootProjection = await _db.Roots.AsNoTracking().SingleOrDefaultAsync(x => x.Tenant == _tenant && x.UserKey == userKey, ct); + var rootProjection = await DbSetRoot.AsNoTracking().SingleOrDefaultAsync(x => x.Tenant == _tenant && x.UserKey == userKey, ct); return rootProjection?.ToDomain(); } @@ -424,7 +428,7 @@ public async Task SaveRootAsync(UAuthSessionRoot root, long expectedVersion, Can { ct.ThrowIfCancellationRequested(); - var projection = await _db.Roots + var projection = await DbSetRoot .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.UserKey == root.UserKey, @@ -449,7 +453,7 @@ public Task CreateRootAsync(UAuthSessionRoot root, CancellationToken ct = defaul var projection = root.ToProjection(); - _db.Roots.Add(projection); + DbSetRoot.Add(projection); return Task.CompletedTask; } @@ -458,7 +462,7 @@ public async Task RevokeRootAsync(UserKey userKey, DateTimeOffset at, Cancellati { ct.ThrowIfCancellationRequested(); - var projection = await _db.Roots + var projection = await DbSetRoot .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.UserKey == userKey, ct); if (projection is null || projection.RevokedAt is not null) @@ -473,7 +477,7 @@ public async Task RevokeRootAsync(UserKey userKey, DateTimeOffset at, Cancellati { ct.ThrowIfCancellationRequested(); - return await _db.Sessions + return await DbSetSession .AsNoTracking() .Where(x => x.Tenant == _tenant && x.SessionId == sessionId) .Select(x => (SessionChainId?)x.ChainId) @@ -484,7 +488,7 @@ public async Task> GetChainsByUserAsync(UserKey { ct.ThrowIfCancellationRequested(); - var rootsQuery = _db.Roots.AsNoTracking().Where(x => x.Tenant == _tenant && x.UserKey == userKey); + var rootsQuery = DbSetRoot.AsNoTracking().Where(x => x.Tenant == _tenant && x.UserKey == userKey); if (!includeHistoricalRoots) { @@ -496,7 +500,7 @@ public async Task> GetChainsByUserAsync(UserKey if (rootIds.Count == 0) return Array.Empty(); - var projections = await _db.Chains.AsNoTracking().Where(x => x.Tenant == _tenant && rootIds.Contains(x.RootId)).ToListAsync(); + var projections = await DbSetChain.AsNoTracking().Where(x => x.Tenant == _tenant && rootIds.Contains(x.RootId)).ToListAsync(); return projections.Select(c => c.ToDomain()).ToList(); } @@ -504,7 +508,7 @@ public async Task> GetChainsByRootAsync(Session { ct.ThrowIfCancellationRequested(); - var projections = await _db.Chains + var projections = await DbSetChain .AsNoTracking() .Where(x => x.Tenant == _tenant && x.RootId == rootId) .ToListAsync(); @@ -516,7 +520,7 @@ public async Task> GetSessionsByChainAsync(SessionCh { ct.ThrowIfCancellationRequested(); - var projections = await _db.Sessions + var projections = await DbSetSession .AsNoTracking() .Where(x => x.Tenant == _tenant && x.ChainId == chainId) .ToListAsync(); @@ -528,7 +532,7 @@ public async Task> GetSessionsByChainAsync(SessionCh { ct.ThrowIfCancellationRequested(); - var projection = await _db.Roots.AsNoTracking().SingleOrDefaultAsync(x => x.Tenant == _tenant && x.RootId == rootId, ct); + var projection = await DbSetRoot.AsNoTracking().SingleOrDefaultAsync(x => x.Tenant == _tenant && x.RootId == rootId, ct); return projection?.ToDomain(); } @@ -536,25 +540,25 @@ public async Task RemoveSessionAsync(AuthSessionId sessionId, CancellationToken { ct.ThrowIfCancellationRequested(); - var projection = await _db.Sessions.SingleOrDefaultAsync(x => x.Tenant == _tenant && x.SessionId == sessionId, ct); + var projection = await DbSetSession.SingleOrDefaultAsync(x => x.Tenant == _tenant && x.SessionId == sessionId, ct); if (projection is null) return; - _db.Sessions.Remove(projection); + DbSetSession.Remove(projection); } public async Task RevokeChainCascadeAsync(SessionChainId chainId, DateTimeOffset at, CancellationToken ct = default) { ct.ThrowIfCancellationRequested(); - var chainProjection = await _db.Chains + var chainProjection = await DbSetChain .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.ChainId == chainId, ct); if (chainProjection is null) return; - var sessionProjections = await _db.Sessions + var sessionProjections = await DbSetSession .Where(x => x.Tenant == _tenant && x.ChainId == chainId && x.RevokedAt == null) .ToListAsync(ct); @@ -577,19 +581,19 @@ public async Task RevokeRootCascadeAsync(UserKey userKey, DateTimeOffset at, Can { ct.ThrowIfCancellationRequested(); - var rootProjection = await _db.Roots + var rootProjection = await DbSetRoot .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.UserKey == userKey, ct); if (rootProjection is null) return; - var chainProjections = await _db.Chains + var chainProjections = await DbSetChain .Where(x => x.Tenant == _tenant && x.UserKey == userKey) .ToListAsync(ct); foreach (var chainProjection in chainProjections) { - var sessions = await _db.Sessions + var sessions = await DbSetSession .Where(x => x.Tenant == _tenant && x.ChainId == chainProjection.ChainId) .ToListAsync(ct); diff --git a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Stores/EfCoreSessionStoreFactory.cs b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Stores/EfCoreSessionStoreFactory.cs index b64206f1..363e3738 100644 --- a/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Stores/EfCoreSessionStoreFactory.cs +++ b/src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/Stores/EfCoreSessionStoreFactory.cs @@ -1,19 +1,20 @@ using CodeBeam.UltimateAuth.Core.Abstractions; using CodeBeam.UltimateAuth.Core.MultiTenancy; +using Microsoft.EntityFrameworkCore; namespace CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; -internal sealed class EfCoreSessionStoreFactory : ISessionStoreFactory +internal sealed class EfCoreSessionStoreFactory : ISessionStoreFactory where TDbContext : DbContext { - private readonly UAuthSessionDbContext _db; + private readonly TDbContext _db; - public EfCoreSessionStoreFactory(UAuthSessionDbContext db) + public EfCoreSessionStoreFactory(TDbContext db) { _db = db; } public ISessionStore Create(TenantKey tenant) { - return new EfCoreSessionStore(_db, new TenantContext(tenant)); + return new EfCoreSessionStore(_db, new TenantContext(tenant)); } } diff --git a/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Data/UAuthTokenDbContext.cs b/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Data/UAuthTokenDbContext.cs index 09dd3eec..032fc0cd 100644 --- a/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Data/UAuthTokenDbContext.cs +++ b/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Data/UAuthTokenDbContext.cs @@ -1,7 +1,4 @@ -using CodeBeam.UltimateAuth.Core.Domain; -using CodeBeam.UltimateAuth.Core.MultiTenancy; -using CodeBeam.UltimateAuth.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; namespace CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore; @@ -15,57 +12,8 @@ public UAuthTokenDbContext(DbContextOptions options) { } - protected override void OnModelCreating(ModelBuilder b) + protected override void OnModelCreating(ModelBuilder modelBuilder) { - b.Entity(e => - { - e.ToTable("UAuth_RefreshTokens"); - e.HasKey(x => x.Id); - - e.Property(x => x.Version) - .IsConcurrencyToken(); - - e.Property(x => x.Tenant) - .HasConversion( - v => v.Value, - v => TenantKey.FromInternal(v)) - .HasMaxLength(128) - .IsRequired(); - e.Property(x => x.UserKey) - .HasConversion( - v => v.Value, - v => UserKey.FromString(v)) - .HasMaxLength(128) - .IsRequired(); - - e.Property(x => x.TokenId) - .HasConversion( - v => v.Value, - v => TokenId.From(v)) - .IsRequired(); - - e.Property(x => x.TokenHash) - .HasMaxLength(128) - .IsRequired(); - - e.HasIndex(x => new { x.Tenant, x.TokenHash }).IsUnique(); - e.HasIndex(x => new { x.Tenant, x.TokenHash, x.RevokedAt }); - e.HasIndex(x => new { x.Tenant, x.TokenId }); - e.HasIndex(x => new { x.Tenant, x.UserKey }); - e.HasIndex(x => new { x.Tenant, x.SessionId }); - e.HasIndex(x => new { x.Tenant, x.ChainId }); - e.HasIndex(x => new { x.Tenant, x.ExpiresAt }); - e.HasIndex(x => new { x.Tenant, x.ExpiresAt, x.RevokedAt }); - e.HasIndex(x => new { x.Tenant, x.ReplacedByTokenHash }); - - e.Property(x => x.SessionId) - .HasConversion(new AuthSessionIdConverter()); - - e.Property(x => x.ChainId) - .HasConversion(new NullableSessionChainIdConverter()); - - e.Property(x => x.ExpiresAt) - .IsRequired(); - }); + UAuthTokensModelBuilder.Configure(modelBuilder); } } diff --git a/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Data/UAuthTokenModelBuilder.cs b/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Data/UAuthTokenModelBuilder.cs new file mode 100644 index 00000000..f6f1b026 --- /dev/null +++ b/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Data/UAuthTokenModelBuilder.cs @@ -0,0 +1,73 @@ +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.MultiTenancy; +using CodeBeam.UltimateAuth.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore; + +public static class UAuthTokensModelBuilder +{ + public static void Configure(ModelBuilder b) + { + ConfigureRefreshTokens(b); + } + + private static void ConfigureRefreshTokens(ModelBuilder b) + { + b.Entity(e => + { + e.ToTable("UAuth_RefreshTokens"); + + e.HasKey(x => x.Id); + + e.Property(x => x.Version).IsConcurrencyToken(); + + e.Property(x => x.Tenant) + .HasConversion( + v => v.Value, + v => TenantKey.FromInternal(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.UserKey) + .HasConversion( + v => v.Value, + v => UserKey.FromString(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.TokenId) + .HasConversion( + v => v.Value, + v => TokenId.From(v)) + .IsRequired(); + + e.Property(x => x.TokenHash) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.SessionId) + .HasConversion(new AuthSessionIdConverter()); + + e.Property(x => x.ChainId) + .HasConversion(new NullableSessionChainIdConverter()); + + e.Property(x => x.CreatedAt).HasUtcDateTimeOffsetConverter().IsRequired(); + e.Property(x => x.ExpiresAt).HasUtcDateTimeOffsetConverter().IsRequired(); + e.Property(x => x.RevokedAt).HasNullableUtcDateTimeOffsetConverter(); + + e.Property(x => x.ReplacedByTokenHash) + .HasMaxLength(128); + + e.HasIndex(x => new { x.Tenant, x.TokenHash }).IsUnique(); + e.HasIndex(x => new { x.Tenant, x.TokenHash, x.RevokedAt }); + e.HasIndex(x => new { x.Tenant, x.TokenId }); + e.HasIndex(x => new { x.Tenant, x.UserKey }); + e.HasIndex(x => new { x.Tenant, x.SessionId }); + e.HasIndex(x => new { x.Tenant, x.ChainId }); + e.HasIndex(x => new { x.Tenant, x.ExpiresAt }); + e.HasIndex(x => new { x.Tenant, x.ExpiresAt, x.RevokedAt }); + e.HasIndex(x => new { x.Tenant, x.ReplacedByTokenHash }); + }); + } +} \ No newline at end of file diff --git a/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs b/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs index 66a01476..11410052 100644 --- a/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs +++ b/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs @@ -6,10 +6,14 @@ namespace CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore.Extensions; public static class ServiceCollectionExtensions { - public static IServiceCollection AddUltimateAuthTokensEntityFrameworkCore(this IServiceCollection services, Action configureDb) + public static IServiceCollection AddUltimateAuthTokensEntityFrameworkCore(this IServiceCollection services, Action? configureDb = null) where TDbContext : DbContext { - services.AddDbContext(configureDb); - services.AddScoped(); + if (configureDb != null) + { + services.AddDbContext(configureDb); + } + + services.AddScoped>(); return services; } } diff --git a/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Projections/RefreshTokenProjection.cs b/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Projections/RefreshTokenProjection.cs index 09e26055..2e67b489 100644 --- a/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Projections/RefreshTokenProjection.cs +++ b/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Projections/RefreshTokenProjection.cs @@ -22,9 +22,7 @@ public sealed class RefreshTokenProjection public string? ReplacedByTokenHash { get; set; } public DateTimeOffset CreatedAt { get; set; } - public DateTimeOffset ExpiresAt { get; set; } - public DateTimeOffset? RevokedAt { get; set; } public long Version { get; set; } diff --git a/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Stores/EfCoreRefreshTokenStore.cs b/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Stores/EfCoreRefreshTokenStore.cs index 6d4aad70..b4be2ef5 100644 --- a/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Stores/EfCoreRefreshTokenStore.cs +++ b/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Stores/EfCoreRefreshTokenStore.cs @@ -5,18 +5,20 @@ namespace CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore; -internal sealed class EfCoreRefreshTokenStore : IRefreshTokenStore +internal sealed class EfCoreRefreshTokenStore : IRefreshTokenStore where TDbContext : DbContext { - private readonly UAuthTokenDbContext _db; + private readonly TDbContext _db; private readonly TenantKey _tenant; private bool _inTransaction; - public EfCoreRefreshTokenStore(UAuthTokenDbContext db, TenantContext tenant) + public EfCoreRefreshTokenStore(TDbContext db, TenantContext tenant) { _db = db; _tenant = tenant.Tenant; } + private DbSet DbSet => _db.Set(); + public async Task ExecuteAsync(Func action, CancellationToken ct = default) { var strategy = _db.Database.CreateExecutionStrategy(); @@ -88,7 +90,7 @@ public Task StoreAsync(RefreshToken token, CancellationToken ct = default) if (token.Tenant != _tenant) throw new InvalidOperationException("Tenant mismatch."); - _db.RefreshTokens.Add(token.ToProjection()); + DbSet.Add(token.ToProjection()); return Task.CompletedTask; } @@ -97,7 +99,7 @@ public Task StoreAsync(RefreshToken token, CancellationToken ct = default) { ct.ThrowIfCancellationRequested(); - var p = await _db.RefreshTokens + var p = await DbSet .AsNoTracking() .SingleOrDefaultAsync( x => x.Tenant == _tenant && @@ -112,7 +114,7 @@ public Task RevokeAsync(string tokenHash, DateTimeOffset revokedAt, string? repl ct.ThrowIfCancellationRequested(); EnsureTransaction(); - var query = _db.RefreshTokens + var query = DbSet .Where(x => x.Tenant == _tenant && x.TokenHash == tokenHash && @@ -137,7 +139,7 @@ public Task RevokeBySessionAsync(AuthSessionId sessionId, DateTimeOffset revoked ct.ThrowIfCancellationRequested(); EnsureTransaction(); - return _db.RefreshTokens + return DbSet .Where(x => x.Tenant == _tenant && x.SessionId == sessionId && @@ -152,7 +154,7 @@ public Task RevokeByChainAsync(SessionChainId chainId, DateTimeOffset revokedAt, ct.ThrowIfCancellationRequested(); EnsureTransaction(); - return _db.RefreshTokens + return DbSet .Where(x => x.Tenant == _tenant && x.ChainId == chainId && @@ -167,7 +169,7 @@ public Task RevokeAllForUserAsync(UserKey userKey, DateTimeOffset revokedAt, Can ct.ThrowIfCancellationRequested(); EnsureTransaction(); - return _db.RefreshTokens + return DbSet .Where(x => x.Tenant == _tenant && x.UserKey == userKey && diff --git a/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Stores/EfCoreRefreshTokenStoreFactory.cs b/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Stores/EfCoreRefreshTokenStoreFactory.cs index f584beae..9cc94371 100644 --- a/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Stores/EfCoreRefreshTokenStoreFactory.cs +++ b/src/tokens/CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore/Stores/EfCoreRefreshTokenStoreFactory.cs @@ -1,19 +1,20 @@ using CodeBeam.UltimateAuth.Core.Abstractions; using CodeBeam.UltimateAuth.Core.MultiTenancy; +using Microsoft.EntityFrameworkCore; namespace CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore; -internal sealed class EfCoreRefreshTokenStoreFactory : IRefreshTokenStoreFactory +internal sealed class EfCoreRefreshTokenStoreFactory : IRefreshTokenStoreFactory where TDbContext : DbContext { - private readonly UAuthTokenDbContext _db; + private readonly TDbContext _db; - public EfCoreRefreshTokenStoreFactory(UAuthTokenDbContext db) + public EfCoreRefreshTokenStoreFactory(TDbContext db) { _db = db; } public IRefreshTokenStore Create(TenantKey tenant) { - return new EfCoreRefreshTokenStore(_db, new TenantContext(tenant)); + return new EfCoreRefreshTokenStore(_db, new TenantContext(tenant)); } } diff --git a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Data/UAuthUserDbContext.cs b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Data/UAuthUserDbContext.cs index a51e248b..88dbd3f2 100644 --- a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Data/UAuthUserDbContext.cs +++ b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Data/UAuthUserDbContext.cs @@ -1,14 +1,11 @@ -using CodeBeam.UltimateAuth.Core.Domain; -using CodeBeam.UltimateAuth.Core.MultiTenancy; -using CodeBeam.UltimateAuth.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; namespace CodeBeam.UltimateAuth.Users.EntityFrameworkCore; public sealed class UAuthUserDbContext : DbContext { - public DbSet Identifiers => Set(); public DbSet Lifecycles => Set(); + public DbSet Identifiers => Set(); public DbSet Profiles => Set(); public UAuthUserDbContext(DbContextOptions options) @@ -16,128 +13,8 @@ public UAuthUserDbContext(DbContextOptions options) { } - protected override void OnModelCreating(ModelBuilder b) - { - ConfigureIdentifiers(b); - ConfigureLifecycles(b); - ConfigureProfiles(b); - } - - private void ConfigureIdentifiers(ModelBuilder b) - { - b.Entity(e => - { - e.ToTable("UAuth_UserIdentifiers"); - e.HasKey(x => x.Id); - - e.Property(x => x.Version) - .IsConcurrencyToken(); - - e.Property(x => x.Tenant) - .HasConversion( - v => v.Value, - v => TenantKey.FromInternal(v)) - .HasMaxLength(128) - .IsRequired(); - - e.Property(x => x.UserKey) - .HasConversion( - v => v.Value, - v => UserKey.FromString(v)) - .HasMaxLength(128) - .IsRequired(); - - e.Property(x => x.Value) - .HasMaxLength(256) - .IsRequired(); - - e.Property(x => x.NormalizedValue) - .HasMaxLength(256) - .IsRequired(); - - e.HasIndex(x => new { x.Tenant, x.Type, x.NormalizedValue }).IsUnique(); - e.HasIndex(x => new { x.Tenant, x.UserKey }); - e.HasIndex(x => new { x.Tenant, x.UserKey, x.Type, x.IsPrimary }); - e.HasIndex(x => new { x.Tenant, x.UserKey, x.IsPrimary }); - e.HasIndex(x => new { x.Tenant, x.NormalizedValue }); - - e.Property(x => x.CreatedAt).IsRequired() - .HasConversion( - v => v.UtcDateTime, - v => new DateTimeOffset(v, TimeSpan.Zero)); - }); - } - - private void ConfigureLifecycles(ModelBuilder b) - { - b.Entity(e => - { - e.ToTable("UAuth_UserLifecycles"); - e.HasKey(x => x.Id); - - e.Property(x => x.Version) - .IsConcurrencyToken(); - - e.Property(x => x.Tenant) - .HasConversion( - v => v.Value, - v => TenantKey.FromInternal(v)) - .HasMaxLength(128) - .IsRequired(); - - e.Property(x => x.UserKey) - .HasConversion( - v => v.Value, - v => UserKey.FromString(v)) - .HasMaxLength(128) - .IsRequired(); - - e.HasIndex(x => new { x.Tenant, x.UserKey }).IsUnique(); - - e.Property(x => x.SecurityVersion) - .IsRequired(); - - e.Property(x => x.CreatedAt).IsRequired() - .HasConversion( - v => v.UtcDateTime, - v => new DateTimeOffset(v, TimeSpan.Zero)); - }); - } - - private void ConfigureProfiles(ModelBuilder b) + protected override void OnModelCreating(ModelBuilder modelBuilder) { - b.Entity(e => - { - e.ToTable("UAuth_UserProfiles"); - e.HasKey(x => x.Id); - - e.Property(x => x.Version) - .IsConcurrencyToken(); - - e.Property(x => x.Tenant) - .HasConversion( - v => v.Value, - v => TenantKey.FromInternal(v)) - .HasMaxLength(128) - .IsRequired(); - - e.Property(x => x.UserKey) - .HasConversion( - v => v.Value, - v => UserKey.FromString(v)) - .HasMaxLength(128) - .IsRequired(); - - e.HasIndex(x => new { x.Tenant, x.UserKey }); - - e.Property(x => x.Metadata) - .HasConversion(new NullableJsonValueConverter>()) - .Metadata.SetValueComparer(JsonValueComparers.Create>()); - - e.Property(x => x.CreatedAt).IsRequired() - .HasConversion( - v => v.UtcDateTime, - v => new DateTimeOffset(v, TimeSpan.Zero)); - }); + UAuthUsersModelBuilder.Configure(modelBuilder); } -} \ No newline at end of file +} diff --git a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Data/UAuthUsersModelBuilder.cs b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Data/UAuthUsersModelBuilder.cs new file mode 100644 index 00000000..edbf278c --- /dev/null +++ b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Data/UAuthUsersModelBuilder.cs @@ -0,0 +1,122 @@ +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.MultiTenancy; +using CodeBeam.UltimateAuth.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace CodeBeam.UltimateAuth.Users.EntityFrameworkCore; + +public static class UAuthUsersModelBuilder +{ + public static void Configure(ModelBuilder b) + { + ConfigureIdentifiers(b); + ConfigureLifecycles(b); + ConfigureProfiles(b); + } + + private static void ConfigureIdentifiers(ModelBuilder b) + { + b.Entity(e => + { + e.ToTable("UAuth_UserIdentifiers"); + + e.HasKey(x => x.Id); + + e.Property(x => x.Version) + .IsConcurrencyToken(); + + e.Property(x => x.Tenant) + .HasConversion(v => v.Value, v => TenantKey.FromInternal(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.UserKey) + .HasConversion(v => v.Value, v => UserKey.FromString(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.Value) + .HasMaxLength(256) + .IsRequired(); + + e.Property(x => x.NormalizedValue) + .HasMaxLength(256) + .IsRequired(); + + e.Property(x => x.CreatedAt).HasUtcDateTimeOffsetConverter().IsRequired(); + e.Property(x => x.UpdatedAt).HasNullableUtcDateTimeOffsetConverter(); + e.Property(x => x.DeletedAt).HasNullableUtcDateTimeOffsetConverter(); + + e.HasIndex(x => new { x.Tenant, x.Type, x.NormalizedValue }).IsUnique(); + e.HasIndex(x => new { x.Tenant, x.UserKey }); + e.HasIndex(x => new { x.Tenant, x.UserKey, x.Type, x.IsPrimary }); + e.HasIndex(x => new { x.Tenant, x.UserKey, x.IsPrimary }); + e.HasIndex(x => new { x.Tenant, x.NormalizedValue }); + }); + } + + private static void ConfigureLifecycles(ModelBuilder b) + { + b.Entity(e => + { + e.ToTable("UAuth_UserLifecycles"); + + e.HasKey(x => x.Id); + + e.Property(x => x.Version) + .IsConcurrencyToken(); + + e.Property(x => x.Tenant) + .HasConversion(v => v.Value, v => TenantKey.FromInternal(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.UserKey) + .HasConversion(v => v.Value, v => UserKey.FromString(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.CreatedAt).HasUtcDateTimeOffsetConverter().IsRequired(); + e.Property(x => x.UpdatedAt).HasNullableUtcDateTimeOffsetConverter(); + e.Property(x => x.DeletedAt).HasNullableUtcDateTimeOffsetConverter(); + + e.Property(x => x.SecurityVersion) + .IsRequired(); + + e.HasIndex(x => new { x.Tenant, x.UserKey }).IsUnique(); + }); + } + + private static void ConfigureProfiles(ModelBuilder b) + { + b.Entity(e => + { + e.ToTable("UAuth_UserProfiles"); + + e.HasKey(x => x.Id); + + e.Property(x => x.Version) + .IsConcurrencyToken(); + + e.Property(x => x.Tenant) + .HasConversion(v => v.Value, v => TenantKey.FromInternal(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.UserKey) + .HasConversion(v => v.Value, v => UserKey.FromString(v)) + .HasMaxLength(128) + .IsRequired(); + + e.Property(x => x.Metadata) + .HasConversion(new NullableJsonValueConverter>()) + .Metadata.SetValueComparer(JsonValueComparers.Create>()); + + e.Property(x => x.CreatedAt).HasUtcDateTimeOffsetConverter().IsRequired(); + e.Property(x => x.UpdatedAt).HasNullableUtcDateTimeOffsetConverter(); + e.Property(x => x.DeletedAt).HasNullableUtcDateTimeOffsetConverter(); + + e.HasIndex(x => new { x.Tenant, x.UserKey }); + }); + } +} \ No newline at end of file diff --git a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs index 656bc78b..3c9162ab 100644 --- a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs +++ b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs @@ -6,12 +6,16 @@ namespace CodeBeam.UltimateAuth.Users.EntityFrameworkCore.Extensions; public static class ServiceCollectionExtensions { - public static IServiceCollection AddUltimateAuthUsersEntityFrameworkCore(this IServiceCollection services, Action configureDb) + public static IServiceCollection AddUltimateAuthUsersEntityFrameworkCore(this IServiceCollection services, Action? configureDb = null) where TDbContext : DbContext { - services.AddDbContext(configureDb); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + if (configureDb is not null) + { + services.AddDbContext(configureDb); + } + + services.AddScoped>(); + services.AddScoped>(); + services.AddScoped>(); return services; } } diff --git a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Projections/UserProfileProjection.cs b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Projections/UserProfileProjection.cs index 654b96af..90dfed20 100644 --- a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Projections/UserProfileProjection.cs +++ b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Projections/UserProfileProjection.cs @@ -32,9 +32,7 @@ public sealed class UserProfileProjection public Dictionary? Metadata { get; set; } public DateTimeOffset CreatedAt { get; set; } - public DateTimeOffset? UpdatedAt { get; set; } - public DateTimeOffset? DeletedAt { get; set; } public long Version { get; set; } diff --git a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EFCoreUserProfileStoreFactory.cs b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EFCoreUserProfileStoreFactory.cs index 8ac0d8c7..e3c4147e 100644 --- a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EFCoreUserProfileStoreFactory.cs +++ b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EFCoreUserProfileStoreFactory.cs @@ -1,19 +1,20 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Users.Reference; +using Microsoft.EntityFrameworkCore; namespace CodeBeam.UltimateAuth.Users.EntityFrameworkCore; -internal sealed class EfCoreUserProfileStoreFactory : IUserProfileStoreFactory +internal sealed class EfCoreUserProfileStoreFactory : IUserProfileStoreFactory where TDbContext : DbContext { - private readonly UAuthUserDbContext _db; + private readonly TDbContext _db; - public EfCoreUserProfileStoreFactory(UAuthUserDbContext db) + public EfCoreUserProfileStoreFactory(TDbContext db) { _db = db; } public IUserProfileStore Create(TenantKey tenant) { - return new EfCoreUserProfileStore(_db, new TenantContext(tenant)); + return new EfCoreUserProfileStore(_db, new TenantContext(tenant)); } } diff --git a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserIdentifierStore.cs b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserIdentifierStore.cs index 5f85b529..e52bb943 100644 --- a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserIdentifierStore.cs +++ b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserIdentifierStore.cs @@ -8,22 +8,24 @@ namespace CodeBeam.UltimateAuth.Users.EntityFrameworkCore; -internal sealed class EfCoreUserIdentifierStore : IUserIdentifierStore +internal sealed class EfCoreUserIdentifierStore : IUserIdentifierStore where TDbContext : DbContext { - private readonly UAuthUserDbContext _db; + private readonly TDbContext _db; private readonly TenantKey _tenant; - public EfCoreUserIdentifierStore(UAuthUserDbContext db, TenantContext tenant) + public EfCoreUserIdentifierStore(TDbContext db, TenantContext tenant) { _db = db; _tenant = tenant.Tenant; } + private DbSet DbSet => _db.Set(); + public async Task ExistsAsync(Guid key, CancellationToken ct = default) { ct.ThrowIfCancellationRequested(); - return await _db.Identifiers + return await DbSet .AnyAsync(x => x.Id == key && x.Tenant == _tenant, @@ -34,7 +36,7 @@ public async Task ExistsAsync(IdentifierExistenceQuer { ct.ThrowIfCancellationRequested(); - var q = _db.Identifiers + var q = DbSet .AsNoTracking() .Where(x => x.Tenant == _tenant && @@ -82,7 +84,7 @@ public async Task ExistsAsync(IdentifierExistenceQuer { ct.ThrowIfCancellationRequested(); - var projection = await _db.Identifiers + var projection = await DbSet .AsNoTracking() .SingleOrDefaultAsync(x => x.Id == key && @@ -96,7 +98,7 @@ public async Task ExistsAsync(IdentifierExistenceQuer { ct.ThrowIfCancellationRequested(); - var projection = await _db.Identifiers + var projection = await DbSet .AsNoTracking() .SingleOrDefaultAsync( x => @@ -113,7 +115,7 @@ public async Task ExistsAsync(IdentifierExistenceQuer { ct.ThrowIfCancellationRequested(); - var projection = await _db.Identifiers + var projection = await DbSet .AsNoTracking() .SingleOrDefaultAsync(x => x.Id == id && @@ -127,7 +129,7 @@ public async Task> GetByUserAsync(UserKey userKey, { ct.ThrowIfCancellationRequested(); - var projections = await _db.Identifiers + var projections = await DbSet .AsNoTracking() .Where(x => x.Tenant == _tenant && @@ -152,7 +154,7 @@ public async Task AddAsync(UserIdentifier entity, CancellationToken ct = default if (entity.IsPrimary) { - await _db.Identifiers + await DbSet .Where(x => x.Tenant == _tenant && x.UserKey == entity.UserKey && @@ -164,7 +166,7 @@ await _db.Identifiers ct); } - _db.Identifiers.Add(projection); + DbSet.Add(projection); await _db.SaveChangesAsync(ct); await tx.CommitAsync(ct); @@ -178,7 +180,7 @@ public async Task SaveAsync(UserIdentifier entity, long expectedVersion, Cancell if (entity.IsPrimary) { - await _db.Identifiers + await DbSet .Where(x => x.Tenant == _tenant && x.UserKey == entity.UserKey && @@ -191,7 +193,7 @@ await _db.Identifiers ct); } - var existing = await _db.Identifiers + var existing = await DbSet .SingleOrDefaultAsync(x => x.Id == entity.Id && x.Tenant == _tenant, @@ -215,7 +217,7 @@ public async Task DeleteAsync(Guid key, long expectedVersion, DeleteMode mode, D { ct.ThrowIfCancellationRequested(); - var projection = await _db.Identifiers + var projection = await DbSet .SingleOrDefaultAsync(x => x.Id == key && x.Tenant == _tenant, @@ -229,7 +231,7 @@ public async Task DeleteAsync(Guid key, long expectedVersion, DeleteMode mode, D if (mode == DeleteMode.Hard) { - _db.Identifiers.Remove(projection); + DbSet.Remove(projection); } else { @@ -247,7 +249,7 @@ public async Task DeleteByUserAsync(UserKey userKey, DeleteMode mode, DateTimeOf if (mode == DeleteMode.Hard) { - await _db.Identifiers + await DbSet .Where(x => x.Tenant == _tenant && x.UserKey == userKey) @@ -256,7 +258,7 @@ await _db.Identifiers return; } - await _db.Identifiers + await DbSet .Where(x => x.Tenant == _tenant && x.UserKey == userKey && @@ -275,7 +277,7 @@ public async Task> GetByUsersAsync(IReadOnlyList(); - var projections = await _db.Identifiers + var projections = await DbSet .AsNoTracking() .Where(x => x.Tenant == _tenant && @@ -299,7 +301,7 @@ public async Task> QueryAsync(UserIdentifierQuery qu var normalized = query.Normalize(); - var baseQuery = _db.Identifiers + var baseQuery = DbSet .AsNoTracking() .Where(x => x.Tenant == _tenant && diff --git a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserIdentifierStoreFactory.cs b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserIdentifierStoreFactory.cs index fe6cdbdf..2d343b42 100644 --- a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserIdentifierStoreFactory.cs +++ b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserIdentifierStoreFactory.cs @@ -4,17 +4,17 @@ namespace CodeBeam.UltimateAuth.Users.EntityFrameworkCore; -internal sealed class EfCoreUserIdentifierStoreFactory : IUserIdentifierStoreFactory +internal sealed class EfCoreUserIdentifierStoreFactory : IUserIdentifierStoreFactory where TDbContext : DbContext { - private readonly UAuthUserDbContext _db; + private readonly TDbContext _db; - public EfCoreUserIdentifierStoreFactory(UAuthUserDbContext db) + public EfCoreUserIdentifierStoreFactory(TDbContext db) { _db = db; } public IUserIdentifierStore Create(TenantKey tenant) { - return new EfCoreUserIdentifierStore(_db, new TenantContext(tenant)); + return new EfCoreUserIdentifierStore(_db, new TenantContext(tenant)); } } diff --git a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserLifecycleStore.cs b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserLifecycleStore.cs index eea3a8c8..63c8ef35 100644 --- a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserLifecycleStore.cs +++ b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserLifecycleStore.cs @@ -6,22 +6,24 @@ namespace CodeBeam.UltimateAuth.Users.EntityFrameworkCore; -internal sealed class EfCoreUserLifecycleStore : IUserLifecycleStore +internal sealed class EfCoreUserLifecycleStore : IUserLifecycleStore where TDbContext : DbContext { - private readonly UAuthUserDbContext _db; + private readonly TDbContext _db; private readonly TenantKey _tenant; - public EfCoreUserLifecycleStore(UAuthUserDbContext db, TenantContext tenant) + public EfCoreUserLifecycleStore(TDbContext db, TenantContext tenant) { _db = db; _tenant = tenant.Tenant; } + private DbSet DbSet => _db.Set(); + public async Task GetAsync(UserLifecycleKey key, CancellationToken ct = default) { ct.ThrowIfCancellationRequested(); - var projection = await _db.Lifecycles + var projection = await DbSet .AsNoTracking() .SingleOrDefaultAsync( x => x.Tenant == _tenant && @@ -35,7 +37,7 @@ public async Task ExistsAsync(UserLifecycleKey key, CancellationToken ct = { ct.ThrowIfCancellationRequested(); - return await _db.Lifecycles + return await DbSet .AnyAsync( x => x.Tenant == _tenant && x.UserKey == key.UserKey, @@ -51,7 +53,7 @@ public async Task AddAsync(UserLifecycle entity, CancellationToken ct = default) var projection = entity.ToProjection(); - _db.Lifecycles.Add(projection); + DbSet.Add(projection); await _db.SaveChangesAsync(ct); } @@ -60,7 +62,7 @@ public async Task SaveAsync(UserLifecycle entity, long expectedVersion, Cancella { ct.ThrowIfCancellationRequested(); - var existing = await _db.Lifecycles + var existing = await DbSet .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.UserKey == entity.UserKey, @@ -82,7 +84,7 @@ public async Task DeleteAsync(UserLifecycleKey key, long expectedVersion, Delete { ct.ThrowIfCancellationRequested(); - var projection = await _db.Lifecycles + var projection = await DbSet .SingleOrDefaultAsync( x => x.Tenant == _tenant && x.UserKey == key.UserKey, @@ -96,7 +98,7 @@ public async Task DeleteAsync(UserLifecycleKey key, long expectedVersion, Delete if (mode == DeleteMode.Hard) { - _db.Lifecycles.Remove(projection); + DbSet.Remove(projection); } else { @@ -113,7 +115,7 @@ public async Task> QueryAsync(UserLifecycleQuery quer var normalized = query.Normalize(); - var baseQuery = _db.Lifecycles + var baseQuery = DbSet .AsNoTracking() .Where(x => x.Tenant == _tenant); diff --git a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserLifecycleStoreFactory.cs b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserLifecycleStoreFactory.cs index 9a346c7a..7e5c4d44 100644 --- a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserLifecycleStoreFactory.cs +++ b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserLifecycleStoreFactory.cs @@ -4,17 +4,17 @@ namespace CodeBeam.UltimateAuth.Users.EntityFrameworkCore; -internal sealed class EfCoreUserLifecycleStoreFactory : IUserLifecycleStoreFactory +internal sealed class EfCoreUserLifecycleStoreFactory : IUserLifecycleStoreFactory where TDbContext : DbContext { - private readonly UAuthUserDbContext _db; + private readonly TDbContext _db; - public EfCoreUserLifecycleStoreFactory(UAuthUserDbContext db) + public EfCoreUserLifecycleStoreFactory(TDbContext db) { _db = db; } public IUserLifecycleStore Create(TenantKey tenant) { - return new EfCoreUserLifecycleStore(_db, new TenantContext(tenant)); + return new EfCoreUserLifecycleStore(_db, new TenantContext(tenant)); } } diff --git a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserProfileStore.cs b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserProfileStore.cs index a3772ddb..9623dc92 100644 --- a/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserProfileStore.cs +++ b/src/users/CodeBeam.UltimateAuth.Users.EntityFrameworkCore/Stores/EfCoreUserProfileStore.cs @@ -7,22 +7,24 @@ namespace CodeBeam.UltimateAuth.Users.EntityFrameworkCore; -internal sealed class EfCoreUserProfileStore : IUserProfileStore +internal sealed class EfCoreUserProfileStore : IUserProfileStore where TDbContext : DbContext { - private readonly UAuthUserDbContext _db; + private readonly TDbContext _db; private readonly TenantKey _tenant; - public EfCoreUserProfileStore(UAuthUserDbContext db, TenantContext tenant) + public EfCoreUserProfileStore(TDbContext db, TenantContext tenant) { _db = db; _tenant = tenant.Tenant; } + private DbSet DbSet => _db.Set(); + public async Task GetAsync(UserProfileKey key, CancellationToken ct = default) { ct.ThrowIfCancellationRequested(); - var projection = await _db.Profiles + var projection = await DbSet .AsNoTracking() .SingleOrDefaultAsync(x => x.Tenant == _tenant && @@ -36,7 +38,7 @@ public async Task ExistsAsync(UserProfileKey key, CancellationToken ct = d { ct.ThrowIfCancellationRequested(); - return await _db.Profiles + return await DbSet .AnyAsync(x => x.Tenant == _tenant && x.UserKey == key.UserKey, @@ -52,7 +54,7 @@ public async Task AddAsync(UserProfile entity, CancellationToken ct = default) if (entity.Version != 0) throw new InvalidOperationException("New profile must have version 0."); - _db.Profiles.Add(projection); + DbSet.Add(projection); await _db.SaveChangesAsync(ct); } @@ -61,7 +63,7 @@ public async Task SaveAsync(UserProfile entity, long expectedVersion, Cancellati { ct.ThrowIfCancellationRequested(); - var existing = await _db.Profiles + var existing = await DbSet .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.UserKey == entity.UserKey, @@ -83,7 +85,7 @@ public async Task DeleteAsync(UserProfileKey key, long expectedVersion, DeleteMo { ct.ThrowIfCancellationRequested(); - var projection = await _db.Profiles + var projection = await DbSet .SingleOrDefaultAsync(x => x.Tenant == _tenant && x.UserKey == key.UserKey, @@ -97,7 +99,7 @@ public async Task DeleteAsync(UserProfileKey key, long expectedVersion, DeleteMo if (mode == DeleteMode.Hard) { - _db.Profiles.Remove(projection); + DbSet.Remove(projection); } else { @@ -114,7 +116,7 @@ public async Task> QueryAsync(UserProfileQuery query, C var normalized = query.Normalize(); - var baseQuery = _db.Profiles + var baseQuery = DbSet .AsNoTracking() .Where(x => x.Tenant == _tenant); @@ -166,7 +168,7 @@ public async Task> GetByUsersAsync(IReadOnlyList x.Tenant == _tenant) .Where(x => userKeys.Contains(x.UserKey)) diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreAuthenticationStoreTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreAuthenticationStoreTests.cs index 9144d984..35d5a069 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreAuthenticationStoreTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreAuthenticationStoreTests.cs @@ -22,7 +22,7 @@ public async Task Add_And_Get_Should_Work() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreAuthenticationSecurityStateStore(db, new TenantContext(tenant)); + var store = new EfCoreAuthenticationSecurityStateStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); @@ -50,13 +50,13 @@ public async Task Update_With_RegisterFailure_Should_Increment_Version() await using (var db1 = CreateDb(connection)) { - var store = new EfCoreAuthenticationSecurityStateStore(db1, new TenantContext(tenant)); + var store = new EfCoreAuthenticationSecurityStateStore(db1, new TenantContext(tenant)); await store.AddAsync(state); } await using (var db2 = CreateDb(connection)) { - var store = new EfCoreAuthenticationSecurityStateStore(db2, new TenantContext(tenant)); + var store = new EfCoreAuthenticationSecurityStateStore(db2, new TenantContext(tenant)); var existing = await store.GetAsync(userKey, AuthenticationSecurityScope.Account, null); var updated = existing!.RegisterFailure( @@ -69,7 +69,7 @@ public async Task Update_With_RegisterFailure_Should_Increment_Version() await using (var db3 = CreateDb(connection)) { - var store = new EfCoreAuthenticationSecurityStateStore(db3, new TenantContext(tenant)); + var store = new EfCoreAuthenticationSecurityStateStore(db3, new TenantContext(tenant)); var result = await store.GetAsync(userKey, AuthenticationSecurityScope.Account, null); Assert.Equal(1, result!.SecurityVersion); @@ -84,7 +84,7 @@ public async Task Update_With_Wrong_Version_Should_Throw() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreAuthenticationSecurityStateStore(db, new TenantContext(tenant)); + var store = new EfCoreAuthenticationSecurityStateStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); var state = AuthenticationSecurityState.CreateAccount(tenant, userKey); @@ -107,13 +107,13 @@ public async Task RegisterSuccess_Should_Clear_Failures() await using (var db1 = CreateDb(connection)) { - var store = new EfCoreAuthenticationSecurityStateStore(db1, new TenantContext(tenant)); + var store = new EfCoreAuthenticationSecurityStateStore(db1, new TenantContext(tenant)); await store.AddAsync(state); } await using (var db2 = CreateDb(connection)) { - var store = new EfCoreAuthenticationSecurityStateStore(db2, new TenantContext(tenant)); + var store = new EfCoreAuthenticationSecurityStateStore(db2, new TenantContext(tenant)); var existing = await store.GetAsync(userKey, AuthenticationSecurityScope.Account, null); var updated = existing!.RegisterSuccess(); await store.UpdateAsync(updated, expectedVersion: 1); @@ -121,7 +121,7 @@ public async Task RegisterSuccess_Should_Clear_Failures() await using (var db3 = CreateDb(connection)) { - var store = new EfCoreAuthenticationSecurityStateStore(db3, new TenantContext(tenant)); + var store = new EfCoreAuthenticationSecurityStateStore(db3, new TenantContext(tenant)); var result = await store.GetAsync(userKey, AuthenticationSecurityScope.Account, null); Assert.Equal(0, result!.FailedAttempts); @@ -140,7 +140,7 @@ public async Task BeginReset_And_Consume_Should_Work() await using (var db1 = CreateDb(connection)) { - var store = new EfCoreAuthenticationSecurityStateStore(db1, new TenantContext(tenant)); + var store = new EfCoreAuthenticationSecurityStateStore(db1, new TenantContext(tenant)); await store.AddAsync(state); } @@ -148,7 +148,7 @@ public async Task BeginReset_And_Consume_Should_Work() await using (var db2 = CreateDb(connection)) { - var store = new EfCoreAuthenticationSecurityStateStore(db2, new TenantContext(tenant)); + var store = new EfCoreAuthenticationSecurityStateStore(db2, new TenantContext(tenant)); var existing = await store.GetAsync(userKey, AuthenticationSecurityScope.Account, null); var updated = existing!.BeginReset("hash", now, TimeSpan.FromMinutes(10)); await store.UpdateAsync(updated, expectedVersion: 0); @@ -156,7 +156,7 @@ public async Task BeginReset_And_Consume_Should_Work() await using (var db3 = CreateDb(connection)) { - var store = new EfCoreAuthenticationSecurityStateStore(db3, new TenantContext(tenant)); + var store = new EfCoreAuthenticationSecurityStateStore(db3, new TenantContext(tenant)); var existing = await store.GetAsync(userKey, AuthenticationSecurityScope.Account, null); var consumed = existing!.ConsumeReset(DateTimeOffset.UtcNow); await store.UpdateAsync(consumed, expectedVersion: 1); @@ -164,7 +164,7 @@ public async Task BeginReset_And_Consume_Should_Work() await using (var db4 = CreateDb(connection)) { - var store = new EfCoreAuthenticationSecurityStateStore(db4, new TenantContext(tenant)); + var store = new EfCoreAuthenticationSecurityStateStore(db4, new TenantContext(tenant)); var result = await store.GetAsync(userKey, AuthenticationSecurityScope.Account, null); Assert.NotNull(result!.ResetConsumedAt); } @@ -177,7 +177,7 @@ public async Task Delete_Should_Work() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreAuthenticationSecurityStateStore(db, new TenantContext(tenant)); + var store = new EfCoreAuthenticationSecurityStateStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); @@ -201,8 +201,8 @@ public async Task Should_Not_See_Data_From_Other_Tenant() var tenant1 = TenantKeys.Single; var tenant2 = TenantKey.FromInternal("tenant-2"); - var store1 = new EfCoreAuthenticationSecurityStateStore(db, new TenantContext(tenant1)); - var store2 = new EfCoreAuthenticationSecurityStateStore(db, new TenantContext(tenant2)); + var store1 = new EfCoreAuthenticationSecurityStateStore(db, new TenantContext(tenant1)); + var store2 = new EfCoreAuthenticationSecurityStateStore(db, new TenantContext(tenant2)); var userKey = UserKey.FromGuid(Guid.NewGuid()); var state = AuthenticationSecurityState.CreateAccount(tenant1, userKey); diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreCredentialStoreTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreCredentialStoreTests.cs index 36f0a519..23e651d1 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreCredentialStoreTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreCredentialStoreTests.cs @@ -24,7 +24,7 @@ public async Task Add_And_Get_Should_Work() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCorePasswordCredentialStore(db, new TenantContext(tenant)); + var store = new EfCorePasswordCredentialStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); @@ -52,7 +52,7 @@ public async Task Exists_Should_Return_True_When_Exists() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCorePasswordCredentialStore(db, new TenantContext(tenant)); + var store = new EfCorePasswordCredentialStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); @@ -90,13 +90,13 @@ public async Task Save_Should_Increment_Version() await using (var db1 = CreateDb(connection)) { - var store1 = new EfCorePasswordCredentialStore(db1, new TenantContext(tenant)); + var store1 = new EfCorePasswordCredentialStore(db1, new TenantContext(tenant)); await store1.AddAsync(credential); } await using (var db2 = CreateDb(connection)) { - var store2 = new EfCorePasswordCredentialStore(db2, new TenantContext(tenant)); + var store2 = new EfCorePasswordCredentialStore(db2, new TenantContext(tenant)); var existing = await store2.GetAsync(new CredentialKey(tenant, credential.Id)); var updated = existing!.ChangeSecret("new_hash", DateTimeOffset.UtcNow); await store2.SaveAsync(updated, expectedVersion: 0); @@ -104,7 +104,7 @@ public async Task Save_Should_Increment_Version() await using (var db3 = CreateDb(connection)) { - var store3 = new EfCorePasswordCredentialStore(db3, new TenantContext(tenant)); + var store3 = new EfCorePasswordCredentialStore(db3, new TenantContext(tenant)); var result = await store3.GetAsync(new CredentialKey(tenant, credential.Id)); Assert.Equal(1, result!.Version); @@ -131,13 +131,13 @@ public async Task Save_With_Wrong_Version_Should_Throw() await using (var db1 = CreateDb(connection)) { - var store1 = new EfCorePasswordCredentialStore(db1, new TenantContext(tenant)); + var store1 = new EfCorePasswordCredentialStore(db1, new TenantContext(tenant)); await store1.AddAsync(credential); } await using (var db2 = CreateDb(connection)) { - var store2 = new EfCorePasswordCredentialStore(db2, new TenantContext(tenant)); + var store2 = new EfCorePasswordCredentialStore(db2, new TenantContext(tenant)); var existing = await store2.GetAsync(new CredentialKey(tenant, credential.Id)); var updated = existing!.ChangeSecret("new_hash", DateTimeOffset.UtcNow); @@ -156,8 +156,8 @@ public async Task Should_Not_See_Data_From_Other_Tenant() var tenant1 = TenantKeys.Single; var tenant2 = TenantKey.FromInternal("tenant-2"); - var store1 = new EfCorePasswordCredentialStore(db, new TenantContext(tenant1)); - var store2 = new EfCorePasswordCredentialStore(db, new TenantContext(tenant2)); + var store1 = new EfCorePasswordCredentialStore(db, new TenantContext(tenant1)); + var store2 = new EfCorePasswordCredentialStore(db, new TenantContext(tenant2)); var userKey = UserKey.FromGuid(Guid.NewGuid()); @@ -183,7 +183,7 @@ public async Task Soft_Delete_Should_Work() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCorePasswordCredentialStore(db, new TenantContext(tenant)); + var store = new EfCorePasswordCredentialStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); @@ -229,13 +229,13 @@ public async Task Revoke_Should_Persist() await using (var db1 = CreateDb(connection)) { - var store1 = new EfCorePasswordCredentialStore(db1, new TenantContext(tenant)); + var store1 = new EfCorePasswordCredentialStore(db1, new TenantContext(tenant)); await store1.AddAsync(credential); } await using (var db2 = CreateDb(connection)) { - var store2 = new EfCorePasswordCredentialStore(db2, new TenantContext(tenant)); + var store2 = new EfCorePasswordCredentialStore(db2, new TenantContext(tenant)); var existing = await store2.GetAsync(new CredentialKey(tenant, credential.Id)); var revoked = existing!.Revoke(DateTimeOffset.UtcNow); await store2.SaveAsync(revoked, expectedVersion: 0); @@ -243,7 +243,7 @@ public async Task Revoke_Should_Persist() await using (var db3 = CreateDb(connection)) { - var store3 = new EfCorePasswordCredentialStore(db3, new TenantContext(tenant)); + var store3 = new EfCorePasswordCredentialStore(db3, new TenantContext(tenant)); var result = await store3.GetAsync(new CredentialKey(tenant, credential.Id)); Assert.True(result!.IsRevoked); @@ -269,13 +269,13 @@ public async Task ChangeSecret_Should_Update_SecurityState() await using (var db1 = CreateDb(connection)) { - var store1 = new EfCorePasswordCredentialStore(db1, new TenantContext(tenant)); + var store1 = new EfCorePasswordCredentialStore(db1, new TenantContext(tenant)); await store1.AddAsync(credential); } await using (var db2 = CreateDb(connection)) { - var store2 = new EfCorePasswordCredentialStore(db2, new TenantContext(tenant)); + var store2 = new EfCorePasswordCredentialStore(db2, new TenantContext(tenant)); var existing = await store2.GetAsync(new CredentialKey(tenant, credential.Id)); var updated = existing!.ChangeSecret("new_hash", DateTimeOffset.UtcNow); @@ -284,7 +284,7 @@ public async Task ChangeSecret_Should_Update_SecurityState() await using (var db3 = CreateDb(connection)) { - var store3 = new EfCorePasswordCredentialStore(db3, new TenantContext(tenant)); + var store3 = new EfCorePasswordCredentialStore(db3, new TenantContext(tenant)); var result = await store3.GetAsync(new CredentialKey(tenant, credential.Id)); Assert.Equal("new_hash", result!.SecretHash); diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreRoleStoreTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreRoleStoreTests.cs index 0332e7f6..8418e86c 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreRoleStoreTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreRoleStoreTests.cs @@ -24,7 +24,7 @@ public async Task Add_And_Get_Should_Work() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreRoleStore(db, new TenantContext(tenant)); var role = Role.Create( null, @@ -49,7 +49,7 @@ public async Task Add_With_Duplicate_Name_Should_Throw() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreRoleStore(db, new TenantContext(tenant)); var role1 = Role.Create(null, tenant, "admin", null, DateTimeOffset.UtcNow); var role2 = Role.Create(null, tenant, "ADMIN", null, DateTimeOffset.UtcNow); @@ -69,7 +69,7 @@ public async Task Save_Should_Increment_Version() await using (var db = CreateDb(connection)) { - var store = new EfCoreRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreRoleStore(db, new TenantContext(tenant)); var role = Role.Create(null, tenant, "admin", null, DateTimeOffset.UtcNow); roleId = role.Id; await store.AddAsync(role); @@ -77,7 +77,7 @@ public async Task Save_Should_Increment_Version() await using (var db = CreateDb(connection)) { - var store = new EfCoreRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreRoleStore(db, new TenantContext(tenant)); var existing = await store.GetAsync(new RoleKey(tenant, roleId)); var updated = existing!.Rename("admin2", DateTimeOffset.UtcNow); await store.SaveAsync(updated, expectedVersion: 0); @@ -85,7 +85,7 @@ public async Task Save_Should_Increment_Version() await using (var db = CreateDb(connection)) { - var store = new EfCoreRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreRoleStore(db, new TenantContext(tenant)); var result = await store.GetAsync(new RoleKey(tenant, roleId)); Assert.Equal(1, result!.Version); @@ -103,7 +103,7 @@ public async Task Save_With_Wrong_Version_Should_Throw() await using (var db = CreateDb(connection)) { - var store = new EfCoreRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreRoleStore(db, new TenantContext(tenant)); var role = Role.Create(null, tenant, "admin", null, DateTimeOffset.UtcNow); roleId = role.Id; await store.AddAsync(role); @@ -111,7 +111,7 @@ public async Task Save_With_Wrong_Version_Should_Throw() await using (var db = CreateDb(connection)) { - var store = new EfCoreRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreRoleStore(db, new TenantContext(tenant)); var existing = await store.GetAsync(new RoleKey(tenant, roleId)); var updated = existing!.Rename("admin2", DateTimeOffset.UtcNow); @@ -131,7 +131,7 @@ public async Task Rename_To_Existing_Name_Should_Throw() await using (var db = CreateDb(connection)) { - var store = new EfCoreRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreRoleStore(db, new TenantContext(tenant)); var role1 = Role.Create(null, tenant, "admin", null, DateTimeOffset.UtcNow); var role2 = Role.Create(null, tenant, "user", null, DateTimeOffset.UtcNow); role1Id = role1.Id; @@ -142,7 +142,7 @@ public async Task Rename_To_Existing_Name_Should_Throw() await using (var db = CreateDb(connection)) { - var store = new EfCoreRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreRoleStore(db, new TenantContext(tenant)); var role = await store.GetAsync(new RoleKey(tenant, role2Id)); var updated = role!.Rename("admin", DateTimeOffset.UtcNow); @@ -160,7 +160,7 @@ public async Task Save_Should_Replace_Permissions() await using (var db = CreateDb(connection)) { - var store = new EfCoreRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreRoleStore(db, new TenantContext(tenant)); var role = Role.Create( null, @@ -176,7 +176,7 @@ public async Task Save_Should_Replace_Permissions() await using (var db = CreateDb(connection)) { - var store = new EfCoreRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreRoleStore(db, new TenantContext(tenant)); var existing = await store.GetAsync(new RoleKey(tenant, roleId)); var updated = existing!.SetPermissions( new[] @@ -189,7 +189,7 @@ public async Task Save_Should_Replace_Permissions() await using (var db = CreateDb(connection)) { - var store = new EfCoreRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreRoleStore(db, new TenantContext(tenant)); var result = await store.GetAsync(new RoleKey(tenant, roleId)); Assert.Single(result!.Permissions); @@ -207,7 +207,7 @@ public async Task Soft_Delete_Should_Work() await using (var db = CreateDb(connection)) { - var store = new EfCoreRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreRoleStore(db, new TenantContext(tenant)); var role = Role.Create(null, tenant, "admin", null, DateTimeOffset.UtcNow); roleId = role.Id; await store.AddAsync(role); @@ -215,13 +215,13 @@ public async Task Soft_Delete_Should_Work() await using (var db = CreateDb(connection)) { - var store = new EfCoreRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreRoleStore(db, new TenantContext(tenant)); await store.DeleteAsync(new RoleKey(tenant, roleId), 0, DeleteMode.Soft, DateTimeOffset.UtcNow); } await using (var db = CreateDb(connection)) { - var store = new EfCoreRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreRoleStore(db, new TenantContext(tenant)); var result = await store.GetAsync(new RoleKey(tenant, roleId)); Assert.NotNull(result!.DeletedAt); } @@ -234,7 +234,7 @@ public async Task Query_Should_Filter_And_Page() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreRoleStore(db, new TenantContext(tenant)); await store.AddAsync(Role.Create(null, tenant, "admin", null, DateTimeOffset.UtcNow)); await store.AddAsync(Role.Create(null, tenant, "user", null, DateTimeOffset.UtcNow)); diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreSessionStoreTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreSessionStoreTests.cs index 3970a4ed..ac3d32c5 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreSessionStoreTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreSessionStoreTests.cs @@ -24,7 +24,7 @@ public async Task Create_And_Get_Session_Should_Work() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); @@ -90,7 +90,7 @@ public async Task Session_Should_Persist_DeviceContext() await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var root = UAuthSessionRoot.Create(tenant, userKey, DateTimeOffset.UtcNow); @@ -127,7 +127,7 @@ await store.ExecuteAsync(async ct => await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var result = await store.GetSessionAsync(sessionId); @@ -167,7 +167,7 @@ public async Task Session_Should_Persist_Claims_And_Metadata() await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var root = UAuthSessionRoot.Create(tenant, userKey, DateTimeOffset.UtcNow); @@ -204,7 +204,7 @@ await store.ExecuteAsync(async ct => await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var result = await store.GetSessionAsync(sessionId); @@ -234,7 +234,7 @@ public async Task Revoke_Session_Should_Work() await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var root = UAuthSessionRoot.Create(tenant, userKey, DateTimeOffset.UtcNow); @@ -288,7 +288,7 @@ public async Task Should_Not_See_Session_From_Other_Tenant() await using (var db = CreateDb(connection)) { - var store1 = new EfCoreSessionStore(db, new TenantContext(tenant1)); + var store1 = new EfCoreSessionStore(db, new TenantContext(tenant1)); var root = UAuthSessionRoot.Create(tenant1, userKey, DateTimeOffset.UtcNow); @@ -325,7 +325,7 @@ await store1.ExecuteAsync(async ct => await using (var db = CreateDb(connection)) { - var store2 = new EfCoreSessionStore(db, new TenantContext(tenant2)); + var store2 = new EfCoreSessionStore(db, new TenantContext(tenant2)); var result = await store2.GetSessionAsync(sessionId); @@ -343,7 +343,7 @@ public async Task ExecuteAsync_Should_Rollback_On_Error() await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); await Assert.ThrowsAsync(async () => { @@ -368,7 +368,7 @@ public async Task GetSessionsByChain_Should_Return_Sessions() await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var root = UAuthSessionRoot.Create(tenant, userKey, DateTimeOffset.UtcNow); chainId = SessionChainId.New(); @@ -405,7 +405,7 @@ await store.ExecuteAsync(async ct => await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var sessions = await store.GetSessionsByChainAsync(chainId); Assert.Single(sessions); } @@ -423,7 +423,7 @@ public async Task ExecuteAsync_Should_Commit_Multiple_Operations() await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); await store.ExecuteAsync(async ct => { @@ -460,7 +460,7 @@ await store.ExecuteAsync(async ct => await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var result = await store.GetSessionAsync(sessionId); @@ -480,7 +480,7 @@ public async Task ExecuteAsync_Should_Rollback_All_On_Failure() await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); await Assert.ThrowsAsync(async () => { @@ -516,7 +516,7 @@ public async Task RevokeChainCascade_Should_Revoke_All_Sessions() await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var root = UAuthSessionRoot.Create(tenant, userKey, DateTimeOffset.UtcNow); chainId = SessionChainId.New(); @@ -554,7 +554,7 @@ await store.ExecuteAsync(async ct => await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); await store.ExecuteAsync(async ct => { @@ -564,7 +564,7 @@ await store.ExecuteAsync(async ct => await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var sessions = await store.GetSessionsByChainAsync(chainId); Assert.All(sessions, s => Assert.True(s.IsRevoked)); @@ -584,7 +584,7 @@ public async Task SetActiveSession_Should_Work() await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var root = UAuthSessionRoot.Create(tenant, userKey, DateTimeOffset.UtcNow); @@ -612,7 +612,7 @@ await store.ExecuteAsync(async ct => await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var active = await store.GetActiveSessionIdAsync(chainId); Assert.Equal(sessionId, active); @@ -648,7 +648,7 @@ public async Task SaveSession_Should_Increment_Version() await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var root = UAuthSessionRoot.Create(tenant, userKey, DateTimeOffset.UtcNow); chainId = SessionChainId.New(); @@ -686,7 +686,7 @@ await store.ExecuteAsync(async ct => await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); await store.ExecuteAsync(async ct => { @@ -699,7 +699,7 @@ await store.ExecuteAsync(async ct => await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var result = await store.GetSessionAsync(sessionId); Assert.Equal(1, result!.Version); @@ -719,7 +719,7 @@ public async Task SaveSession_With_Wrong_Version_Should_Throw() await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var root = UAuthSessionRoot.Create(tenant, userKey, DateTimeOffset.UtcNow); chainId = SessionChainId.New(); @@ -757,7 +757,7 @@ await store.ExecuteAsync(async ct => await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); await Assert.ThrowsAsync(async () => { @@ -784,7 +784,7 @@ public async Task SaveChain_Should_Increment_Version() await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var root = UAuthSessionRoot.Create(tenant, userKey, DateTimeOffset.UtcNow); chainId = SessionChainId.New(); @@ -809,7 +809,7 @@ await store.ExecuteAsync(async ct => await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); await store.ExecuteAsync(async ct => { @@ -822,7 +822,7 @@ await store.ExecuteAsync(async ct => await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var result = await store.GetChainAsync(chainId); Assert.Equal(1, result!.Version); @@ -839,7 +839,7 @@ public async Task SaveRoot_Should_Increment_Version() await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var root = UAuthSessionRoot.Create( tenant, @@ -854,7 +854,7 @@ await store.ExecuteAsync(async ct => await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); await store.ExecuteAsync(async ct => { @@ -867,7 +867,7 @@ await store.ExecuteAsync(async ct => await using (var db = CreateDb(connection)) { - var store = new EfCoreSessionStore(db, new TenantContext(tenant)); + var store = new EfCoreSessionStore(db, new TenantContext(tenant)); var result = await store.GetRootByUserAsync(userKey); Assert.Equal(1, result!.Version); diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreTokenStoreTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreTokenStoreTests.cs index 58e608a9..c7bd8179 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreTokenStoreTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreTokenStoreTests.cs @@ -22,7 +22,7 @@ public async Task Store_And_Find_Should_Work() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreRefreshTokenStore(db, new TenantContext(tenant)); + var store = new EfCoreRefreshTokenStore(db, new TenantContext(tenant)); AuthSessionId.TryCreate(ValidRaw, out var sessionId); var token = RefreshToken.Create( @@ -58,7 +58,7 @@ public async Task Revoke_Should_Set_RevokedAt() await using (var db = CreateDb(connection)) { - var store = new EfCoreRefreshTokenStore(db, new TenantContext(tenant)); + var store = new EfCoreRefreshTokenStore(db, new TenantContext(tenant)); var token = RefreshToken.Create( TokenId.From(Guid.NewGuid()), @@ -79,7 +79,7 @@ await store.ExecuteAsync(async ct => await using (var db = CreateDb(connection)) { - var store = new EfCoreRefreshTokenStore(db, new TenantContext(tenant)); + var store = new EfCoreRefreshTokenStore(db, new TenantContext(tenant)); await store.ExecuteAsync(async ct => { @@ -89,7 +89,7 @@ await store.ExecuteAsync(async ct => await using (var db = CreateDb(connection)) { - var store = new EfCoreRefreshTokenStore(db, new TenantContext(tenant)); + var store = new EfCoreRefreshTokenStore(db, new TenantContext(tenant)); var result = await store.FindByHashAsync(tokenHash); Assert.NotNull(result!.RevokedAt); @@ -103,7 +103,7 @@ public async Task Store_Outside_Transaction_Should_Throw() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreRefreshTokenStore(db, new TenantContext(tenant)); + var store = new EfCoreRefreshTokenStore(db, new TenantContext(tenant)); AuthSessionId.TryCreate(ValidRaw, out var sessionId); var token = RefreshToken.Create( diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreUserIdentifierStoreTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreUserIdentifierStoreTests.cs index 751f24f2..4fd806af 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreUserIdentifierStoreTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreUserIdentifierStoreTests.cs @@ -23,7 +23,7 @@ public async Task Add_And_Get_Should_Work() using var connection = CreateOpenConnection(); await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreUserIdentifierStore(db, new TenantContext(tenant)); + var store = new EfCoreUserIdentifierStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); var identifier = UserIdentifier.Create( @@ -49,7 +49,7 @@ public async Task Exists_Should_Return_True_When_Exists() using var connection = CreateOpenConnection(); await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreUserIdentifierStore(db, new TenantContext(tenant)); + var store = new EfCoreUserIdentifierStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); var identifier = UserIdentifier.Create( @@ -77,7 +77,7 @@ public async Task Save_With_Wrong_Version_Should_Throw() var userKey = UserKey.FromGuid(Guid.NewGuid()); await using var db1 = CreateDb(connection); - var store1 = new EfCoreUserIdentifierStore(db1, new TenantContext(tenant)); + var store1 = new EfCoreUserIdentifierStore(db1, new TenantContext(tenant)); var identifier = UserIdentifier.Create( Guid.NewGuid(), @@ -92,7 +92,7 @@ public async Task Save_With_Wrong_Version_Should_Throw() await store1.AddAsync(identifier); await using var db2 = CreateDb(connection); - var store2 = new EfCoreUserIdentifierStore(db2, new TenantContext(tenant)); + var store2 = new EfCoreUserIdentifierStore(db2, new TenantContext(tenant)); var updated = identifier.SetPrimary(DateTimeOffset.UtcNow); @@ -120,13 +120,13 @@ public async Task Save_Should_Increment_Version() await using (var db1 = CreateDb(connection)) { - var store1 = new EfCoreUserIdentifierStore(db1, new TenantContext(tenant)); + var store1 = new EfCoreUserIdentifierStore(db1, new TenantContext(tenant)); await store1.AddAsync(identifier); } await using (var db2 = CreateDb(connection)) { - var store2 = new EfCoreUserIdentifierStore(db2, new TenantContext(tenant)); + var store2 = new EfCoreUserIdentifierStore(db2, new TenantContext(tenant)); var existing = await store2.GetAsync(identifier.Id); var updated = existing!.SetPrimary(DateTimeOffset.UtcNow); await store2.SaveAsync(updated, expectedVersion: 0); @@ -134,7 +134,7 @@ public async Task Save_Should_Increment_Version() await using (var db3 = CreateDb(connection)) { - var store3 = new EfCoreUserIdentifierStore(db3, new TenantContext(tenant)); + var store3 = new EfCoreUserIdentifierStore(db3, new TenantContext(tenant)); var result = await store3.GetAsync(identifier.Id); Assert.Equal(1, result!.Version); } @@ -147,8 +147,8 @@ public async Task Should_Not_See_Data_From_Other_Tenant() await using var db = CreateDb(connection); var tenant1 = TenantKeys.Single; var tenant2 = TenantKey.FromInternal("tenant-2"); - var store1 = new EfCoreUserIdentifierStore(db, new TenantContext(tenant1)); - var store2 = new EfCoreUserIdentifierStore(db, new TenantContext(tenant2)); + var store1 = new EfCoreUserIdentifierStore(db, new TenantContext(tenant1)); + var store2 = new EfCoreUserIdentifierStore(db, new TenantContext(tenant2)); var userKey = UserKey.FromGuid(Guid.NewGuid()); @@ -174,7 +174,7 @@ public async Task Soft_Delete_Should_Work() using var connection = CreateOpenConnection(); await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreUserIdentifierStore(db, new TenantContext(tenant)); + var store = new EfCoreUserIdentifierStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); var identifier = UserIdentifier.Create( diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreUserLifecycleStoreTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreUserLifecycleStoreTests.cs index 4c08b7b3..0c7c0333 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreUserLifecycleStoreTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreUserLifecycleStoreTests.cs @@ -23,7 +23,7 @@ public async Task Add_And_Get_Should_Work() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreUserLifecycleStore(db, new TenantContext(tenant)); + var store = new EfCoreUserLifecycleStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); @@ -48,7 +48,7 @@ public async Task Exists_Should_Return_True_When_Exists() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreUserLifecycleStore(db, new TenantContext(tenant)); + var store = new EfCoreUserLifecycleStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); @@ -78,13 +78,13 @@ public async Task Save_Should_Increment_Version() await using (var db1 = CreateDb(connection)) { - var store1 = new EfCoreUserLifecycleStore(db1, new TenantContext(tenant)); + var store1 = new EfCoreUserLifecycleStore(db1, new TenantContext(tenant)); await store1.AddAsync(lifecycle); } await using (var db2 = CreateDb(connection)) { - var store2 = new EfCoreUserLifecycleStore(db2, new TenantContext(tenant)); + var store2 = new EfCoreUserLifecycleStore(db2, new TenantContext(tenant)); var existing = await store2.GetAsync(new UserLifecycleKey(tenant, userKey)); var updated = existing!.ChangeStatus(DateTimeOffset.UtcNow, UserStatus.Suspended); await store2.SaveAsync(updated, expectedVersion: 0); @@ -92,7 +92,7 @@ public async Task Save_Should_Increment_Version() await using (var db3 = CreateDb(connection)) { - var store3 = new EfCoreUserLifecycleStore(db3, new TenantContext(tenant)); + var store3 = new EfCoreUserLifecycleStore(db3, new TenantContext(tenant)); var result = await store3.GetAsync(new UserLifecycleKey(tenant, userKey)); Assert.Equal(1, result!.Version); @@ -115,13 +115,13 @@ public async Task Save_With_Wrong_Version_Should_Throw() await using (var db1 = CreateDb(connection)) { - var store1 = new EfCoreUserLifecycleStore(db1, new TenantContext(tenant)); + var store1 = new EfCoreUserLifecycleStore(db1, new TenantContext(tenant)); await store1.AddAsync(lifecycle); } await using (var db2 = CreateDb(connection)) { - var store2 = new EfCoreUserLifecycleStore(db2, new TenantContext(tenant)); + var store2 = new EfCoreUserLifecycleStore(db2, new TenantContext(tenant)); var existing = await store2.GetAsync(new UserLifecycleKey(tenant, userKey)); var updated = existing!.ChangeStatus(DateTimeOffset.UtcNow, UserStatus.Suspended); @@ -139,8 +139,8 @@ public async Task Should_Not_See_Data_From_Other_Tenant() var tenant1 = TenantKeys.Single; var tenant2 = TenantKey.FromInternal("tenant-2"); - var store1 = new EfCoreUserLifecycleStore(db, new TenantContext(tenant1)); - var store2 = new EfCoreUserLifecycleStore(db, new TenantContext(tenant2)); + var store1 = new EfCoreUserLifecycleStore(db, new TenantContext(tenant1)); + var store2 = new EfCoreUserLifecycleStore(db, new TenantContext(tenant2)); var userKey = UserKey.FromGuid(Guid.NewGuid()); @@ -163,7 +163,7 @@ public async Task Soft_Delete_Should_Work() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreUserLifecycleStore(db, new TenantContext(tenant)); + var store = new EfCoreUserLifecycleStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); @@ -201,13 +201,13 @@ public async Task Delete_Should_Increment_SecurityVersion() await using (var db1 = CreateDb(connection)) { - var store1 = new EfCoreUserLifecycleStore(db1, new TenantContext(tenant)); + var store1 = new EfCoreUserLifecycleStore(db1, new TenantContext(tenant)); await store1.AddAsync(lifecycle); } await using (var db2 = CreateDb(connection)) { - var store2 = new EfCoreUserLifecycleStore(db2, new TenantContext(tenant)); + var store2 = new EfCoreUserLifecycleStore(db2, new TenantContext(tenant)); var existing = await store2.GetAsync(new UserLifecycleKey(tenant, userKey)); var deleted = existing!.MarkDeleted(DateTimeOffset.UtcNow); @@ -217,7 +217,7 @@ public async Task Delete_Should_Increment_SecurityVersion() await using (var db3 = CreateDb(connection)) { - var store3 = new EfCoreUserLifecycleStore(db3, new TenantContext(tenant)); + var store3 = new EfCoreUserLifecycleStore(db3, new TenantContext(tenant)); var result = await store3.GetAsync(new UserLifecycleKey(tenant, userKey)); diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreUserProfileStoreTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreUserProfileStoreTests.cs index db39136f..87d84078 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreUserProfileStoreTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreUserProfileStoreTests.cs @@ -23,7 +23,7 @@ public async Task Add_And_Get_Should_Work() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreUserProfileStore(db, new TenantContext(tenant)); + var store = new EfCoreUserProfileStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); @@ -51,7 +51,7 @@ public async Task Exists_Should_Return_True_When_Exists() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreUserProfileStore(db, new TenantContext(tenant)); + var store = new EfCoreUserProfileStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); @@ -91,13 +91,13 @@ public async Task Save_Should_Increment_Version() await using (var db1 = CreateDb(connection)) { - var store1 = new EfCoreUserProfileStore(db1, new TenantContext(tenant)); + var store1 = new EfCoreUserProfileStore(db1, new TenantContext(tenant)); await store1.AddAsync(profile); } await using (var db2 = CreateDb(connection)) { - var store2 = new EfCoreUserProfileStore(db2, new TenantContext(tenant)); + var store2 = new EfCoreUserProfileStore(db2, new TenantContext(tenant)); var existing = await store2.GetAsync(new UserProfileKey(tenant, userKey)); var updated = existing!.UpdateName(existing.FirstName, existing.LastName, "new", DateTimeOffset.UtcNow); await store2.SaveAsync(updated, expectedVersion: 0); @@ -105,7 +105,7 @@ public async Task Save_Should_Increment_Version() await using (var db3 = CreateDb(connection)) { - var store3 = new EfCoreUserProfileStore(db3, new TenantContext(tenant)); + var store3 = new EfCoreUserProfileStore(db3, new TenantContext(tenant)); var result = await store3.GetAsync(new UserProfileKey(tenant, userKey)); Assert.Equal(1, result!.Version); @@ -133,13 +133,13 @@ public async Task Save_With_Wrong_Version_Should_Throw() await using (var db1 = CreateDb(connection)) { - var store1 = new EfCoreUserProfileStore(db1, new TenantContext(tenant)); + var store1 = new EfCoreUserProfileStore(db1, new TenantContext(tenant)); await store1.AddAsync(profile); } await using (var db2 = CreateDb(connection)) { - var store2 = new EfCoreUserProfileStore(db2, new TenantContext(tenant)); + var store2 = new EfCoreUserProfileStore(db2, new TenantContext(tenant)); var existing = await store2.GetAsync(new UserProfileKey(tenant, userKey)); var updated = existing!.UpdateName(existing.FirstName, existing.LastName, "new", DateTimeOffset.UtcNow); @@ -157,8 +157,8 @@ public async Task Should_Not_See_Data_From_Other_Tenant() var tenant1 = TenantKeys.Single; var tenant2 = TenantKey.FromInternal("tenant-2"); - var store1 = new EfCoreUserProfileStore(db, new TenantContext(tenant1)); - var store2 = new EfCoreUserProfileStore(db, new TenantContext(tenant2)); + var store1 = new EfCoreUserProfileStore(db, new TenantContext(tenant1)); + var store2 = new EfCoreUserProfileStore(db, new TenantContext(tenant2)); var userKey = UserKey.FromGuid(Guid.NewGuid()); @@ -185,7 +185,7 @@ public async Task Soft_Delete_Should_Work() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreUserProfileStore(db, new TenantContext(tenant)); + var store = new EfCoreUserProfileStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreUserRoleStoreTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreUserRoleStoreTests.cs index f1b5f3b6..e1062e34 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreUserRoleStoreTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreUserRoleStoreTests.cs @@ -22,7 +22,7 @@ public async Task Assign_And_GetAssignments_Should_Work() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreUserRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreUserRoleStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); var roleId = RoleId.New(); @@ -41,7 +41,7 @@ public async Task Assign_Duplicate_Should_Throw() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreUserRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreUserRoleStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); var roleId = RoleId.New(); @@ -58,7 +58,7 @@ public async Task Remove_Should_Work() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreUserRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreUserRoleStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); var roleId = RoleId.New(); @@ -77,7 +77,7 @@ public async Task Remove_NonExisting_Should_Not_Throw() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreUserRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreUserRoleStore(db, new TenantContext(tenant)); var userKey = UserKey.FromGuid(Guid.NewGuid()); var roleId = RoleId.New(); @@ -92,7 +92,7 @@ public async Task CountAssignments_Should_Return_Correct_Count() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreUserRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreUserRoleStore(db, new TenantContext(tenant)); var roleId = RoleId.New(); @@ -111,7 +111,7 @@ public async Task RemoveAssignmentsByRole_Should_Remove_All() await using var db = CreateDb(connection); var tenant = TenantKeys.Single; - var store = new EfCoreUserRoleStore(db, new TenantContext(tenant)); + var store = new EfCoreUserRoleStore(db, new TenantContext(tenant)); var roleId = RoleId.New(); @@ -137,8 +137,8 @@ public async Task Should_Not_See_Data_From_Other_Tenant() var tenant1 = TenantKeys.Single; var tenant2 = TenantKey.FromInternal("tenant-2"); - var store1 = new EfCoreUserRoleStore(db, new TenantContext(tenant1)); - var store2 = new EfCoreUserRoleStore(db, new TenantContext(tenant2)); + var store1 = new EfCoreUserRoleStore(db, new TenantContext(tenant1)); + var store2 = new EfCoreUserRoleStore(db, new TenantContext(tenant2)); var userKey = UserKey.FromGuid(Guid.NewGuid()); var roleId = RoleId.New(); From ce515c1a884cf06a2521b155b2738eebab083012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Fri, 27 Mar 2026 22:31:29 +0300 Subject: [PATCH 5/6] Fix CORS & Completed Samples --- ...timateAuthEntityFrameworkCoreExtensions.cs | 15 ---- .../Components/Pages/AnonymousTestPage.razor | 1 + .../Components/Pages/AuthorizedTestPage.razor | 26 +++++++ .../Components/Pages/Register.razor | 60 ++++++++++++++++ .../Components/Pages/Register.razor.cs | 45 ++++++++++++ .../Components/Pages/ResetCredential.razor | 18 +++++ .../Components/Pages/ResetCredential.razor.cs | 49 +++++++++++++ .../Components/Routes.razor | 2 +- .../Components/_Imports.razor | 1 + .../Program.cs | 7 +- .../uauth.db-shm | Bin 32768 -> 32768 bytes .../uauth.db-wal | Bin 914672 -> 716912 bytes .../Components/Pages/Register.razor | 6 ++ .../Program.cs | 2 +- .../Pages/Register.razor | 6 ++ .../Program.cs | 25 +++---- .../Program.cs | 6 +- .../EndpointRouteBuilderExtensions.cs | 22 ++++-- .../Extensions/ServiceCollectionExtensions.cs | 67 ++++++------------ .../UAuthApplicationBuilderExtensions.cs | 9 +++ 20 files changed, 274 insertions(+), 93 deletions(-) create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/AnonymousTestPage.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/AuthorizedTestPage.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Register.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Register.razor.cs create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/ResetCredential.razor create mode 100644 samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/ResetCredential.razor.cs diff --git a/nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/Extensions/UltimateAuthEntityFrameworkCoreExtensions.cs b/nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/Extensions/UltimateAuthEntityFrameworkCoreExtensions.cs index 25f63b6d..6d69104d 100644 --- a/nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/Extensions/UltimateAuthEntityFrameworkCoreExtensions.cs +++ b/nuget/CodeBeam.UltimateAuth.EntityFrameworkCore/Extensions/UltimateAuthEntityFrameworkCoreExtensions.cs @@ -69,21 +69,6 @@ public static IServiceCollection AddUltimateAuthEntityFrameworkCore(this IServic return services; } - - //public static IServiceCollection AddUltimateAuthEntityFrameworkCore(this IServiceCollection services, Action configureDb) - //{ - // services - // .AddUltimateAuthReferences() - // .AddUltimateAuthUsersEntityFrameworkCore(configureDb) - // .AddUltimateAuthCredentialsEntityFrameworkCore(configureDb) - // .AddUltimateAuthAuthorizationEntityFrameworkCore(configureDb) - // .AddUltimateAuthSessionsEntityFrameworkCore(configureDb) - // .AddUltimateAuthTokensEntityFrameworkCore(configureDb) - // .AddUltimateAuthAuthenticationEntityFrameworkCore(configureDb); - - // return services; - //} - /// /// Adds and configures Entity Framework Core-based UltimateAuth services and related references to the specified /// service collection. diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/AnonymousTestPage.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/AnonymousTestPage.razor new file mode 100644 index 00000000..10d035ba --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/AnonymousTestPage.razor @@ -0,0 +1 @@ +@page "/anonymous-test" diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/AuthorizedTestPage.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/AuthorizedTestPage.razor new file mode 100644 index 00000000..5dc5d8aa --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/AuthorizedTestPage.razor @@ -0,0 +1,26 @@ +@page "/authorized-test" +@attribute [Authorize] + + + + + + + Everything is Ok + + + If you see this section, it means you succesfully logged in. + + + + Go Profile + + + + + + UltimateAuth protects this resource based on your session and permissions. + + + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Register.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Register.razor new file mode 100644 index 00000000..881cae5c --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Register.razor @@ -0,0 +1,60 @@ +@page "/register" +@inherits UAuthFlowPageBase + +@implements IDisposable +@inject IUAuthClient UAuthClient +@inject ISnackbar Snackbar +@inject IUAuthClientProductInfoProvider ClientProductInfoProvider +@inject IDeviceIdProvider DeviceIdProvider +@inject IDialogService DialogService + + + + + + + + + + + + + + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Register.razor.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Register.razor.cs new file mode 100644 index 00000000..e8c16205 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/Register.razor.cs @@ -0,0 +1,45 @@ +using CodeBeam.UltimateAuth.Client.Runtime; +using CodeBeam.UltimateAuth.Users.Contracts; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Pages; + +public partial class Register +{ + private string? _username; + private string? _password; + private string? _passwordCheck; + private string? _email; + private UAuthClientProductInfo? _productInfo; + private MudForm _form = null!; + + protected override async Task OnInitializedAsync() + { + _productInfo = ClientProductInfoProvider.Get(); + } + + private async Task HandleRegisterAsync() + { + await _form.Validate(); + + if (!_form.IsValid) + return; + + var request = new CreateUserRequest + { + UserName = _username, + Password = _password, + Email = _email, + }; + + var result = await UAuthClient.Users.CreateAsync(request); + if (result.IsSuccess) + { + Snackbar.Add("User created successfully.", Severity.Success); + } + else + { + Snackbar.Add(result.ErrorText ?? "Failed to create user.", Severity.Error); + } + } +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/ResetCredential.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/ResetCredential.razor new file mode 100644 index 00000000..753878b8 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/ResetCredential.razor @@ -0,0 +1,18 @@ +@page "/reset" +@inherits UAuthFlowPageBase + +@inject IUAuthClient UAuthClient +@inject ISnackbar Snackbar + + + + + + + + + Change Password + + + + \ No newline at end of file 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 new file mode 100644 index 00000000..3bdd9f68 --- /dev/null +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Pages/ResetCredential.razor.cs @@ -0,0 +1,49 @@ +using CodeBeam.UltimateAuth.Credentials.Contracts; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore.Components.Pages; + +public partial class ResetCredential +{ + private MudForm _form = null!; + private string? _code; + private string? _newPassword; + private string? _newPasswordCheck; + + private async Task ResetPasswordAsync() + { + await _form.Validate(); + if (!_form.IsValid) + { + Snackbar.Add("Please fix the validation errors.", Severity.Error); + return; + } + + if (_newPassword != _newPasswordCheck) + { + Snackbar.Add("Passwords do not match.", Severity.Error); + return; + } + + var request = new CompleteCredentialResetRequest + { + ResetToken = _code, + NewSecret = _newPassword ?? string.Empty, + Identifier = Identifier // Coming from UAuthFlowPageBase automatically if begin reset is successful + }; + + var result = await UAuthClient.Credentials.CompleteResetMyAsync(request); + + if (result.IsSuccess) + { + Snackbar.Add("Credential reset successfully. Please log in with your new password.", Severity.Success); + Nav.NavigateTo("/login"); + } + else + { + Snackbar.Add(result.Problem?.Detail ?? result.Problem?.Title ?? "Failed to reset credential. Please try again.", Severity.Error); + } + } + + private string PasswordMatch(string arg) => _newPassword != arg ? "Passwords don't match" : string.Empty; +} diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Routes.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Routes.razor index 033ad7bd..60331591 100644 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Routes.razor +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/Routes.razor @@ -17,7 +17,7 @@ - @* Advanced: you can fully control routing by providing your own Router *@ + @* Advanced: you can fully control routing by providing your own Router (in the commented code below) *@ @* diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/_Imports.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/_Imports.razor index 88b26436..59225b8c 100644 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/_Imports.razor +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Components/_Imports.razor @@ -1,5 +1,6 @@ @using System.Net.Http @using System.Net.Http.Json +@using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Program.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Program.cs index 0a00a45b..377eb8b1 100644 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Program.cs +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/Program.cs @@ -1,6 +1,5 @@ using CodeBeam.UltimateAuth.Client.Blazor; using CodeBeam.UltimateAuth.Client.Blazor.Extensions; -using CodeBeam.UltimateAuth.Core.Abstractions; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.Infrastructure; using CodeBeam.UltimateAuth.EntityFrameworkCore; @@ -87,10 +86,6 @@ app.MapOpenApi(); app.MapScalarApiReference(); - //using var scope = app.Services.CreateScope(); - //var seedRunner = scope.ServiceProvider.GetRequiredService(); - //await seedRunner.RunAsync(null); - using (var scope = app.Services.CreateScope()) { await UAuthDbInitializer.InitializeAsync(app.Services, reset: true); @@ -105,7 +100,7 @@ app.UseHttpsRedirection(); app.UseStaticFiles(); -app.UseUltimateAuthWithAspNetCore(enableCors: true); +app.UseUltimateAuthWithAspNetCore(); app.UseAntiforgery(); app.MapUltimateAuthEndpoints(); diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/uauth.db-shm b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer.EFCore/uauth.db-shm index ad804a2a9e406286bc8a42c686f5be34d684450e..9c25688eed7baaff75ab914efad771e9281bd466 100644 GIT binary patch delta 368 zcmZo@U}|V!s+V}A%K!pmK+MR%Ag~Td`v5WLcG0%FCo#;7!PmZ@iJ!}ExT&o7vW-^} zsp^47fdR#-+PtX*#@bL0W38)# KvDVZ=`~(2r#(1Rw delta 567 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 ZUj@Ab#x=s?MUFsV5r1u71#|Z&`WKkk!8QN@ delta 16233 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 Sign Up + + + + + Already have an account? SignIn + diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Program.cs b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Program.cs index 485e813b..7b54009b 100644 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Program.cs +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Program.cs @@ -92,7 +92,7 @@ app.UseHttpsRedirection(); app.UseStaticFiles(); -app.UseUltimateAuthWithAspNetCore(enableCors: true); +app.UseUltimateAuthWithAspNetCore(); app.UseAntiforgery(); app.MapUltimateAuthEndpoints(); diff --git a/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/Register.razor b/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/Register.razor index e32cc79c..881cae5c 100644 --- a/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/Register.razor +++ b/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/Register.razor @@ -45,6 +45,12 @@ Sign Up + + + + + Already have an account? SignIn + diff --git a/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Program.cs b/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Program.cs index 7bc8536b..6e346489 100644 --- a/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Program.cs +++ b/samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Program.cs @@ -13,36 +13,31 @@ builder.RootComponents.Add("#app"); builder.RootComponents.Add("head::after"); +builder.Services.AddMudServices(o => { + o.SnackbarConfiguration.PreventDuplicates = false; +}); +builder.Services.AddMudExtensions(); +builder.Services.AddScoped(); + + builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddUltimateAuth(); builder.Services.AddUltimateAuthClientBlazor(o => { - o.Endpoints.BasePath = "https://localhost:6110/auth"; + o.Endpoints.BasePath = "https://localhost:6110/auth"; // UAuthHub URL o.Reauth.Behavior = ReauthBehavior.RaiseEvent; o.Login.AllowCredentialPost = true; - o.Pkce.ReturnUrl = "https://localhost:6130/home"; + o.Pkce.ReturnUrl = "https://localhost:6130/home"; // This application domain + path }); -builder.Services.AddMudServices(o => { - o.SnackbarConfiguration.PreventDuplicates = false; -}); -builder.Services.AddMudExtensions(); - builder.Services.AddScoped(); -builder.Services.AddScoped(); - - -//builder.Services.AddHttpClient("ResourceApi", client => -//{ -// client.BaseAddress = new Uri("https://localhost:6120"); -//}); builder.Services.AddScoped(sp => { return new HttpClient { - BaseAddress = new Uri("https://localhost:6120") // Resource API + BaseAddress = new Uri("https://localhost:6120") // Resource API URL }; }); diff --git a/samples/resource-api/CodeBeam.UltimateAuth.Sample.ResourceApi/Program.cs b/samples/resource-api/CodeBeam.UltimateAuth.Sample.ResourceApi/Program.cs index 35eb05d1..a59b1f61 100644 --- a/samples/resource-api/CodeBeam.UltimateAuth.Sample.ResourceApi/Program.cs +++ b/samples/resource-api/CodeBeam.UltimateAuth.Sample.ResourceApi/Program.cs @@ -17,13 +17,9 @@ { app.MapOpenApi(); } - app.UseHttpsRedirection(); -app.UseUltimateAuthResourceApi(); -app.UseAuthentication(); -app.UseAuthorization(); +app.UseUltimateAuthResourceApiWithAspNetCore(); app.MapControllers(); - app.Run(); diff --git a/src/CodeBeam.UltimateAuth.Server/Extensions/EndpointRouteBuilderExtensions.cs b/src/CodeBeam.UltimateAuth.Server/Extensions/EndpointRouteBuilderExtensions.cs index cedcbdfe..bf1bf3cb 100644 --- a/src/CodeBeam.UltimateAuth.Server/Extensions/EndpointRouteBuilderExtensions.cs +++ b/src/CodeBeam.UltimateAuth.Server/Extensions/EndpointRouteBuilderExtensions.cs @@ -1,4 +1,5 @@ -using CodeBeam.UltimateAuth.Server.Endpoints; +using CodeBeam.UltimateAuth.Core.Runtime; +using CodeBeam.UltimateAuth.Server.Endpoints; using CodeBeam.UltimateAuth.Server.Options; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; @@ -11,10 +12,21 @@ public static class EndpointRouteBuilderExtensions { public static IEndpointRouteBuilder MapUltimateAuthEndpoints(this IEndpointRouteBuilder endpoints) { - var registrar = endpoints.ServiceProvider.GetRequiredService(); - var options = endpoints.ServiceProvider.GetRequiredService>().Value; - var rootGroup = endpoints.MapGroup("") - .RequireCors("UAuthHub"); + var sp = endpoints.ServiceProvider; + + var registrar = sp.GetRequiredService(); + var options = sp.GetRequiredService>().Value; + + var marker = sp.GetService(); + var requiresCors = marker?.RequiresCors == true; + + var rootGroup = endpoints.MapGroup(""); + + if (requiresCors) + { + rootGroup = rootGroup.RequireCors("UAuthHub"); + } + registrar.MapEndpoints(rootGroup, options); if (endpoints is WebApplication app) diff --git a/src/CodeBeam.UltimateAuth.Server/Extensions/ServiceCollectionExtensions.cs b/src/CodeBeam.UltimateAuth.Server/Extensions/ServiceCollectionExtensions.cs index 93ebb780..a9da44d6 100644 --- a/src/CodeBeam.UltimateAuth.Server/Extensions/ServiceCollectionExtensions.cs +++ b/src/CodeBeam.UltimateAuth.Server/Extensions/ServiceCollectionExtensions.cs @@ -297,29 +297,6 @@ private static IServiceCollection AddUltimateAuthServerInternal(this IServiceCol opt.CustomResolversFirst = true; }); - services.AddCors(options => - { - options.AddPolicy("UAuthHub", policy => - { - var sp = services.BuildServiceProvider(); - var serverOptions = sp.GetRequiredService>().Value; - - var origins = serverOptions.Hub.AllowedClientOrigins - .Select(OriginHelper.Normalize) - .ToArray(); - - if (origins.Length > 0) - { - policy - .WithOrigins(origins) - .AllowAnyHeader() - .AllowAnyMethod() - .AllowCredentials() - .WithExposedHeaders("X-UAuth-Refresh"); - } - }); - }); - return services; } @@ -403,28 +380,28 @@ public static IServiceCollection AddUAuthHub(this IServiceCollection services, A services.TryAddScoped(); services.TryAddScoped(); - //services.AddCors(options => - //{ - // options.AddPolicy("UAuthHub", policy => - // { - // var sp = services.BuildServiceProvider(); - // var serverOptions = sp.GetRequiredService>().Value; - - // var origins = serverOptions.Hub.AllowedClientOrigins - // .Select(OriginHelper.Normalize) - // .ToArray(); - - // if (origins.Length > 0) - // { - // policy - // .WithOrigins(origins) - // .AllowAnyHeader() - // .AllowAnyMethod() - // .AllowCredentials() - // .WithExposedHeaders("X-UAuth-Refresh"); - // } - // }); - //}); + services.AddCors(options => + { + options.AddPolicy("UAuthHub", policy => + { + var sp = services.BuildServiceProvider(); + var serverOptions = sp.GetRequiredService>().Value; + + var origins = serverOptions.Hub.AllowedClientOrigins + .Select(OriginHelper.Normalize) + .ToArray(); + + if (origins.Length > 0) + { + policy + .WithOrigins(origins) + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials() + .WithExposedHeaders("X-UAuth-Refresh"); + } + }); + }); return services; } diff --git a/src/CodeBeam.UltimateAuth.Server/Extensions/UAuthApplicationBuilderExtensions.cs b/src/CodeBeam.UltimateAuth.Server/Extensions/UAuthApplicationBuilderExtensions.cs index c78d6427..d352d7f1 100644 --- a/src/CodeBeam.UltimateAuth.Server/Extensions/UAuthApplicationBuilderExtensions.cs +++ b/src/CodeBeam.UltimateAuth.Server/Extensions/UAuthApplicationBuilderExtensions.cs @@ -65,4 +65,13 @@ public static IApplicationBuilder UseUltimateAuthResourceApi(this IApplicationBu return app; } + + public static IApplicationBuilder UseUltimateAuthResourceApiWithAspNetCore(this IApplicationBuilder app) + { + app.UseUltimateAuthResourceApi(); + app.UseAuthentication(); + app.UseAuthorization(); + + return app; + } } From ccf7ecefe9e1878ea313be7c23af566a4129cc26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Fri, 27 Mar 2026 23:53:51 +0300 Subject: [PATCH 6/6] Update README --- README.md | 196 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 130 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 69aad5de..d1c63fa8 100644 --- a/README.md +++ b/README.md @@ -16,38 +16,118 @@ The first preview release (**v 0.1.0-preview**) is planned within the next week. [![Discord](https://img.shields.io/discord/1459498792192839774?color=%237289da&label=Discord&logo=discord&logoColor=%237289da&style=flat-square)](https://discord.gg/QscA86dXSR) [![codecov](https://codecov.io/gh/CodeBeamOrg/UltimateAuth/branch/dev/graph/badge.svg)](https://codecov.io/gh/CodeBeamOrg/UltimateAuth) +--- + +## 📑 Table of Contents + +- [🗺 Roadmap](#-roadmap) +- [🌟 Why UltimateAuth](#-why-ultimateauth) +- [🚀 Quick Start](#-quick-start) +- [💡 Usage](#-usage) +- [📘 Documentation](#-documentation) +- [🤝 Contributing](#-contributing) +- [⭐ Acknowledgements](#-acknowledgements) --- -UltimateAuth is an open-source authentication framework that unifies secure session and token based authentication, modern PKCE flows, Blazor/Maui-ready client experiences, and a fully extensible architecture — all with a focus on clarity, lightweight design, and developer happiness. +UltimateAuth is an open-source auth framework with platform-level capabilities that unifies secure session, cookie and token based Auth, modern PKCE flows, Blazor/Maui-ready client experiences - eliminating the complexity of traditional Auth systems while providing a clean, lightweight, extensible and developer-first architecture. + +--- +## 🗺 Roadmap + +| Phase | Version | Scope | Status | Release Date | +| ----------------------- | ------------- | ----------------------------------------- | -------------- | ------------ | +| First Preview | 0.1.0-preview | "Stable" Preview Core | ✅ Completed | Last check | +| 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 | + +*v 0.1.0 already provides a skeleton of multi tenancy, MFA, reauth etc. Expansion releases will enhance these areas. + +> The project roadmap is actively maintained as a GitHub issue: + +👉 https://github.com/CodeBeamOrg/UltimateAuth/issues/8 + +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: The Six-Point Principles -### **1) Developer-Centric & User-Friendly** -Clean APIs, predictable behavior, minimal ceremony — designed to make authentication *pleasant* for developers. +### 1) Unified Authentication System + +One solution, one mental model — across Blazor Server, WASM, MAUI, and APIs. +UltimateAuth eliminates fragmentation by handling client differences internally and exposing a single, consistent API. -### **2) Security-Driven** -PKCE, hardened session flows, reuse detection, event-driven safeguards, device awareness, and modern best practices. +### 2) Plug & Play Ready -### **3) Extensible & Lightweight by Design** -Every component can be replaced or overridden. -No forced dependencies. No unnecessary weight. +Built-in capabilities designed for real-world scenarios: -### **4) Plug-and-Play Ready** -From setup to production, UltimateAuth prioritizes a frictionless integration journey with sensible defaults. +- Automatic client profile detection (blazor server - WASM - MAUI) +- Selectable authentication modes (Session / Token / Hybrid / SemiHybrid) +- Device-aware sessions +- PKCE flows out of the box +- Unified session + token lifecycle +- Event-driven extensibility -### **5) Blazor & MAUI-Ready for Modern .NET** -Blazor WebApp, Blazor WASM, Blazor Server, and .NET MAUI expose weaknesses in traditional auth systems. -UltimateAuth is engineered from day one to support real-world scenarios across the entire modern .NET UI stack. +No boilerplate. No hidden complexity. -### **6) Unified Framework** -One solution, same codebase across Blazor server, WASM and MAUI. UltimateAuth handles client differences internally and providing consistent and reliable public API. +### 3) Developer-Centric + +Clean APIs, predictable behavior, minimal ceremony — designed to make authentication pleasant. + +### 4) Security as a First-Class Concern + +Modern security built-in by default: + +- PKCE support +- Session reuse detection +- Device tracking +- Hardened auth flows +- Safe defaults with extensibility + +### 5) Extensible & Lightweight + +Start simple, scale infinitely: + +- Works out of the box with sensible defaults +- Replace any component when needed +- No forced architecture decisions + +### 6) Built for Modern .NET Applications + +Designed specifically for real-world .NET environments: + +- Blazor Server +- Blazor WASM +- .NET MAUI +- Backend APIs + +Traditional auth solutions struggle here — UltimateAuth embraces it. --- # 🚀 Quick Start +> ⏱ Takes ~2 minutes to get started ### 1) Install packages (Will be available soon) @@ -66,7 +146,11 @@ Server registration: ```csharp builder.Services .AddUltimateAuthServer() - .AddUltimateAuthEntityFrameworkCore(); // Production + .AddUltimateAuthEntityFrameworkCore(db => + { + // use with your database provider + db.UseSqlite("Data Source=uauth.db"); + }); // OR @@ -107,7 +191,36 @@ Place this in `App.razor` or `index.html` ``` -### 5) Optional: Blazor Usings +### 5) 🗄️ Database Setup (EF Core) + +After configuring UltimateAuth with Entity Framework Core, you need to create and apply database migrations. + +5.1) Install EF Core tools (if not installed) +```bash +dotnet tool install --global dotnet-ef +``` +5.2) Add migration +```bash +dotnet ef migrations add InitUAuth +``` + +5.3) Update database +```bash +dotnet ef database update +``` +💡 Visual Studio (PMC alternative) + +If you are using Visual Studio, you can run these commands in Package Manager Console: +```bash +Add-Migration InitUAuth -Context UAuthDbContext +Update-Database -Context UAuthDbContext +``` +⚠️ 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` ```csharp @using CodeBeam.UltimateAuth.Client.Blazor @@ -181,39 +294,6 @@ UltimateAuth turns Auth into a simple application service — not a separate sys --- - -## 📅 Release Timeline (Targeted) - -> _Dates reflect targeted milestones and may evolve with community feedback._ - -### **Q1 2026 — First Release** -- v 0.1.0-preview to v 0.1.0 - -### **Q2 2026 — Stable Feature Releases** -- v 0.2.0 to v 0.3.0 - -### **Q3 2026 — General Availability** -- API surface locked -- Production-ready security hardening -- Unified architecture finalized - -### **Q4 2026 — v 11.x.x (.NET 11 Alignment Release)** -UltimateAuth adopts .NET platform versioning to align with the broader ecosystem. - ---- - -## 🗺 Roadmap - -The project roadmap is actively maintained as a GitHub issue: - -👉 https://github.com/CodeBeamOrg/UltimateAuth/issues/8 - -We keep it up-to-date with current priorities, planned features, and progress. - -Feel free to follow, comment, or contribute ideas. - ---- - ## 📘 Documentation Two documentation experiences will be provided: @@ -235,22 +315,6 @@ Discussions are open — your ideas matter. --- -## 🛠 Project Status - -UltimateAuth core architecture is implemented and validated through the sample application. - -We are currently: - -- Polishing developer experience -- Reviewing public APIs -- Preparing EF Core integration packages - -Preview release is coming soon. - -You can check the samples and try what UltimateAuth offers by downloading repo and running locally. - ---- - ## ⭐ Acknowledgements UltimateAuth is built with love by CodeBeam and shaped by real-world .NET development —