From 51f8fa0905cf0a4ed91aa26e3dc01c7a97803c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Sun, 14 Dec 2025 01:01:14 +0300 Subject: [PATCH 1/4] Build Server Project (Part 2) --- .../MultiTenancy/DomainTenantAdapter.cs | 34 ------------------- .../MultiTenancy/HeaderTenantAdapter.cs | 34 ------------------- .../MultiTenancy/RouteTenantAdapter.cs | 32 ----------------- 3 files changed, 100 deletions(-) delete mode 100644 src/CodeBeam.UltimateAuth.Server/MultiTenancy/DomainTenantAdapter.cs delete mode 100644 src/CodeBeam.UltimateAuth.Server/MultiTenancy/HeaderTenantAdapter.cs delete mode 100644 src/CodeBeam.UltimateAuth.Server/MultiTenancy/RouteTenantAdapter.cs diff --git a/src/CodeBeam.UltimateAuth.Server/MultiTenancy/DomainTenantAdapter.cs b/src/CodeBeam.UltimateAuth.Server/MultiTenancy/DomainTenantAdapter.cs deleted file mode 100644 index 88b3c74..0000000 --- a/src/CodeBeam.UltimateAuth.Server/MultiTenancy/DomainTenantAdapter.cs +++ /dev/null @@ -1,34 +0,0 @@ -using CodeBeam.UltimateAuth.Core.MultiTenancy; -using CodeBeam.UltimateAuth.Core.Options; -using Microsoft.AspNetCore.Http; - -namespace CodeBeam.UltimateAuth.Server.MultiTenancy -{ - public sealed class DomainTenantAdapter : ITenantResolver - { - private readonly ITenantIdResolver _coreResolver; - private readonly UAuthMultiTenantOptions _options; - - public DomainTenantAdapter( - HostTenantResolver coreResolver, - UAuthMultiTenantOptions options) - { - _coreResolver = coreResolver; - _options = options; - } - - public async Task ResolveAsync(HttpContext ctx) - { - if (!_options.Enabled) - return UAuthTenantContext.NotResolved(); - - var resolutionContext = TenantResolutionContextFactory.FromHttpContext(ctx); - var tenantId = await _coreResolver.ResolveTenantIdAsync(resolutionContext); - - if (tenantId is null) - return UAuthTenantContext.NotResolved(); - - return UAuthTenantContextFactory.Create(tenantId, _options); - } - } -} diff --git a/src/CodeBeam.UltimateAuth.Server/MultiTenancy/HeaderTenantAdapter.cs b/src/CodeBeam.UltimateAuth.Server/MultiTenancy/HeaderTenantAdapter.cs deleted file mode 100644 index 8250cdb..0000000 --- a/src/CodeBeam.UltimateAuth.Server/MultiTenancy/HeaderTenantAdapter.cs +++ /dev/null @@ -1,34 +0,0 @@ -using CodeBeam.UltimateAuth.Core.MultiTenancy; -using CodeBeam.UltimateAuth.Core.Options; -using Microsoft.AspNetCore.Http; - -namespace CodeBeam.UltimateAuth.Server.MultiTenancy -{ - public sealed class HeaderTenantAdapter : ITenantResolver - { - private readonly ITenantIdResolver _coreResolver; - private readonly UAuthMultiTenantOptions _options; - - public HeaderTenantAdapter( - HeaderTenantResolver coreResolver, - UAuthMultiTenantOptions options) - { - _coreResolver = coreResolver; - _options = options; - } - - public async Task ResolveAsync(HttpContext ctx) - { - if (!_options.Enabled) - return UAuthTenantContext.NotResolved(); - - var resolutionContext = TenantResolutionContextFactory.FromHttpContext(ctx); - - var tenantId = await _coreResolver.ResolveTenantIdAsync(resolutionContext); - if (tenantId is null) - return UAuthTenantContext.NotResolved(); - - return UAuthTenantContextFactory.Create(tenantId, _options); - } - } -} diff --git a/src/CodeBeam.UltimateAuth.Server/MultiTenancy/RouteTenantAdapter.cs b/src/CodeBeam.UltimateAuth.Server/MultiTenancy/RouteTenantAdapter.cs deleted file mode 100644 index e69719a..0000000 --- a/src/CodeBeam.UltimateAuth.Server/MultiTenancy/RouteTenantAdapter.cs +++ /dev/null @@ -1,32 +0,0 @@ -using CodeBeam.UltimateAuth.Core.MultiTenancy; -using CodeBeam.UltimateAuth.Core.Options; -using CodeBeam.UltimateAuth.Server.MultiTenancy; -using Microsoft.AspNetCore.Http; - -public sealed class RouteTenantAdapter : ITenantResolver -{ - private readonly ITenantIdResolver _coreResolver; - private readonly UAuthMultiTenantOptions _options; - - public RouteTenantAdapter( - PathTenantResolver coreResolver, - UAuthMultiTenantOptions options) - { - _coreResolver = coreResolver; - _options = options; - } - - public async Task ResolveAsync(HttpContext ctx) - { - if (!_options.Enabled) - return UAuthTenantContext.NotResolved(); - - var resolutionContext = TenantResolutionContextFactory.FromHttpContext(ctx); - var tenantId = await _coreResolver.ResolveTenantIdAsync(resolutionContext); - - if (tenantId is null) - return UAuthTenantContext.NotResolved(); - - return UAuthTenantContextFactory.Create(tenantId, _options); - } -} From f09362001c17a426f000ba8ef7620e9205fb3af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Sun, 14 Dec 2025 19:55:16 +0300 Subject: [PATCH 2/4] TokenService Implementation --- UltimateAuth.slnx | 2 +- .../{Handlers => Abstractions}/.gitkeep | 0 ...CodeBeam.UltimateAuth.Server.Users.csproj} | 0 .../{Issuers => }/ITokenHasher.cs | 0 .../Abstractions/ITokenValidator.cs | 16 ++ .../Abstractions/IUAuthPasswordHasher.cs | 13 ++ .../Abstractions/IUserIdConverter.cs | 2 + .../Abstractions/IUserIdFactory.cs | 16 ++ .../Abstractions/Services/ISessionService.cs | 93 ---------- .../Services/IUAuthFlowService.cs | 59 +++++++ .../Abstractions/Services/IUAuthService.cs | 15 ++ .../Services/IUAuthSessionService.cs | 46 +++++ .../Services/IUAuthTokenService.cs | 34 ++++ .../Abstractions/Stores/IOpaqueTokenStore.cs | 11 ++ .../{IUserStore.cs => IUAuthUserStore.cs} | 24 ++- .../Abstractions/Stores/IUserStoreFactory.cs | 4 +- .../Contexts/Issued/IssuedAccessToken.cs | 3 +- .../Domain/User/UserId.cs | 16 ++ .../Enums/TokenInvalidReason.cs | 16 ++ .../Enums/TokenType.cs | 10 ++ .../Models/BeginMfaRequest.cs | 7 + .../Models/CompleteMfaRequest.cs | 8 + .../Models/ExternalLoginRequest.cs | 11 ++ .../Models/LoginRequest.cs | 11 ++ .../Models/LoginResult.cs | 16 ++ .../Models/LogoutAllRequest.cs | 16 ++ .../Models/LogoutRequest.cs | 10 ++ .../Models/MfaChallengeResult.cs | 8 + .../Models/OpaqueTokenRecord.cs | 18 ++ .../Models/PkceChallengeResult.cs | 8 + .../Models/PkceConsumeRequest.cs | 7 + .../Models/PkceCreateRequest.cs | 7 + .../Models/PkceVerificationResult.cs | 7 + .../Models/PkceVerifyRequest.cs | 8 + .../Models/ReauthRequest.cs | 11 ++ .../Models/ReauthResult.cs | 13 ++ .../Models/RegisterUserRequest.cs | 30 ++++ .../Models/SessionRefreshRequest.cs | 8 + .../Models/SessionRefreshResult.cs | 10 ++ .../Models/TokenIssueContext.cs | 23 +++ .../Models/TokenIssueResult.cs | 14 ++ .../Models/TokenRefreshContext.cs | 9 + .../Models/TokenValidationResult.cs | 67 +++++++ .../Models/ValidateCredentialsRequest.cs | 24 +++ .../IUAuthUserManagementService.cs | 28 +++ .../Abstractions/IUAuthUserProfileService.cs | 23 +++ .../Users/Abstractions/IUAuthUserService.cs | 25 +++ .../Users/Models/AdminUserFilter.cs | 10 ++ .../Users/Models/ChangePasswordRequest.cs | 13 ++ .../Users/Models/ConfigureMfaRequest.cs | 12 ++ .../Users/Models/ResetPasswordRequest.cs | 12 ++ .../Users/Models/UpdateProfileRequest.cs | 8 + .../Users/Models/UserDto.cs | 16 ++ .../Users/Models/UserProfileDto.cs | 14 ++ .../Users/UserIdFactory.cs | 10 ++ .../Users/UserRecord.cs | 11 ++ .../Utilities/GuidUserIdFactory.cs | 9 + .../Utilities/StringUserIdFactory.cs | 9 + .../Utilities/UAuthUserIdConverter.cs | 28 +++ .../CodeBeam.UltimateAuth.Server.csproj | 8 +- .../UAuthServerServiceCollectionExtensions.cs | 10 +- .../Issuers/UAuthTokenIssuer.cs | 4 +- .../Services/.gitkeep | 1 - .../Services/UAuthFlowService.cs | 81 +++++++++ .../Services/UAuthSessionService.cs | 58 ++++++ .../Services/UAuthTokenService.cs | 58 ++++++ .../Services/UAuthTokenValidator.cs | 165 ++++++++++++++++++ .../Services/UAuthUserService.cs | 73 ++++++++ .../Users/UAuthUserAccessor.cs | 4 +- .../Users/UAuthUserId.cs | 12 ++ 70 files changed, 1325 insertions(+), 108 deletions(-) rename src/CodeBeam.UltimateAuth.AspNetCore/{Handlers => Abstractions}/.gitkeep (100%) rename src/CodeBeam.UltimateAuth.AspNetCore/{CodeBeam.UltimateAuth.AspNetCore.csproj => CodeBeam.UltimateAuth.Server.Users.csproj} (100%) rename src/CodeBeam.UltimateAuth.Core/Abstractions/{Issuers => }/ITokenHasher.cs (100%) create mode 100644 src/CodeBeam.UltimateAuth.Core/Abstractions/ITokenValidator.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Abstractions/IUAuthPasswordHasher.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Abstractions/IUserIdFactory.cs delete mode 100644 src/CodeBeam.UltimateAuth.Core/Abstractions/Services/ISessionService.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthFlowService.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthService.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthSessionService.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthTokenService.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IOpaqueTokenStore.cs rename src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/{IUserStore.cs => IUAuthUserStore.cs} (81%) create mode 100644 src/CodeBeam.UltimateAuth.Core/Domain/User/UserId.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Enums/TokenInvalidReason.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Enums/TokenType.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/BeginMfaRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/CompleteMfaRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/ExternalLoginRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/LoginRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/LoginResult.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/LogoutAllRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/LogoutRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/MfaChallengeResult.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/OpaqueTokenRecord.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/PkceChallengeResult.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/PkceConsumeRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/PkceCreateRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/PkceVerificationResult.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/PkceVerifyRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/ReauthRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/ReauthResult.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/RegisterUserRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshResult.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/TokenIssueContext.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/TokenIssueResult.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/TokenRefreshContext.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/TokenValidationResult.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/ValidateCredentialsRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserManagementService.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserProfileService.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserService.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Users/Models/AdminUserFilter.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Users/Models/ChangePasswordRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Users/Models/ConfigureMfaRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Users/Models/ResetPasswordRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Users/Models/UpdateProfileRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Users/Models/UserDto.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Users/Models/UserProfileDto.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Users/UserIdFactory.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Users/UserRecord.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Utilities/GuidUserIdFactory.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Utilities/StringUserIdFactory.cs delete mode 100644 src/CodeBeam.UltimateAuth.Server/Services/.gitkeep create mode 100644 src/CodeBeam.UltimateAuth.Server/Services/UAuthFlowService.cs create mode 100644 src/CodeBeam.UltimateAuth.Server/Services/UAuthSessionService.cs create mode 100644 src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenService.cs create mode 100644 src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenValidator.cs create mode 100644 src/CodeBeam.UltimateAuth.Server/Services/UAuthUserService.cs create mode 100644 src/CodeBeam.UltimateAuth.Server/Users/UAuthUserId.cs diff --git a/UltimateAuth.slnx b/UltimateAuth.slnx index 21efc31..18a8f98 100644 --- a/UltimateAuth.slnx +++ b/UltimateAuth.slnx @@ -3,7 +3,7 @@ - + diff --git a/src/CodeBeam.UltimateAuth.AspNetCore/Handlers/.gitkeep b/src/CodeBeam.UltimateAuth.AspNetCore/Abstractions/.gitkeep similarity index 100% rename from src/CodeBeam.UltimateAuth.AspNetCore/Handlers/.gitkeep rename to src/CodeBeam.UltimateAuth.AspNetCore/Abstractions/.gitkeep diff --git a/src/CodeBeam.UltimateAuth.AspNetCore/CodeBeam.UltimateAuth.AspNetCore.csproj b/src/CodeBeam.UltimateAuth.AspNetCore/CodeBeam.UltimateAuth.Server.Users.csproj similarity index 100% rename from src/CodeBeam.UltimateAuth.AspNetCore/CodeBeam.UltimateAuth.AspNetCore.csproj rename to src/CodeBeam.UltimateAuth.AspNetCore/CodeBeam.UltimateAuth.Server.Users.csproj diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenHasher.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/ITokenHasher.cs similarity index 100% rename from src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenHasher.cs rename to src/CodeBeam.UltimateAuth.Core/Abstractions/ITokenHasher.cs diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/ITokenValidator.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/ITokenValidator.cs new file mode 100644 index 0000000..9b8a7e2 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/ITokenValidator.cs @@ -0,0 +1,16 @@ +using CodeBeam.UltimateAuth.Core.Models; + +namespace CodeBeam.UltimateAuth.Core.Abstractions +{ + /// + /// Validates access tokens (JWT or opaque) and resolves + /// the authenticated user context. + /// + public interface ITokenValidator + { + Task> ValidateAsync( + string token, + TokenType type, + CancellationToken ct = default); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/IUAuthPasswordHasher.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/IUAuthPasswordHasher.cs new file mode 100644 index 0000000..53246c1 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/IUAuthPasswordHasher.cs @@ -0,0 +1,13 @@ +namespace CodeBeam.UltimateAuth.Core.Abstractions +{ + /// + /// Securely hashes and verifies user passwords. + /// Designed for slow, adaptive, memory-hard algorithms + /// such as Argon2 or bcrypt. + /// + public interface IUAuthPasswordHasher + { + string Hash(string password); + bool Verify(string password, string hash); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/IUserIdConverter.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/IUserIdConverter.cs index a52f3ec..5564215 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/IUserIdConverter.cs +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/IUserIdConverter.cs @@ -31,6 +31,7 @@ public interface IUserIdConverter /// Thrown when the input value cannot be parsed into a valid identifier. /// TUserId FromString(string value); + bool TryFromString(string value, out TUserId userId); /// /// Reconstructs a typed user identifier from its binary representation. @@ -41,5 +42,6 @@ public interface IUserIdConverter /// Thrown when the input binary value cannot be parsed into a valid identifier. /// TUserId FromBytes(byte[] binary); + bool TryFromBytes(byte[] binary, out TUserId userId); } } diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/IUserIdFactory.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/IUserIdFactory.cs new file mode 100644 index 0000000..b5d2715 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/IUserIdFactory.cs @@ -0,0 +1,16 @@ +namespace CodeBeam.UltimateAuth.Core.Abstractions +{ + /// + /// Responsible for creating new user identifiers. + /// This abstraction allows UltimateAuth to remain + /// independent from the concrete user ID type. + /// + /// User identifier type. + public interface IUserIdFactory + { + /// + /// Creates a new unique user identifier. + /// + TUserId Create(); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/ISessionService.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/ISessionService.cs deleted file mode 100644 index c9ba215..0000000 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/ISessionService.cs +++ /dev/null @@ -1,93 +0,0 @@ -using CodeBeam.UltimateAuth.Core.Domain; -using CodeBeam.UltimateAuth.Core.Models; - -namespace CodeBeam.UltimateAuth.Core.Abstractions -{ - /// - /// Provides high-level session lifecycle operations such as creation, refresh, validation, and revocation. - /// - /// The type used to uniquely identify the user. - public interface ISessionService - { - /// - /// Creates a new login session for the specified user. - /// - /// - /// The tenant identifier. Use null for single-tenant applications. - /// - /// The user associated with the session. - /// Information about the device initiating the session. - /// Optional metadata describing the session context. - /// The current UTC timestamp. - /// - /// A result containing the newly created session, chain, and session root. - /// - Task> CreateLoginSessionAsync(string? tenantId, TUserId userId, DeviceInfo deviceInfo, SessionMetadata? metadata, DateTime now); - - /// - /// Rotates the specified session and issues a new one while preserving the session chain. - /// - /// The tenant identifier, or null. - /// The active session identifier to be refreshed. - /// The current UTC timestamp. - /// - /// A result containing the refreshed session and updated chain. - /// - /// - /// Thrown if the session, its chain, or the user's session root is invalid. - /// - Task> RefreshSessionAsync(string? tenantId, AuthSessionId currentSessionId,DateTime now); - - /// - /// Revokes a single session, preventing further use. - /// - /// The tenant identifier, or null. - /// The session identifier to revoke. - /// The UTC timestamp of the revocation. - Task RevokeSessionAsync(string? tenantId, AuthSessionId sessionId, DateTime at); - - /// - /// Revokes an entire session chain (device-level logout). - /// - /// The tenant identifier, or null. - /// The session chain identifier to revoke. - /// The UTC timestamp of the revocation. - Task RevokeChainAsync(string? tenantId, ChainId chainId, DateTime at); - - /// - /// Revokes the user's session root, invalidating all existing sessions across all chains. - /// - /// The tenant identifier, or null. - /// The user whose root should be revoked. - /// The UTC timestamp of the revocation. - Task RevokeRootAsync(string? tenantId, TUserId userId, DateTime at); - - /// - /// Validates a session and evaluates its current state, including expiration, revocation, and security version alignment. - /// - /// The tenant identifier, or null. - /// The session identifier to validate. - /// The current UTC timestamp. - /// - /// A detailed validation result describing the session, chain, root, - /// and computed session state. - /// - Task> ValidateSessionAsync(string? tenantId, AuthSessionId sessionId, DateTime now); - - /// - /// Retrieves all session chains belonging to the specified user. - /// - /// The tenant identifier, or null. - /// The user whose session chains are requested. - /// A read-only list of session chains. - Task>> GetChainsAsync(string? tenantId, TUserId userId); - - /// - /// Retrieves all sessions belonging to a specific session chain. - /// - /// The tenant identifier, or null. - /// The session chain identifier. - /// A read-only list of sessions contained within the chain. - Task>> GetSessionsAsync(string? tenantId, ChainId chainId); - } -} diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthFlowService.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthFlowService.cs new file mode 100644 index 0000000..00eb1d0 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthFlowService.cs @@ -0,0 +1,59 @@ +using CodeBeam.UltimateAuth.Core.Models; + +namespace CodeBeam.UltimateAuth.Core.Abstractions +{ + /// + /// Handles authentication flows such as login, + /// logout, session refresh and reauthentication. + /// + public interface IUAuthFlowService + { + // ---------- LOGIN ---------- + Task LoginAsync( + LoginRequest request, + CancellationToken ct = default); + + Task ExternalLoginAsync( + ExternalLoginRequest request, + CancellationToken ct = default); + + // ---------- MFA ---------- + Task BeginMfaAsync( + BeginMfaRequest request, + CancellationToken ct = default); + + Task CompleteMfaAsync( + CompleteMfaRequest request, + CancellationToken ct = default); + + // ---------- SESSION FLOW ---------- + Task LogoutAsync( + LogoutRequest request, + CancellationToken ct = default); + + Task LogoutAllAsync( + LogoutAllRequest request, + CancellationToken ct = default); + + Task RefreshSessionAsync( + SessionRefreshRequest request, + CancellationToken ct = default); + + Task ReauthenticateAsync( + ReauthRequest request, + CancellationToken ct = default); + + // ---------- PKCE ---------- + Task CreatePkceChallengeAsync( + PkceCreateRequest request, + CancellationToken ct = default); + + Task VerifyPkceAsync( + PkceVerifyRequest request, + CancellationToken ct = default); + + Task ConsumePkceAsync( + PkceConsumeRequest request, + CancellationToken ct = default); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthService.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthService.cs new file mode 100644 index 0000000..3e3e0ac --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthService.cs @@ -0,0 +1,15 @@ +namespace CodeBeam.UltimateAuth.Core.Abstractions +{ + /// + /// High-level facade for UltimateAuth. + /// Provides access to authentication flows, + /// session lifecycle and user operations. + /// + public interface IUAuthService + { + IUAuthFlowService Flow { get; } + IUAuthSessionService Sessions { get; } + IUAuthTokenService Tokens { get; } + IUAuthUserService Users { get; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthSessionService.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthSessionService.cs new file mode 100644 index 0000000..fcc5b9e --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthSessionService.cs @@ -0,0 +1,46 @@ +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Models; + +namespace CodeBeam.UltimateAuth.Core.Abstractions +{ + /// + /// Provides high-level session lifecycle operations such as creation, refresh, validation, and revocation. + /// + /// The type used to uniquely identify the user. + public interface IUAuthSessionService + { + // ---------- READ ---------- + Task> ValidateSessionAsync( + string? tenantId, + AuthSessionId sessionId, + DateTime now); + + Task>> GetChainsAsync( + string? tenantId, + TUserId userId); + + Task>> GetSessionsAsync( + string? tenantId, + ChainId chainId); + + Task?> GetCurrentSessionAsync( + string? tenantId, + AuthSessionId sessionId); + + // ---------- WRITE / REVOKE ---------- + Task RevokeSessionAsync( + string? tenantId, + AuthSessionId sessionId, + DateTime at); + + Task RevokeChainAsync( + string? tenantId, + ChainId chainId, + DateTime at); + + Task RevokeRootAsync( + string? tenantId, + TUserId userId, + DateTime at); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthTokenService.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthTokenService.cs new file mode 100644 index 0000000..f7a84c9 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthTokenService.cs @@ -0,0 +1,34 @@ +using CodeBeam.UltimateAuth.Core.Models; + +namespace CodeBeam.UltimateAuth.Core.Abstractions +{ + /// + /// Issues, refreshes and validates access and refresh tokens. + /// Stateless or hybrid depending on auth mode. + /// + public interface IUAuthTokenService + { + /// + /// Issues access (and optionally refresh) tokens + /// for a validated session. + /// + Task IssueAsync( + TokenIssueContext context, + CancellationToken cancellationToken = default); + + /// + /// Refreshes tokens using a refresh token. + /// + Task RefreshAsync( + TokenRefreshContext context, + CancellationToken cancellationToken = default); + + /// + /// Validates an access token (JWT or opaque). + /// + Task> ValidateAsync( + string token, + TokenType type, + CancellationToken cancellationToken = default); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IOpaqueTokenStore.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IOpaqueTokenStore.cs new file mode 100644 index 0000000..2657788 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IOpaqueTokenStore.cs @@ -0,0 +1,11 @@ +using CodeBeam.UltimateAuth.Core.Models; + +namespace CodeBeam.UltimateAuth.Core.Abstractions +{ + public interface IOpaqueTokenStore + { + Task FindByHashAsync( + string tokenHash, + CancellationToken ct = default); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUserStore.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUAuthUserStore.cs similarity index 81% rename from src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUserStore.cs rename to src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUAuthUserStore.cs index fcb268b..9561161 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUserStore.cs +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUAuthUserStore.cs @@ -1,11 +1,13 @@ -namespace CodeBeam.UltimateAuth.Core.Abstractions +using CodeBeam.UltimateAuth.Core.Users; + +namespace CodeBeam.UltimateAuth.Core.Abstractions { /// /// Provides minimal user lookup and security metadata required for authentication. /// This store does not manage user creation, claims, or profile data — these belong /// to higher-level application services outside UltimateAuth. /// - public interface IUserStore + public interface IUAuthUserStore { /// /// Retrieves a user by identifier. Returns null if no such user exists. @@ -14,6 +16,10 @@ public interface IUserStore /// The user instance or null if not found. Task?> FindByIdAsync(TUserId userId); + Task?> FindByUsernameAsync( + string username, + CancellationToken ct = default); + /// /// Retrieves a user by a login credential such as username or email. /// Returns null if no matching user exists. @@ -33,7 +39,7 @@ public interface IUserStore /// /// Updates the password hash for the specified user. This method is invoked by - /// password management services and not by . + /// password management services and not by . /// /// The user identifier. /// The new password hash value. @@ -54,5 +60,17 @@ public interface IUserStore /// /// The user identifier. Task IncrementSecurityVersionAsync(TUserId userId); + + Task ExistsByUsernameAsync( + string username, + CancellationToken ct = default); + + Task CreateAsync( + UserRecord user, + CancellationToken ct = default); + + Task DeleteAsync( + TUserId userId, + CancellationToken ct = default); } } diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUserStoreFactory.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUserStoreFactory.cs index 06e7a0d..23f8551 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUserStoreFactory.cs +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUserStoreFactory.cs @@ -16,11 +16,11 @@ public interface IUserStoreFactory /// in single-tenant deployments. /// /// - /// An implementation capable of user lookup and security metadata retrieval. + /// An implementation capable of user lookup and security metadata retrieval. /// /// /// Thrown if no user store implementation has been registered for the given user ID type. /// - IUserStore Create(string tenantId); + IUAuthUserStore Create(string tenantId); } } diff --git a/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/IssuedAccessToken.cs b/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/IssuedAccessToken.cs index 32d37e6..88aaaec 100644 --- a/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/IssuedAccessToken.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/IssuedAccessToken.cs @@ -10,11 +10,12 @@ public sealed class IssuedAccessToken /// public required string Token { get; init; } + // TODO: TokenKind enum? /// /// Token type: "jwt" or "opaque". /// Used for diagnostics and middleware behavior. /// - public required string TokenType { get; init; } + public TokenType Type { get; init; } /// /// Expiration time of the token. diff --git a/src/CodeBeam.UltimateAuth.Core/Domain/User/UserId.cs b/src/CodeBeam.UltimateAuth.Core/Domain/User/UserId.cs new file mode 100644 index 0000000..962885f --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Domain/User/UserId.cs @@ -0,0 +1,16 @@ +namespace CodeBeam.UltimateAuth.Core.Domain +{ + /// + /// Strongly typed identifier for a user. + /// Default user id implementation for UltimateAuth. + /// + public readonly record struct UserId(string Value) + { + public override string ToString() => Value; + + public static UserId New() => new(Guid.NewGuid().ToString("N")); + + public static implicit operator string(UserId id) => id.Value; + public static implicit operator UserId(string value) => new(value); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Enums/TokenInvalidReason.cs b/src/CodeBeam.UltimateAuth.Core/Enums/TokenInvalidReason.cs new file mode 100644 index 0000000..eb1dcd7 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Enums/TokenInvalidReason.cs @@ -0,0 +1,16 @@ +namespace CodeBeam.UltimateAuth.Core +{ + public enum TokenInvalidReason + { + Invalid, + Expired, + Revoked, + Malformed, + SignatureInvalid, + AudienceMismatch, + IssuerMismatch, + MissingSubject, + Unknown, + NotImplemented + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Enums/TokenType.cs b/src/CodeBeam.UltimateAuth.Core/Enums/TokenType.cs new file mode 100644 index 0000000..db0ea7c --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Enums/TokenType.cs @@ -0,0 +1,10 @@ +namespace CodeBeam.UltimateAuth.Core +{ + public enum TokenType + { + Opaque, + Jwt, + Unknown + } + +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/BeginMfaRequest.cs b/src/CodeBeam.UltimateAuth.Core/Models/BeginMfaRequest.cs new file mode 100644 index 0000000..cb19c3c --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/BeginMfaRequest.cs @@ -0,0 +1,7 @@ +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record BeginMfaRequest + { + public string MfaToken { get; init; } = default!; + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/CompleteMfaRequest.cs b/src/CodeBeam.UltimateAuth.Core/Models/CompleteMfaRequest.cs new file mode 100644 index 0000000..82eaee4 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/CompleteMfaRequest.cs @@ -0,0 +1,8 @@ +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record CompleteMfaRequest + { + public string ChallengeId { get; init; } = default!; + public string Code { get; init; } = default!; + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/ExternalLoginRequest.cs b/src/CodeBeam.UltimateAuth.Core/Models/ExternalLoginRequest.cs new file mode 100644 index 0000000..249cdac --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/ExternalLoginRequest.cs @@ -0,0 +1,11 @@ +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record ExternalLoginRequest + { + public string? TenantId { get; init; } + public string Provider { get; init; } = default!; + public string ExternalToken { get; init; } = default!; + public string? DeviceId { get; init; } + } + +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/LoginRequest.cs b/src/CodeBeam.UltimateAuth.Core/Models/LoginRequest.cs new file mode 100644 index 0000000..2e647a9 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/LoginRequest.cs @@ -0,0 +1,11 @@ +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record LoginRequest + { + public string? TenantId { get; init; } + public string Identifier { get; init; } = default!; // username, email etc. + public string Secret { get; init; } = default!; // password + public string? DeviceId { get; init; } + public IReadOnlyDictionary? Metadata { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/LoginResult.cs b/src/CodeBeam.UltimateAuth.Core/Models/LoginResult.cs new file mode 100644 index 0000000..0c92e99 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/LoginResult.cs @@ -0,0 +1,16 @@ +using CodeBeam.UltimateAuth.Core.Contexts; +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record LoginResult + { + public bool RequiresMfa { get; init; } + public string? MfaToken { get; init; } + + public AuthSessionId? SessionId { get; init; } + public IssuedAccessToken? AccessToken { get; init; } + public IssuedRefreshToken? RefreshToken { get; init; } + } + +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/LogoutAllRequest.cs b/src/CodeBeam.UltimateAuth.Core/Models/LogoutAllRequest.cs new file mode 100644 index 0000000..69ce8c2 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/LogoutAllRequest.cs @@ -0,0 +1,16 @@ +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed class LogoutAllRequest + { + public string? TenantId { get; init; } + public AuthSessionId? CurrentSessionId { get; init; } + + /// + /// If true, the current session will NOT be revoked. + /// + public bool ExceptCurrent { get; init; } + } + +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/LogoutRequest.cs b/src/CodeBeam.UltimateAuth.Core/Models/LogoutRequest.cs new file mode 100644 index 0000000..a869335 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/LogoutRequest.cs @@ -0,0 +1,10 @@ +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record LogoutRequest + { + public string? TenantId { get; init; } + public AuthSessionId SessionId { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/MfaChallengeResult.cs b/src/CodeBeam.UltimateAuth.Core/Models/MfaChallengeResult.cs new file mode 100644 index 0000000..1fa4653 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/MfaChallengeResult.cs @@ -0,0 +1,8 @@ +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record MfaChallengeResult + { + public string ChallengeId { get; init; } = default!; + public string Method { get; init; } = default!; // totp, sms, email etc. + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/OpaqueTokenRecord.cs b/src/CodeBeam.UltimateAuth.Core/Models/OpaqueTokenRecord.cs new file mode 100644 index 0000000..419436e --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/OpaqueTokenRecord.cs @@ -0,0 +1,18 @@ +using CodeBeam.UltimateAuth.Core.Domain; +using System.Security.Claims; + +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed class OpaqueTokenRecord + { + public string TokenHash { get; init; } = default!; + public string UserId { get; init; } = default!; + public string? TenantId { get; init; } + public AuthSessionId? SessionId { get; init; } + public DateTimeOffset ExpiresAt { get; init; } + public bool IsRevoked { get; init; } + public DateTimeOffset? RevokedAt { get; init; } + public IReadOnlyCollection Claims { get; init; } = Array.Empty(); + } + +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/PkceChallengeResult.cs b/src/CodeBeam.UltimateAuth.Core/Models/PkceChallengeResult.cs new file mode 100644 index 0000000..1e67e38 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/PkceChallengeResult.cs @@ -0,0 +1,8 @@ +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record PkceChallengeResult + { + public string Challenge { get; init; } = default!; + public string Method { get; init; } = "S256"; + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/PkceConsumeRequest.cs b/src/CodeBeam.UltimateAuth.Core/Models/PkceConsumeRequest.cs new file mode 100644 index 0000000..4366c55 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/PkceConsumeRequest.cs @@ -0,0 +1,7 @@ +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record PkceConsumeRequest + { + public string Challenge { get; init; } = default!; + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/PkceCreateRequest.cs b/src/CodeBeam.UltimateAuth.Core/Models/PkceCreateRequest.cs new file mode 100644 index 0000000..7230a8b --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/PkceCreateRequest.cs @@ -0,0 +1,7 @@ +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record PkceCreateRequest + { + public string ClientId { get; init; } = default!; + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/PkceVerificationResult.cs b/src/CodeBeam.UltimateAuth.Core/Models/PkceVerificationResult.cs new file mode 100644 index 0000000..2d14c05 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/PkceVerificationResult.cs @@ -0,0 +1,7 @@ +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record PkceVerificationResult + { + public bool IsValid { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/PkceVerifyRequest.cs b/src/CodeBeam.UltimateAuth.Core/Models/PkceVerifyRequest.cs new file mode 100644 index 0000000..a6b6776 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/PkceVerifyRequest.cs @@ -0,0 +1,8 @@ +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record PkceVerifyRequest + { + public string Challenge { get; init; } = default!; + public string Verifier { get; init; } = default!; + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/ReauthRequest.cs b/src/CodeBeam.UltimateAuth.Core/Models/ReauthRequest.cs new file mode 100644 index 0000000..9479357 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/ReauthRequest.cs @@ -0,0 +1,11 @@ +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record ReauthRequest + { + public string? TenantId { get; init; } + public AuthSessionId SessionId { get; init; } + public string Secret { get; init; } = default!; + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/ReauthResult.cs b/src/CodeBeam.UltimateAuth.Core/Models/ReauthResult.cs new file mode 100644 index 0000000..97846c0 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/ReauthResult.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record ReauthResult + { + public bool Success { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/RegisterUserRequest.cs b/src/CodeBeam.UltimateAuth.Core/Models/RegisterUserRequest.cs new file mode 100644 index 0000000..6a9ed71 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/RegisterUserRequest.cs @@ -0,0 +1,30 @@ +namespace CodeBeam.UltimateAuth.Core.Models +{ + /// + /// Request to register a new user with credentials. + /// + public sealed class RegisterUserRequest + { + /// + /// Unique user identifier (username, email, or external id). + /// Interpretation is application-specific. + /// + public required string Identifier { get; init; } + + /// + /// Plain-text password. + /// Will be hashed by the configured password hasher. + /// + public required string Password { get; init; } + + /// + /// Optional tenant identifier. + /// + public string? TenantId { get; init; } + + /// + /// Optional initial claims or metadata. + /// + public IReadOnlyDictionary? Metadata { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshRequest.cs b/src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshRequest.cs new file mode 100644 index 0000000..deee12a --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshRequest.cs @@ -0,0 +1,8 @@ +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record SessionRefreshRequest + { + public string? TenantId { get; init; } + public string RefreshToken { get; init; } = default!; + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshResult.cs b/src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshResult.cs new file mode 100644 index 0000000..4b371cb --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshResult.cs @@ -0,0 +1,10 @@ +using CodeBeam.UltimateAuth.Core.Contexts; + +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record SessionRefreshResult + { + public IssuedAccessToken AccessToken { get; init; } = default!; + public IssuedRefreshToken? RefreshToken { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/TokenIssueContext.cs b/src/CodeBeam.UltimateAuth.Core/Models/TokenIssueContext.cs new file mode 100644 index 0000000..ebf2e0b --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/TokenIssueContext.cs @@ -0,0 +1,23 @@ +using CodeBeam.UltimateAuth.Core.Domain; +using System.Security.Claims; + +namespace CodeBeam.UltimateAuth.Core.Models; + +public sealed record TokenIssueContext +{ + public string? TenantId { get; init; } + + public TUserId UserId { get; init; } = default!; + + public AuthSessionId? SessionId { get; init; } + + /// + /// Claims to embed into the access token (JWT or stored metadata). + /// + public IReadOnlyCollection Claims { get; init; } = Array.Empty(); + + /// + /// Indicates whether a refresh token should be issued. + /// + public bool IssueRefreshToken { get; init; } = true; +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/TokenIssueResult.cs b/src/CodeBeam.UltimateAuth.Core/Models/TokenIssueResult.cs new file mode 100644 index 0000000..9accd5c --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/TokenIssueResult.cs @@ -0,0 +1,14 @@ +using CodeBeam.UltimateAuth.Core.Contexts; + +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record TokenIssueResult + { + public IssuedAccessToken AccessToken { get; init; } = default!; + + public IssuedRefreshToken? RefreshToken { get; init; } + + public static TokenIssueResult From(IssuedAccessToken access, IssuedRefreshToken? refresh) + => new() { AccessToken = access, RefreshToken = refresh }; + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/TokenRefreshContext.cs b/src/CodeBeam.UltimateAuth.Core/Models/TokenRefreshContext.cs new file mode 100644 index 0000000..fa561d3 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/TokenRefreshContext.cs @@ -0,0 +1,9 @@ +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record TokenRefreshContext + { + public string? TenantId { get; init; } + + public string RefreshToken { get; init; } = default!; + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/TokenValidationResult.cs b/src/CodeBeam.UltimateAuth.Core/Models/TokenValidationResult.cs new file mode 100644 index 0000000..353f4f0 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/TokenValidationResult.cs @@ -0,0 +1,67 @@ +using CodeBeam.UltimateAuth.Core.Domain; +using System.Security.Claims; + +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record TokenValidationResult + { + public bool IsValid { get; init; } + public TokenType Type { get; init; } + public string? TenantId { get; init; } + public TUserId? UserId { get; init; } + public AuthSessionId? SessionId { get; init; } + public IReadOnlyCollection Claims { get; init; } = Array.Empty(); + public TokenInvalidReason? InvalidReason { get; init; } + public DateTime? ExpiresAt { get; set; } + + private TokenValidationResult( + bool isValid, + TokenType type, + string? tenantId, + TUserId? userId, + AuthSessionId? sessionId, + IReadOnlyCollection? claims, + TokenInvalidReason? invalidReason, + DateTime? expiresAt + ) + { + IsValid = isValid; + TenantId = tenantId; + UserId = userId; + SessionId = sessionId; + Claims = claims ?? Array.Empty(); + InvalidReason = invalidReason; + ExpiresAt = expiresAt; + } + + public static TokenValidationResult Valid( + TokenType type, + string? tenantId, + TUserId userId, + AuthSessionId? sessionId, + IReadOnlyCollection claims, + DateTime? expiresAt) + => new( + isValid: true, + type, + tenantId, + userId, + sessionId, + claims, + invalidReason: null, + expiresAt + ); + + public static TokenValidationResult Invalid(TokenType type, TokenInvalidReason reason) + => new( + isValid: false, + type, + tenantId: null, + userId: default, + sessionId: null, + claims: null, + invalidReason: reason, + expiresAt: null + ); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/ValidateCredentialsRequest.cs b/src/CodeBeam.UltimateAuth.Core/Models/ValidateCredentialsRequest.cs new file mode 100644 index 0000000..711e3f0 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/ValidateCredentialsRequest.cs @@ -0,0 +1,24 @@ +namespace CodeBeam.UltimateAuth.Core.Models +{ + /// + /// Request to validate user credentials. + /// Used during login flows. + /// + public sealed class ValidateCredentialsRequest + { + /// + /// User identifier (same value used during registration). + /// + public required string Identifier { get; init; } + + /// + /// Plain-text password provided by the user. + /// + public required string Password { get; init; } + + /// + /// Optional tenant identifier. + /// + public string? TenantId { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserManagementService.cs b/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserManagementService.cs new file mode 100644 index 0000000..255d4ab --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserManagementService.cs @@ -0,0 +1,28 @@ +namespace CodeBeam.UltimateAuth.Core.Users +{ + /// + /// Administrative user management operations. + /// + public interface IUAuthUserManagementService + { + Task> GetByIdAsync( + TUserId userId, + CancellationToken ct = default); + + Task>> GetAllAsync( + CancellationToken ct = default); + + Task DisableAsync( + TUserId userId, + CancellationToken ct = default); + + Task EnableAsync( + TUserId userId, + CancellationToken ct = default); + + Task ResetPasswordAsync( + TUserId userId, + ResetPasswordRequest request, + CancellationToken ct = default); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserProfileService.cs b/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserProfileService.cs new file mode 100644 index 0000000..ea2dcb3 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserProfileService.cs @@ -0,0 +1,23 @@ +namespace CodeBeam.UltimateAuth.Core.Users.Abstractions +{ + /// + /// User self-service operations (profile, password, MFA). + /// + public interface IUAuthUserProfileService + { + Task> GetCurrentAsync( + CancellationToken ct = default); + + Task UpdateProfileAsync( + UpdateProfileRequest request, + CancellationToken ct = default); + + Task ChangePasswordAsync( + ChangePasswordRequest request, + CancellationToken ct = default); + + Task ConfigureMfaAsync( + ConfigureMfaRequest request, + CancellationToken ct = default); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserService.cs b/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserService.cs new file mode 100644 index 0000000..4998288 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserService.cs @@ -0,0 +1,25 @@ +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Models; + +namespace CodeBeam.UltimateAuth.Core.Abstractions +{ + /// + /// Minimal user operations required for authentication. + /// Does NOT include role or permission management. + /// For user management, CodeBeam.UltimateAuth.Users package is recommended. + /// + public interface IUAuthUserService + { + Task RegisterAsync( + RegisterUserRequest request, + CancellationToken cancellationToken = default); + + Task DeleteAsync( + TUserId userId, + CancellationToken cancellationToken = default); + + Task ValidateCredentialsAsync( + ValidateCredentialsRequest request, + CancellationToken cancellationToken = default); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Models/AdminUserFilter.cs b/src/CodeBeam.UltimateAuth.Core/Users/Models/AdminUserFilter.cs new file mode 100644 index 0000000..e6256f2 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Users/Models/AdminUserFilter.cs @@ -0,0 +1,10 @@ +namespace CodeBeam.UltimateAuth.Core.Users.Models +{ + public sealed class AdminUserFilter + { + public bool? IsActive { get; init; } + public bool? IsEmailConfirmed { get; init; } + + public string? Search { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Models/ChangePasswordRequest.cs b/src/CodeBeam.UltimateAuth.Core/Users/Models/ChangePasswordRequest.cs new file mode 100644 index 0000000..e4a9b37 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Users/Models/ChangePasswordRequest.cs @@ -0,0 +1,13 @@ +namespace CodeBeam.UltimateAuth.Core.Users +{ + public sealed class ChangePasswordRequest + { + public required string CurrentPassword { get; init; } + public required string NewPassword { get; init; } + + /// + /// If true, other sessions will be revoked. + /// + public bool RevokeOtherSessions { get; init; } = true; + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Models/ConfigureMfaRequest.cs b/src/CodeBeam.UltimateAuth.Core/Users/Models/ConfigureMfaRequest.cs new file mode 100644 index 0000000..4f35557 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Users/Models/ConfigureMfaRequest.cs @@ -0,0 +1,12 @@ +namespace CodeBeam.UltimateAuth.Core.Users +{ + public sealed class ConfigureMfaRequest + { + public bool Enable { get; init; } + + /// + /// Optional verification code when enabling MFA. + /// + public string? VerificationCode { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Models/ResetPasswordRequest.cs b/src/CodeBeam.UltimateAuth.Core/Users/Models/ResetPasswordRequest.cs new file mode 100644 index 0000000..48733c0 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Users/Models/ResetPasswordRequest.cs @@ -0,0 +1,12 @@ +namespace CodeBeam.UltimateAuth.Core.Users +{ + public sealed class ResetPasswordRequest + { + public required string NewPassword { get; init; } + + /// + /// If true, all active sessions will be revoked. + /// + public bool RevokeSessions { get; init; } = true; + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Models/UpdateProfileRequest.cs b/src/CodeBeam.UltimateAuth.Core/Users/Models/UpdateProfileRequest.cs new file mode 100644 index 0000000..db6b710 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Users/Models/UpdateProfileRequest.cs @@ -0,0 +1,8 @@ +namespace CodeBeam.UltimateAuth.Core.Users +{ + public sealed class UpdateProfileRequest + { + public string? Username { get; init; } + public string? Email { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Models/UserDto.cs b/src/CodeBeam.UltimateAuth.Core/Users/Models/UserDto.cs new file mode 100644 index 0000000..f81ba14 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Users/Models/UserDto.cs @@ -0,0 +1,16 @@ +namespace CodeBeam.UltimateAuth.Core.Users +{ + public sealed class UserDto + { + public required TUserId UserId { get; init; } + + public string? Username { get; init; } + public string? Email { get; init; } + + public bool IsActive { get; init; } + public bool IsEmailConfirmed { get; init; } + + public DateTime CreatedAt { get; init; } + public DateTime? LastLoginAt { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Models/UserProfileDto.cs b/src/CodeBeam.UltimateAuth.Core/Users/Models/UserProfileDto.cs new file mode 100644 index 0000000..6724614 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Users/Models/UserProfileDto.cs @@ -0,0 +1,14 @@ +namespace CodeBeam.UltimateAuth.Core.Users +{ + public sealed class UserProfileDto + { + public required TUserId UserId { get; init; } + + public string? Username { get; init; } + public string? Email { get; init; } + + public bool IsEmailConfirmed { get; init; } + + public DateTime CreatedAt { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Users/UserIdFactory.cs b/src/CodeBeam.UltimateAuth.Core/Users/UserIdFactory.cs new file mode 100644 index 0000000..a134eb7 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Users/UserIdFactory.cs @@ -0,0 +1,10 @@ +using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Utilities +{ + public sealed class UserIdFactory : IUserIdFactory + { + public UserId Create() => new UserId(Guid.NewGuid().ToString("N")); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Users/UserRecord.cs b/src/CodeBeam.UltimateAuth.Core/Users/UserRecord.cs new file mode 100644 index 0000000..b632a09 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Users/UserRecord.cs @@ -0,0 +1,11 @@ +namespace CodeBeam.UltimateAuth.Core.Users +{ + public sealed class UserRecord + { + public required TUserId Id { get; init; } + public required string Username { get; init; } + public required string PasswordHash { get; init; } + public DateTime CreatedAt { get; init; } + public bool IsDeleted { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Utilities/GuidUserIdFactory.cs b/src/CodeBeam.UltimateAuth.Core/Utilities/GuidUserIdFactory.cs new file mode 100644 index 0000000..195e5e0 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Utilities/GuidUserIdFactory.cs @@ -0,0 +1,9 @@ +using CodeBeam.UltimateAuth.Core.Abstractions; + +namespace CodeBeam.UltimateAuth.Core.Utilities +{ + public sealed class GuidUserIdFactory : IUserIdFactory + { + public Guid Create() => Guid.NewGuid(); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Utilities/StringUserIdFactory.cs b/src/CodeBeam.UltimateAuth.Core/Utilities/StringUserIdFactory.cs new file mode 100644 index 0000000..624490e --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Utilities/StringUserIdFactory.cs @@ -0,0 +1,9 @@ +using CodeBeam.UltimateAuth.Core.Abstractions; + +namespace CodeBeam.UltimateAuth.Core.Utilities +{ + public sealed class StringUserIdFactory : IUserIdFactory + { + public string Create() => Guid.NewGuid().ToString("N"); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Utilities/UAuthUserIdConverter.cs b/src/CodeBeam.UltimateAuth.Core/Utilities/UAuthUserIdConverter.cs index e0ebae5..a885a29 100644 --- a/src/CodeBeam.UltimateAuth.Core/Utilities/UAuthUserIdConverter.cs +++ b/src/CodeBeam.UltimateAuth.Core/Utilities/UAuthUserIdConverter.cs @@ -71,6 +71,20 @@ public TUserId FromString(string value) }; } + public bool TryFromString(string value, out TUserId? id) + { + try + { + id = FromString(value); + return true; + } + catch + { + id = default; + return false; + } + } + /// /// Converts a UTF-8 encoded binary representation back into a user id. /// @@ -79,5 +93,19 @@ public TUserId FromString(string value) public TUserId FromBytes(byte[] binary) => FromString(Encoding.UTF8.GetString(binary)); + public bool TryFromBytes(byte[] binary, out TUserId? id) + { + try + { + id = FromBytes(binary); + return true; + } + catch + { + id = default; + return false; + } + } + } } diff --git a/src/CodeBeam.UltimateAuth.Server/CodeBeam.UltimateAuth.Server.csproj b/src/CodeBeam.UltimateAuth.Server/CodeBeam.UltimateAuth.Server.csproj index d4e9395..7d9e6f8 100644 --- a/src/CodeBeam.UltimateAuth.Server/CodeBeam.UltimateAuth.Server.csproj +++ b/src/CodeBeam.UltimateAuth.Server/CodeBeam.UltimateAuth.Server.csproj @@ -8,15 +8,19 @@ - + - + + + + + diff --git a/src/CodeBeam.UltimateAuth.Server/Extensions/UAuthServerServiceCollectionExtensions.cs b/src/CodeBeam.UltimateAuth.Server/Extensions/UAuthServerServiceCollectionExtensions.cs index 8520014..c34701d 100644 --- a/src/CodeBeam.UltimateAuth.Server/Extensions/UAuthServerServiceCollectionExtensions.cs +++ b/src/CodeBeam.UltimateAuth.Server/Extensions/UAuthServerServiceCollectionExtensions.cs @@ -1,10 +1,13 @@ -using CodeBeam.UltimateAuth.Core.Extensions; +using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Extensions; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Core.Options; using CodeBeam.UltimateAuth.Server.Endpoints; using CodeBeam.UltimateAuth.Server.Issuers; using CodeBeam.UltimateAuth.Server.MultiTenancy; using CodeBeam.UltimateAuth.Server.Options; +using CodeBeam.UltimateAuth.Server.Services; +using CodeBeam.UltimateAuth.Server.Users; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -84,6 +87,11 @@ private static IServiceCollection AddUltimateAuthServerInternal( services.TryAddScoped(); + services.AddScoped(typeof(IUAuthFlowService), typeof(UAuthFlowService<>)); + services.AddScoped(typeof(IUAuthSessionService<>), typeof(UAuthSessionService<>)); + services.AddScoped(typeof(IUAuthUserService<>), typeof(UAuthUserService<>)); + services.AddScoped(typeof(IUAuthTokenService<>), typeof(UAuthTokenService<>)); + // ----------------------------- // SESSION / TOKEN ISSUERS // ----------------------------- diff --git a/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthTokenIssuer.cs b/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthTokenIssuer.cs index 46d6e3f..d20b2aa 100644 --- a/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthTokenIssuer.cs +++ b/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthTokenIssuer.cs @@ -76,7 +76,7 @@ private IssuedAccessToken IssueOpaqueAccessToken(DateTimeOffset expires, string? return new IssuedAccessToken { Token = token, - TokenType = "opaque", + Type = TokenType.Opaque, ExpiresAt = expires, SessionId = sessionId }; @@ -117,7 +117,7 @@ private IssuedAccessToken IssueJwtAccessToken(TokenIssueContext context, DateTim return new IssuedAccessToken { Token = jwt, - TokenType = "jwt", + Type = TokenType.Jwt, ExpiresAt = expires, SessionId = context.SessionId }; diff --git a/src/CodeBeam.UltimateAuth.Server/Services/.gitkeep b/src/CodeBeam.UltimateAuth.Server/Services/.gitkeep deleted file mode 100644 index 5f28270..0000000 --- a/src/CodeBeam.UltimateAuth.Server/Services/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/CodeBeam.UltimateAuth.Server/Services/UAuthFlowService.cs b/src/CodeBeam.UltimateAuth.Server/Services/UAuthFlowService.cs new file mode 100644 index 0000000..2a5c9e9 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Server/Services/UAuthFlowService.cs @@ -0,0 +1,81 @@ +using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Models; + +namespace CodeBeam.UltimateAuth.Server.Services +{ + internal sealed class UAuthFlowService : IUAuthFlowService + { + private readonly IUAuthUserService _users; + private readonly IUAuthSessionService _sessions; + private readonly IUAuthTokenService _tokens; + + public UAuthFlowService( + IUAuthUserService users, + IUAuthSessionService sessions, + IUAuthTokenService tokens) + { + _users = users; + _sessions = sessions; + _tokens = tokens; + } + + public Task BeginMfaAsync(BeginMfaRequest request, CancellationToken ct = default) + { + throw new NotImplementedException(); + } + + public Task CompleteMfaAsync(CompleteMfaRequest request, CancellationToken ct = default) + { + throw new NotImplementedException(); + } + + public Task ConsumePkceAsync(PkceConsumeRequest request, CancellationToken ct = default) + { + throw new NotImplementedException(); + } + + public Task CreatePkceChallengeAsync(PkceCreateRequest request, CancellationToken ct = default) + { + throw new NotImplementedException(); + } + + public Task ExternalLoginAsync(ExternalLoginRequest request, CancellationToken ct = default) + { + throw new NotImplementedException(); + } + + public async Task LoginAsync( + LoginRequest request, + CancellationToken ct = default) + { + // burayı birazdan dolduracağız + throw new NotImplementedException(); + } + + public Task LogoutAllAsync(LogoutAllRequest request, CancellationToken ct = default) + { + throw new NotImplementedException(); + } + + public Task LogoutAsync(LogoutRequest request, CancellationToken ct = default) + { + throw new NotImplementedException(); + } + + public Task ReauthenticateAsync(ReauthRequest request, CancellationToken ct = default) + { + throw new NotImplementedException(); + } + + public Task RefreshSessionAsync(SessionRefreshRequest request, CancellationToken ct = default) + { + throw new NotImplementedException(); + } + + public Task VerifyPkceAsync(PkceVerifyRequest request, CancellationToken ct = default) + { + throw new NotImplementedException(); + } + } + +} diff --git a/src/CodeBeam.UltimateAuth.Server/Services/UAuthSessionService.cs b/src/CodeBeam.UltimateAuth.Server/Services/UAuthSessionService.cs new file mode 100644 index 0000000..f94a7a2 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Server/Services/UAuthSessionService.cs @@ -0,0 +1,58 @@ +using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Models; +using CodeBeam.UltimateAuth.Server.Sessions; + +namespace CodeBeam.UltimateAuth.Server.Services +{ + internal sealed class UAuthSessionService : IUAuthSessionService + { + private readonly ISessionOrchestrator _engine; + + public UAuthSessionService( + ISessionOrchestrator engine) + { + _engine = engine; + } + + public Task> ValidateSessionAsync( + string? tenantId, + AuthSessionId sessionId, + DateTime now) + => _engine.ValidateSessionAsync(tenantId, sessionId, now); + + public Task>> GetChainsAsync( + string? tenantId, + TUserId userId) + => _engine.GetChainsAsync(tenantId, userId); + + public Task>> GetSessionsAsync( + string? tenantId, + ChainId chainId) + => _engine.GetSessionsAsync(tenantId, chainId); + + public Task?> GetCurrentSessionAsync( + string? tenantId, + AuthSessionId sessionId) + => _engine.GetSessionAsync(tenantId, sessionId); + + public Task RevokeSessionAsync( + string? tenantId, + AuthSessionId sessionId, + DateTime at) + => _engine.RevokeSessionAsync(tenantId, sessionId, at); + + public Task RevokeChainAsync( + string? tenantId, + ChainId chainId, + DateTime at) + => _engine.RevokeChainAsync(tenantId, chainId, at); + + public Task RevokeRootAsync( + string? tenantId, + TUserId userId, + DateTime at) + => _engine.RevokeRootAsync(tenantId, userId, at); + } + +} diff --git a/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenService.cs b/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenService.cs new file mode 100644 index 0000000..0f6348f --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenService.cs @@ -0,0 +1,58 @@ +using CodeBeam.UltimateAuth.Core; +using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Contexts; +using CodeBeam.UltimateAuth.Core.Models; + +namespace CodeBeam.UltimateAuth.Server.Services +{ + internal sealed class UAuthTokenService : IUAuthTokenService + { + private readonly ITokenIssuer _issuer; + private readonly ITokenValidator _validator; + private readonly IUserIdConverter _userIdConverter; + + public UAuthTokenService(ITokenIssuer issuer, ITokenValidator validator, IUserIdConverterResolver converterResolver) + { + _issuer = issuer; + _validator = validator; + _userIdConverter = converterResolver.GetConverter(); + } + + public async Task IssueAsync( + TokenIssueContext context, + CancellationToken ct = default) + { + var issuerCtx = ToIssuerContext(context); + + var access = await _issuer.IssueAccessTokenAsync(issuerCtx, ct); + var refresh = await _issuer.IssueRefreshTokenAsync(issuerCtx, ct); + + return TokenIssueResult.From(access, refresh); + } + + public async Task RefreshAsync( + TokenRefreshContext context, + CancellationToken ct = default) + { + throw new NotImplementedException("Refresh flow will be implemented after refresh-token store & validation."); + } + + public async Task> ValidateAsync( + string token, + TokenType type, + CancellationToken ct = default) + => await _validator.ValidateAsync(token, type, ct); + + private TokenIssueContext ToIssuerContext(TokenIssueContext src) + { + return new TokenIssueContext + { + UserId = _userIdConverter.ToString(src.UserId), + TenantId = src.TenantId, + SessionId = src.SessionId, + Claims = src.Claims + }; + } + + } +} diff --git a/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenValidator.cs b/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenValidator.cs new file mode 100644 index 0000000..d6414b2 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenValidator.cs @@ -0,0 +1,165 @@ +using CodeBeam.UltimateAuth.Core; +using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Models; +using CodeBeam.UltimateAuth.Server.Options; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.JsonWebTokens; +using Microsoft.IdentityModel.Tokens; +using System.Security.Claims; + +namespace CodeBeam.UltimateAuth.Server.Services +{ + internal sealed class UAuthTokenValidator : ITokenValidator + { + private readonly IOpaqueTokenStore _opaqueStore; + private readonly JsonWebTokenHandler _jwtHandler; + private readonly TokenValidationParameters _jwtParameters; + private readonly IUserIdConverterResolver _converters; + private readonly UAuthServerOptions _options; + private readonly ITokenHasher _tokenHasher; + + public UAuthTokenValidator( + IOpaqueTokenStore opaqueStore, + TokenValidationParameters jwtParameters, + IUserIdConverterResolver converters, + IOptions options, + ITokenHasher tokenHasher) + { + _opaqueStore = opaqueStore; + _jwtHandler = new JsonWebTokenHandler(); + _jwtParameters = jwtParameters; + _converters = converters; + _options = options.Value; + _tokenHasher = tokenHasher; + } + + public async Task> ValidateAsync( + string token, + TokenType type, + CancellationToken ct = default) + { + return type switch + { + TokenType.Jwt => await ValidateJwt(token), + TokenType.Opaque => await ValidateOpaqueAsync(token, ct), + _ => TokenValidationResult.Invalid(TokenType.Unknown, TokenInvalidReason.Unknown) + }; + } + + // ---------------- JWT ---------------- + + private async Task> ValidateJwt(string token) + { + var result = await _jwtHandler.ValidateTokenAsync(token, _jwtParameters); + + if (!result.IsValid) + { + return TokenValidationResult.Invalid(TokenType.Jwt, MapJwtError(result.Exception)); + } + + var jwt = (JsonWebToken)result.SecurityToken; + var claims = jwt.Claims.ToArray(); + + var converter = _converters.GetConverter(); + + var userIdString = jwt.GetClaim(ClaimTypes.NameIdentifier)?.Value ?? jwt.GetClaim("sub")?.Value; + if (string.IsNullOrWhiteSpace(userIdString)) + { + return TokenValidationResult.Invalid(TokenType.Jwt, TokenInvalidReason.MissingSubject); + } + + TUserId userId; + try + { + userId = converter.FromString(userIdString); + } + catch + { + return TokenValidationResult.Invalid(TokenType.Jwt, TokenInvalidReason.Malformed); + } + + var tenantId = jwt.GetClaim("tenant")?.Value ?? jwt.GetClaim("tid")?.Value; + AuthSessionId? sessionId = null; + var sid = jwt.GetClaim("sid")?.Value; + if (!string.IsNullOrWhiteSpace(sid)) + { + sessionId = new AuthSessionId(sid); + } + + return TokenValidationResult.Valid( + type: TokenType.Jwt, + tenantId: tenantId, + userId, + sessionId: sessionId, + claims: claims, + expiresAt: jwt.ValidTo); + } + + + // ---------------- OPAQUE ---------------- + + private async Task> ValidateOpaqueAsync(string token, CancellationToken ct) + { + var hash = _tokenHasher.Hash(token); + + var record = await _opaqueStore.FindByHashAsync(hash, ct); + if (record is null) + { + return TokenValidationResult.Invalid( + TokenType.Opaque, + TokenInvalidReason.Invalid); + } + + var now = DateTimeOffset.UtcNow; + if (record.ExpiresAt <= now) + { + return TokenValidationResult.Invalid( + TokenType.Opaque, + TokenInvalidReason.Expired); + } + + if (record.IsRevoked) + { + return TokenValidationResult.Invalid( + TokenType.Opaque, + TokenInvalidReason.Revoked); + } + + var converter = _converters.GetConverter(); + + TUserId userId; + try + { + userId = converter.FromString(record.UserId); + } + catch + { + return TokenValidationResult.Invalid( + TokenType.Opaque, + TokenInvalidReason.Invalid); + } + + return TokenValidationResult.Valid( + TokenType.Opaque, + record.TenantId, + userId, + record.SessionId, + record.Claims, + record.ExpiresAt.UtcDateTime); + } + + private static TokenInvalidReason MapJwtError(Exception? ex) + { + return ex switch + { + SecurityTokenExpiredException => TokenInvalidReason.Expired, + SecurityTokenInvalidSignatureException => TokenInvalidReason.SignatureInvalid, + SecurityTokenInvalidAudienceException => TokenInvalidReason.AudienceMismatch, + SecurityTokenInvalidIssuerException => TokenInvalidReason.IssuerMismatch, + _ => TokenInvalidReason.Invalid + }; + } + + } +} diff --git a/src/CodeBeam.UltimateAuth.Server/Services/UAuthUserService.cs b/src/CodeBeam.UltimateAuth.Server/Services/UAuthUserService.cs new file mode 100644 index 0000000..f44bfea --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Server/Services/UAuthUserService.cs @@ -0,0 +1,73 @@ +using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Models; +using CodeBeam.UltimateAuth.Core.Users; + +namespace CodeBeam.UltimateAuth.Server.Users; + +internal sealed class UAuthUserService : IUAuthUserService +{ + private readonly IUAuthUserStore _userStore; + private readonly IUAuthPasswordHasher _passwordHasher; + private readonly IUserIdFactory _userIdFactory; + + public UAuthUserService( + IUAuthUserStore userStore, + IUAuthPasswordHasher passwordHasher, + IUserIdFactory userIdFactory) + { + _userStore = userStore; + _passwordHasher = passwordHasher; + _userIdFactory = userIdFactory; + } + + public async Task RegisterAsync( + RegisterUserRequest request, + CancellationToken ct = default) + { + if (string.IsNullOrWhiteSpace(request.Identifier)) + throw new ArgumentException("Username is required."); + + if (string.IsNullOrWhiteSpace(request.Password)) + throw new ArgumentException("Password is required."); + + if (await _userStore.ExistsByUsernameAsync(request.Identifier, ct)) + throw new InvalidOperationException("User already exists."); + + var hash = _passwordHasher.Hash(request.Password); + + var userId = _userIdFactory.Create(); + + await _userStore.CreateAsync( + new UserRecord + { + Id = userId, + Username = request.Identifier, + PasswordHash = hash, + CreatedAt = DateTime.UtcNow + }, + ct); + + return userId; + } + + public async Task ValidateCredentialsAsync( + ValidateCredentialsRequest request, + CancellationToken ct = default) + { + var user = await _userStore.FindByUsernameAsync(request.Identifier, ct); + if (user is null) + return false; + + return _passwordHasher.Verify( + request.Password, + user.PasswordHash); + } + + public Task DeleteAsync( + TUserId userId, + CancellationToken ct = default) + { + return _userStore.DeleteAsync(userId, ct); + } +} diff --git a/src/CodeBeam.UltimateAuth.Server/Users/UAuthUserAccessor.cs b/src/CodeBeam.UltimateAuth.Server/Users/UAuthUserAccessor.cs index 0769479..a292f37 100644 --- a/src/CodeBeam.UltimateAuth.Server/Users/UAuthUserAccessor.cs +++ b/src/CodeBeam.UltimateAuth.Server/Users/UAuthUserAccessor.cs @@ -9,11 +9,11 @@ namespace CodeBeam.UltimateAuth.Server.Users public sealed class UAuthUserAccessor : IUserAccessor { private readonly ISessionStore _sessionStore; - private readonly IUserStore _userStore; + private readonly IUAuthUserStore _userStore; public UAuthUserAccessor( ISessionStore sessionStore, - IUserStore userStore) + IUAuthUserStore userStore) { _sessionStore = sessionStore; _userStore = userStore; diff --git a/src/CodeBeam.UltimateAuth.Server/Users/UAuthUserId.cs b/src/CodeBeam.UltimateAuth.Server/Users/UAuthUserId.cs new file mode 100644 index 0000000..eaaa711 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Server/Users/UAuthUserId.cs @@ -0,0 +1,12 @@ +namespace CodeBeam.UltimateAuth.Server.Users +{ + public readonly record struct UAuthUserId(Guid Value) + { + public override string ToString() => Value.ToString("N"); + + public static UAuthUserId New() => new(Guid.NewGuid()); + + public static implicit operator Guid(UAuthUserId id) => id.Value; + public static implicit operator UAuthUserId(Guid value) => new(value); + } +} From 7ee3e21f241688f678329ced4399ac611b1ca413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Tue, 16 Dec 2025 01:16:41 +0300 Subject: [PATCH 3/4] Login Flow & Code Improvements --- .../Abstractions/IClock.cs | 11 + .../Abstractions/IUserAuthenticator.cs | 13 + .../Abstractions/Issuers/ISessionIssuer.cs | 2 +- .../Abstractions/Issuers/ITokenIssuer.cs | 5 +- .../Services/IUAuthSessionService.cs | 19 +- .../Services/IUAuthTokenService.cs | 4 +- .../Stores/ISessionStoreKernel.cs | 14 +- .../Abstractions/Stores/IUAuthUserStore.cs | 9 +- .../Contexts/AuthenticatedSessionContext.cs | 31 ++ .../{IssuedAccessToken.cs => AccessToken.cs} | 4 +- ...{IssuedRefreshToken.cs => RefreshToken.cs} | 2 +- .../Contexts/SessionIssueContext.cs | 23 -- .../Contexts/SessionRotationContext.cs | 15 + .../Contexts/SessionValidationContext.cs | 12 + .../Contexts/TokenIssueContext.cs | 16 - .../Domain/Session/ClaimsSnapshot.cs | 69 ++++ .../Domain/Session/DeviceInfo.cs | 39 ++- .../Domain/Session/ISession.cs | 6 + .../Domain/Session/ISessionChain.cs | 6 +- .../Domain/Session/SessionMetadata.cs | 6 - .../Domain/Session/SessionState.cs | 41 +-- .../Domain/Session/UAuthSession.cs | 76 +++-- .../Domain/Session/UAuthSessionChain.cs | 28 +- .../Enums/LoginContinuationType.cs | 9 + .../Enums/LoginStatus.cs | 9 + .../Errors/Base/UAuthChainException.cs | 17 + .../Session/UAuthChainLinkMissingException.cs | 14 + .../UAuthSessionChainNotFoundException.cs | 12 + .../UAuthSessionChainRevokedException.cs | 14 + .../UAuthSessionDeviceMismatchException.cs | 22 ++ .../UAuthSessionExpiredException.cs | 0 .../UAuthSessionInvalidStateException.cs | 19 ++ .../UAuthSessionNotActiveException.cs | 0 .../Session/UAuthSessionNotFoundException.cs | 13 + .../UAuthSessionRevokedException.cs | 0 .../UAuthSessionRootRevokedException.cs | 13 + .../UAuthSessionSecurityMismatchException.cs | 17 + .../UAuthSecurityVersionMismatchException.cs | 37 -- .../Models/AuthTokens.cs | 19 ++ .../Models/LoginContinuation.cs | 20 ++ .../Models/LoginRequest.cs | 18 +- .../Models/LoginResult.cs | 34 +- .../Models/LogoutAllRequest.cs | 7 + .../Models/LogoutRequest.cs | 6 + .../Models/SessionRefreshResult.cs | 4 +- .../Models/SessionValidationResult.cs | 57 ++-- .../Models/TokenIssueContext.cs | 26 +- .../Models/TokenIssueResult.cs | 14 - .../Models/TokenIssuerContext.cs | 13 + .../Models/UserAuthenticationResult.cs | 24 ++ .../Users/Abstractions/IUAuthUserService.cs | 6 + .../Users/UserRecord.cs | 7 +- .../Abstractions/IDeviceResolver.cs | 13 + .../CodeBeam.UltimateAuth.Server.csproj | 4 - .../Endpoints/DefaultLoginEndpointHandler.cs | 61 +++- .../Events/.gitkeep | 1 - .../Extensions/ClaimsSnapshotExtensions.cs | 14 + .../Internal/.gitkeep | 1 - .../Issuers/UAuthSessionIssuer.cs | 15 +- .../Issuers/UAuthTokenIssuer.cs | 17 +- .../Services/UAuthFlowService.cs | 87 ++++- .../Services/UAuthSessionService.cs | 93 ++++- .../Services/UAuthTokenService.cs | 21 +- .../Services/UAuthUserService.cs | 25 +- .../Sessions/ISessionOrchestrator.cs | 49 ++- .../Sessions/UAuthSessionOrchestrator.cs | 319 ++++++++++++------ .../Users/AspNetIdentityUserStore.cs | 38 +++ .../Users/DefaultUserAuthenticator.cs | 40 +++ .../Users/UAuthUserAccessor.cs | 2 +- .../Utility/SystemClock.cs | 9 + .../Utility/UAuthDeviceResolver.cs | 49 +++ 71 files changed, 1329 insertions(+), 431 deletions(-) create mode 100644 src/CodeBeam.UltimateAuth.Core/Abstractions/IClock.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Abstractions/IUserAuthenticator.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Contexts/AuthenticatedSessionContext.cs rename src/CodeBeam.UltimateAuth.Core/Contexts/Issued/{IssuedAccessToken.cs => AccessToken.cs} (91%) rename src/CodeBeam.UltimateAuth.Core/Contexts/Issued/{IssuedRefreshToken.cs => RefreshToken.cs} (93%) delete mode 100644 src/CodeBeam.UltimateAuth.Core/Contexts/SessionIssueContext.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Contexts/SessionRotationContext.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Contexts/SessionValidationContext.cs delete mode 100644 src/CodeBeam.UltimateAuth.Core/Contexts/TokenIssueContext.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Domain/Session/ClaimsSnapshot.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Enums/LoginContinuationType.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Enums/LoginStatus.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Errors/Base/UAuthChainException.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthChainLinkMissingException.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionChainNotFoundException.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionChainRevokedException.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionDeviceMismatchException.cs rename src/CodeBeam.UltimateAuth.Core/Errors/{ => Session}/UAuthSessionExpiredException.cs (100%) create mode 100644 src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionInvalidStateException.cs rename src/CodeBeam.UltimateAuth.Core/Errors/{ => Session}/UAuthSessionNotActiveException.cs (100%) create mode 100644 src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionNotFoundException.cs rename src/CodeBeam.UltimateAuth.Core/Errors/{ => Session}/UAuthSessionRevokedException.cs (100%) create mode 100644 src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionRootRevokedException.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionSecurityMismatchException.cs delete mode 100644 src/CodeBeam.UltimateAuth.Core/Errors/UAuthSecurityVersionMismatchException.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/AuthTokens.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/LoginContinuation.cs delete mode 100644 src/CodeBeam.UltimateAuth.Core/Models/TokenIssueResult.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/TokenIssuerContext.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Models/UserAuthenticationResult.cs create mode 100644 src/CodeBeam.UltimateAuth.Server/Abstractions/IDeviceResolver.cs delete mode 100644 src/CodeBeam.UltimateAuth.Server/Events/.gitkeep create mode 100644 src/CodeBeam.UltimateAuth.Server/Extensions/ClaimsSnapshotExtensions.cs delete mode 100644 src/CodeBeam.UltimateAuth.Server/Internal/.gitkeep create mode 100644 src/CodeBeam.UltimateAuth.Server/Users/AspNetIdentityUserStore.cs create mode 100644 src/CodeBeam.UltimateAuth.Server/Users/DefaultUserAuthenticator.cs create mode 100644 src/CodeBeam.UltimateAuth.Server/Utility/SystemClock.cs create mode 100644 src/CodeBeam.UltimateAuth.Server/Utility/UAuthDeviceResolver.cs diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/IClock.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/IClock.cs new file mode 100644 index 0000000..ce4905a --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/IClock.cs @@ -0,0 +1,11 @@ +namespace CodeBeam.UltimateAuth.Core.Abstractions +{ + /// + /// Provides an abstracted time source for the system. + /// Used to improve testability and ensure consistent time handling. + /// + public interface IClock + { + DateTime UtcNow { get; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/IUserAuthenticator.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/IUserAuthenticator.cs new file mode 100644 index 0000000..139d773 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/IUserAuthenticator.cs @@ -0,0 +1,13 @@ +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Abstractions +{ + public interface IUserAuthenticator + { + Task> AuthenticateAsync( + string? tenantId, + string identifier, + string secret, + CancellationToken cancellationToken = default); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ISessionIssuer.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ISessionIssuer.cs index f19db3e..732633d 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ISessionIssuer.cs +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ISessionIssuer.cs @@ -8,6 +8,6 @@ namespace CodeBeam.UltimateAuth.Core.Abstractions /// public interface ISessionIssuer { - Task> IssueAsync(SessionIssueContext context, UAuthSessionChain chain, CancellationToken cancellationToken = default); + Task> IssueAsync(AuthenticatedSessionContext context, ISessionChain chain, CancellationToken cancellationToken = default); } } diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenIssuer.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenIssuer.cs index 948bf3b..d1ac6bb 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenIssuer.cs +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenIssuer.cs @@ -1,4 +1,5 @@ using CodeBeam.UltimateAuth.Core.Contexts; +using CodeBeam.UltimateAuth.Core.Models; namespace CodeBeam.UltimateAuth.Core.Abstractions { @@ -8,7 +9,7 @@ namespace CodeBeam.UltimateAuth.Core.Abstractions /// public interface ITokenIssuer { - Task IssueAccessTokenAsync(TokenIssueContext context, CancellationToken cancellationToken = default); - Task IssueRefreshTokenAsync(TokenIssueContext context, CancellationToken cancellationToken = default); + Task IssueAccessTokenAsync(TokenIssuerContext context, CancellationToken cancellationToken = default); + Task IssueRefreshTokenAsync(TokenIssuerContext context, CancellationToken cancellationToken = default); } } diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthSessionService.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthSessionService.cs index fcc5b9e..60e6413 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthSessionService.cs +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthSessionService.cs @@ -1,4 +1,5 @@ -using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Contexts; +using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.Models; namespace CodeBeam.UltimateAuth.Core.Abstractions @@ -38,9 +39,25 @@ Task RevokeChainAsync( ChainId chainId, DateTime at); + Task ResolveChainIdAsync( + string? tenantId, + AuthSessionId sessionId); + + Task RevokeAllChainsAsync( + string? tenantId, + TUserId userId, + ChainId? exceptChainId, + DateTime at); + + // Hard revoke - admin Task RevokeRootAsync( string? tenantId, TUserId userId, DateTime at); + + Task> IssueSessionAfterAuthenticationAsync( + string? tenantId, + AuthenticatedSessionContext context, + CancellationToken cancellationToken = default); } } diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthTokenService.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthTokenService.cs index f7a84c9..aca3d88 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthTokenService.cs +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthTokenService.cs @@ -12,14 +12,14 @@ public interface IUAuthTokenService /// Issues access (and optionally refresh) tokens /// for a validated session. /// - Task IssueAsync( + Task CreateTokensAsync( TokenIssueContext context, CancellationToken cancellationToken = default); /// /// Refreshes tokens using a refresh token. /// - Task RefreshAsync( + Task RefreshAsync( TokenRefreshContext context, CancellationToken cancellationToken = default); diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/ISessionStoreKernel.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/ISessionStoreKernel.cs index 9d8763b..d398eb1 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/ISessionStoreKernel.cs +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/ISessionStoreKernel.cs @@ -56,13 +56,6 @@ public interface ISessionStoreKernel /// The chain to save. Task SaveChainAsync(string? tenantId, ISessionChain chain); - /// - /// Updates an existing session chain, typically after session rotation or revocation. Implementations must preserve atomicity. - /// - /// The tenant identifier, or null. - /// The updated session chain. - Task UpdateChainAsync(string? tenantId, ISessionChain chain); - /// /// Marks the entire session chain as revoked, invalidating all associated sessions for the device or app family. /// @@ -135,6 +128,11 @@ public interface ISessionStoreKernel /// The session identifier. /// The chain identifier or null. Task GetChainIdBySessionAsync(string? tenantId, AuthSessionId sessionId); - } + /// + /// Executes multiple store operations as a single atomic unit. + /// Implementations must ensure transactional consistency where supported. + /// + Task ExecuteAsync(Func action); + } } diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUAuthUserStore.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUAuthUserStore.cs index 9561161..386675f 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUAuthUserStore.cs +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUAuthUserStore.cs @@ -9,14 +9,9 @@ namespace CodeBeam.UltimateAuth.Core.Abstractions /// public interface IUAuthUserStore { - /// - /// Retrieves a user by identifier. Returns null if no such user exists. - /// - /// The identifier of the user. - /// The user instance or null if not found. - Task?> FindByIdAsync(TUserId userId); + Task?> FindByIdAsync(string? tenantId, TUserId userId, CancellationToken token = default); - Task?> FindByUsernameAsync( + Task?> FindByUsernameAsync(string? tenantId, string username, CancellationToken ct = default); diff --git a/src/CodeBeam.UltimateAuth.Core/Contexts/AuthenticatedSessionContext.cs b/src/CodeBeam.UltimateAuth.Core/Contexts/AuthenticatedSessionContext.cs new file mode 100644 index 0000000..6bfc961 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Contexts/AuthenticatedSessionContext.cs @@ -0,0 +1,31 @@ +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Contexts +{ + /// + /// Represents the context in which a session is issued + /// (login, refresh, reauthentication). + /// + public sealed class AuthenticatedSessionContext + { + public string? TenantId { get; init; } + public required TUserId UserId { get; init; } + public DeviceInfo DeviceInfo { get; init; } + public DateTime Now { get; init; } + public ClaimsSnapshot? Claims { get; init; } + public SessionMetadata Metadata { get; init; } + + /// + /// Optional chain identifier. + /// If null, a new chain will be created. + /// If provided, session will be issued under the existing chain. + /// + public ChainId? ChainId { get; init; } + + /// + /// Indicates that authentication has already been completed. + /// This context MUST NOT be constructed from raw credentials. + /// + public bool IsAuthenticated { get; init; } = true; + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/IssuedAccessToken.cs b/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/AccessToken.cs similarity index 91% rename from src/CodeBeam.UltimateAuth.Core/Contexts/Issued/IssuedAccessToken.cs rename to src/CodeBeam.UltimateAuth.Core/Contexts/Issued/AccessToken.cs index 88aaaec..26da31f 100644 --- a/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/IssuedAccessToken.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/AccessToken.cs @@ -3,7 +3,7 @@ /// /// Represents an issued access token (JWT or opaque). /// - public sealed class IssuedAccessToken + public sealed class AccessToken { /// /// The actual token value sent to the client. @@ -26,5 +26,7 @@ public sealed class IssuedAccessToken /// Optional session id this token is bound to (Hybrid / SemiHybrid). /// public string? SessionId { get; init; } + + public string? Scope { get; init; } } } diff --git a/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/IssuedRefreshToken.cs b/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/RefreshToken.cs similarity index 93% rename from src/CodeBeam.UltimateAuth.Core/Contexts/Issued/IssuedRefreshToken.cs rename to src/CodeBeam.UltimateAuth.Core/Contexts/Issued/RefreshToken.cs index 1f64804..943e424 100644 --- a/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/IssuedRefreshToken.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/RefreshToken.cs @@ -4,7 +4,7 @@ /// Represents an issued refresh token. /// Always opaque and hashed at rest. /// - public sealed class IssuedRefreshToken + public sealed class RefreshToken { /// /// Plain refresh token value (returned to client once). diff --git a/src/CodeBeam.UltimateAuth.Core/Contexts/SessionIssueContext.cs b/src/CodeBeam.UltimateAuth.Core/Contexts/SessionIssueContext.cs deleted file mode 100644 index d75473c..0000000 --- a/src/CodeBeam.UltimateAuth.Core/Contexts/SessionIssueContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using CodeBeam.UltimateAuth.Core.Domain; - -namespace CodeBeam.UltimateAuth.Core.Contexts -{ - /// - /// Represents the context in which a session is issued - /// (login, refresh, reauthentication). - /// - public sealed class SessionIssueContext - { - public required TUserId UserId { get; init; } - public string? TenantId { get; init; } - - public required long SecurityVersion { get; init; } - - public DeviceInfo Device { get; init; } - public IReadOnlyDictionary? ClaimsSnapshot { get; init; } - - public DateTime Now { get; init; } = DateTime.UtcNow; - - public ChainId? ChainId { get; init; } - } -} diff --git a/src/CodeBeam.UltimateAuth.Core/Contexts/SessionRotationContext.cs b/src/CodeBeam.UltimateAuth.Core/Contexts/SessionRotationContext.cs new file mode 100644 index 0000000..775ee63 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Contexts/SessionRotationContext.cs @@ -0,0 +1,15 @@ +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Contexts +{ + public sealed record SessionRotationContext + { + public string? TenantId { get; init; } + public AuthSessionId CurrentSessionId { get; init; } + public TUserId UserId { get; init; } + public DateTime Now { get; init; } + public DeviceInfo Device { get; init; } + public ClaimsSnapshot Claims { get; init; } + public SessionMetadata Metadata { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Contexts/SessionValidationContext.cs b/src/CodeBeam.UltimateAuth.Core/Contexts/SessionValidationContext.cs new file mode 100644 index 0000000..33d6b66 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Contexts/SessionValidationContext.cs @@ -0,0 +1,12 @@ +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Contexts +{ + public sealed record SessionValidationContext + { + public string? TenantId { get; init; } + public AuthSessionId SessionId { get; init; } + public DateTime Now { get; init; } + public DeviceInfo Device { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Contexts/TokenIssueContext.cs b/src/CodeBeam.UltimateAuth.Core/Contexts/TokenIssueContext.cs deleted file mode 100644 index 782d6a4..0000000 --- a/src/CodeBeam.UltimateAuth.Core/Contexts/TokenIssueContext.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Security.Claims; - -namespace CodeBeam.UltimateAuth.Core.Contexts -{ - public sealed class TokenIssueContext - { - public required string UserId { get; init; } - public required string TenantId { get; init; } - - public IReadOnlyCollection Claims { get; init; } = Array.Empty(); - - public string? SessionId { get; init; } - - public DateTimeOffset IssuedAt { get; init; } = DateTimeOffset.UtcNow; - } -} diff --git a/src/CodeBeam.UltimateAuth.Core/Domain/Session/ClaimsSnapshot.cs b/src/CodeBeam.UltimateAuth.Core/Domain/Session/ClaimsSnapshot.cs new file mode 100644 index 0000000..9cf5ad5 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Domain/Session/ClaimsSnapshot.cs @@ -0,0 +1,69 @@ +namespace CodeBeam.UltimateAuth.Core.Domain +{ + public sealed class ClaimsSnapshot + { + private readonly IReadOnlyDictionary _claims; + + public ClaimsSnapshot(IReadOnlyDictionary claims) + { + _claims = new Dictionary(claims); + } + + public IReadOnlyDictionary AsDictionary() => _claims; + + public bool TryGet(string type, out string value) => _claims.TryGetValue(type, out value); + + public string? Get(string type) + => _claims.TryGetValue(type, out var value) + ? value + : null; + + public static ClaimsSnapshot Empty { get; } = new ClaimsSnapshot(new Dictionary()); + + public override bool Equals(object? obj) + { + if (obj is not ClaimsSnapshot other) + return false; + + if (_claims.Count != other._claims.Count) + return false; + + foreach (var kv in _claims) + { + if (!other._claims.TryGetValue(kv.Key, out var v)) + return false; + + if (!string.Equals(kv.Value, v, StringComparison.Ordinal)) + return false; + } + + return true; + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + foreach (var kv in _claims.OrderBy(x => x.Key)) + { + hash = hash * 23 + kv.Key.GetHashCode(); + hash = hash * 23 + kv.Value.GetHashCode(); + } + return hash; + } + } + + public static ClaimsSnapshot From(params (string Type, string Value)[] claims) + { + var dict = new Dictionary(StringComparer.Ordinal); + foreach (var (type, value) in claims) + dict[type] = value; + + return new ClaimsSnapshot(dict); + } + + // TODO: Add ToClaimsPrincipal and FromClaimsPrincipal methods + + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Domain/Session/DeviceInfo.cs b/src/CodeBeam.UltimateAuth.Core/Domain/Session/DeviceInfo.cs index ca204c3..c9322b2 100644 --- a/src/CodeBeam.UltimateAuth.Core/Domain/Session/DeviceInfo.cs +++ b/src/CodeBeam.UltimateAuth.Core/Domain/Session/DeviceInfo.cs @@ -1,4 +1,6 @@ -namespace CodeBeam.UltimateAuth.Core.Domain +using Microsoft.Extensions.DependencyInjection; + +namespace CodeBeam.UltimateAuth.Core.Domain { /// /// Represents metadata describing the device or client environment initiating @@ -7,6 +9,11 @@ /// public sealed class DeviceInfo { + /// + /// Gets the unique identifier for the device. + /// + public string DeviceId { get; init; } = default!; + /// /// Gets the high-level platform identifier, such as web, mobile, /// tablet or iot. @@ -55,6 +62,34 @@ public sealed class DeviceInfo /// Gets optional custom metadata supplied by the application. /// Allows additional device attributes not covered by standard fields. /// - public Dictionary? Custom { get; init; } + public Dictionary? Custom { get; init; } + + public static DeviceInfo Unknown { get; } = new() + { + DeviceId = "unknown", + Platform = null, + Browser = null, + IpAddress = null, + UserAgent = null, + IsTrusted = null + }; + + /// + /// Determines whether the current device information matches the specified device information based on device + /// identifiers. + /// + /// The device information to compare with the current instance. Cannot be null. + /// true if the device identifiers are equal; otherwise, false. + public bool Matches(DeviceInfo other) + { + if (other is null) + return false; + + if (DeviceId != other.DeviceId) + return false; + + // TODO: UA / IP drift policy + return true; + } } } diff --git a/src/CodeBeam.UltimateAuth.Core/Domain/Session/ISession.cs b/src/CodeBeam.UltimateAuth.Core/Domain/Session/ISession.cs index 349ae7e..232e13a 100644 --- a/src/CodeBeam.UltimateAuth.Core/Domain/Session/ISession.cs +++ b/src/CodeBeam.UltimateAuth.Core/Domain/Session/ISession.cs @@ -56,6 +56,8 @@ public interface ISession /// DeviceInfo Device { get; } + ClaimsSnapshot Claims { get; } + /// /// Gets session-scoped metadata used for application-specific extensions, /// such as tenant data, app version, locale, or CSRF tokens. @@ -69,5 +71,9 @@ public interface ISession /// Current timestamp used for comparisons. /// The evaluated of this session. SessionState GetState(DateTime now); + + bool ShouldUpdateLastSeen(DateTime now); + ISession Touch(DateTime now); + } } diff --git a/src/CodeBeam.UltimateAuth.Core/Domain/Session/ISessionChain.cs b/src/CodeBeam.UltimateAuth.Core/Domain/Session/ISessionChain.cs index e408b17..358beb8 100644 --- a/src/CodeBeam.UltimateAuth.Core/Domain/Session/ISessionChain.cs +++ b/src/CodeBeam.UltimateAuth.Core/Domain/Session/ISessionChain.cs @@ -35,7 +35,7 @@ public interface ISessionChain /// Useful for offline clients, WASM apps, and environments where /// full user lookup cannot be performed on each request. /// - IReadOnlyDictionary? ClaimsSnapshot { get; } + ClaimsSnapshot ClaimsSnapshot { get; } /// /// Gets the identifier of the currently active authentication session, if one exists. @@ -53,6 +53,10 @@ public interface ISessionChain /// Gets the timestamp when the chain was revoked, if applicable. /// DateTime? RevokedAt { get; } + + ISessionChain AttachSession(AuthSessionId sessionId); + ISessionChain RotateSession(AuthSessionId sessionId); + ISessionChain Revoke(DateTime at); } } diff --git a/src/CodeBeam.UltimateAuth.Core/Domain/Session/SessionMetadata.cs b/src/CodeBeam.UltimateAuth.Core/Domain/Session/SessionMetadata.cs index 20fe7fe..ca81551 100644 --- a/src/CodeBeam.UltimateAuth.Core/Domain/Session/SessionMetadata.cs +++ b/src/CodeBeam.UltimateAuth.Core/Domain/Session/SessionMetadata.cs @@ -27,12 +27,6 @@ public sealed class SessionMetadata /// public string? Locale { get; init; } - /// - /// Gets the tenant identifier attached to this session, if applicable. - /// This value may override or complement root-level multi-tenant resolution. - /// - public string? TenantId { get; init; } - /// /// Gets a Cross-Site Request Forgery token or other session-scoped secret /// used for request integrity validation in web applications. diff --git a/src/CodeBeam.UltimateAuth.Core/Domain/Session/SessionState.cs b/src/CodeBeam.UltimateAuth.Core/Domain/Session/SessionState.cs index 0faaf75..95a6af0 100644 --- a/src/CodeBeam.UltimateAuth.Core/Domain/Session/SessionState.cs +++ b/src/CodeBeam.UltimateAuth.Core/Domain/Session/SessionState.cs @@ -6,39 +6,12 @@ /// public enum SessionState { - /// - /// The session is valid, not expired, not revoked, and its security version - /// matches the user's current security version. - /// - Active = 0, - - /// - /// The session has passed its expiration time and is no longer valid. - /// - Expired = 1, - - /// - /// The session was explicitly revoked by user action or administrative control. - /// - Revoked = 2, - - /// - /// The session's parent chain has been revoked, typically representing a - /// device-level logout or device ban. - /// - ChainRevoked = 3, - - /// - /// The user's entire session root has been revoked. This invalidates all - /// chains and sessions immediately across all devices. - /// - RootRevoked = 4, - - /// - /// The session's stored SecurityVersionAtCreation does not match the user's - /// current security version, indicating a password reset, MFA reset, - /// or other critical security event. - /// - SecurityVersionMismatch = 5 + Active, + Expired, + Revoked, + NotFound, + Invalid, + SecurityMismatch, + DeviceMismatch } } diff --git a/src/CodeBeam.UltimateAuth.Core/Domain/Session/UAuthSession.cs b/src/CodeBeam.UltimateAuth.Core/Domain/Session/UAuthSession.cs index 07dab4e..a56fcd2 100644 --- a/src/CodeBeam.UltimateAuth.Core/Domain/Session/UAuthSession.cs +++ b/src/CodeBeam.UltimateAuth.Core/Domain/Session/UAuthSession.cs @@ -12,20 +12,22 @@ public sealed class UAuthSession : ISession public DateTime? RevokedAt { get; } public long SecurityVersionAtCreation { get; } public DeviceInfo Device { get; } + public ClaimsSnapshot Claims { get; } public SessionMetadata Metadata { get; } private UAuthSession( - AuthSessionId sessionId, - string? tenantId, - TUserId userId, - DateTime createdAt, - DateTime expiresAt, - DateTime? lastSeenAt, - bool isRevoked, - DateTime? revokedAt, - long securityVersionAtCreation, - DeviceInfo device, - SessionMetadata metadata) + AuthSessionId sessionId, + string? tenantId, + TUserId userId, + DateTime createdAt, + DateTime expiresAt, + DateTime? lastSeenAt, + bool isRevoked, + DateTime? revokedAt, + long securityVersionAtCreation, + DeviceInfo device, + ClaimsSnapshot claims, + SessionMetadata metadata) { SessionId = sessionId; TenantId = tenantId; @@ -37,6 +39,7 @@ private UAuthSession( RevokedAt = revokedAt; SecurityVersionAtCreation = securityVersionAtCreation; Device = device; + Claims = claims; Metadata = metadata; } @@ -46,11 +49,11 @@ public static UAuthSession Create( TUserId userId, DateTime now, DateTime expiresAt, - long securityVersion, DeviceInfo device, + ClaimsSnapshot claims, SessionMetadata metadata) { - return new UAuthSession( + return new( sessionId, tenantId, userId, @@ -59,25 +62,59 @@ public static UAuthSession Create( lastSeenAt: now, isRevoked: false, revokedAt: null, - securityVersionAtCreation: securityVersion, + securityVersionAtCreation: 0, device: device, + claims: claims, metadata: metadata ); } - public UAuthSession WithLastSeen(DateTime now) + public UAuthSession WithSecurityVersion(long version) { + if (SecurityVersionAtCreation == version) + return this; + return new UAuthSession( SessionId, TenantId, UserId, CreatedAt, ExpiresAt, - lastSeenAt: now, + LastSeenAt, + IsRevoked, + RevokedAt, + version, + Device, + Claims, + Metadata + ); + } + + public bool ShouldUpdateLastSeen(DateTime now) + { + if (LastSeenAt is null) + return true; + + return (now - LastSeenAt.Value) >= TimeSpan.FromMinutes(1); + } + + public ISession Touch(DateTime now) + { + if (!ShouldUpdateLastSeen(now)) + return this; + + return new UAuthSession( + SessionId, + TenantId, + UserId, + CreatedAt, + ExpiresAt, + now, IsRevoked, RevokedAt, SecurityVersionAtCreation, Device, + Claims, Metadata ); } @@ -86,17 +123,18 @@ public UAuthSession Revoke(DateTime at) { if (IsRevoked) return this; - return new UAuthSession( + return new( SessionId, TenantId, UserId, CreatedAt, ExpiresAt, LastSeenAt, - isRevoked: true, - revokedAt: at, + true, + at, SecurityVersionAtCreation, Device, + Claims, Metadata ); } diff --git a/src/CodeBeam.UltimateAuth.Core/Domain/Session/UAuthSessionChain.cs b/src/CodeBeam.UltimateAuth.Core/Domain/Session/UAuthSessionChain.cs index c775877..1afa3aa 100644 --- a/src/CodeBeam.UltimateAuth.Core/Domain/Session/UAuthSessionChain.cs +++ b/src/CodeBeam.UltimateAuth.Core/Domain/Session/UAuthSessionChain.cs @@ -7,7 +7,7 @@ public sealed class UAuthSessionChain : ISessionChain public TUserId UserId { get; } public int RotationCount { get; } public long SecurityVersionAtCreation { get; } - public IReadOnlyDictionary? ClaimsSnapshot { get; } + public ClaimsSnapshot ClaimsSnapshot { get; } public AuthSessionId? ActiveSessionId { get; } public bool IsRevoked { get; } public DateTime? RevokedAt { get; } @@ -18,7 +18,7 @@ private UAuthSessionChain( TUserId userId, int rotationCount, long securityVersionAtCreation, - IReadOnlyDictionary? claimsSnapshot, + ClaimsSnapshot claimsSnapshot, AuthSessionId? activeSessionId, bool isRevoked, DateTime? revokedAt) @@ -39,7 +39,7 @@ public static UAuthSessionChain Create( string? tenantId, TUserId userId, long securityVersion, - IReadOnlyDictionary? claimsSnapshot = null) + ClaimsSnapshot claimsSnapshot) { return new UAuthSessionChain( chainId, @@ -54,7 +54,25 @@ public static UAuthSessionChain Create( ); } - public UAuthSessionChain ActivateSession(AuthSessionId sessionId) + public ISessionChain AttachSession(AuthSessionId sessionId) + { + if (IsRevoked) + return this; + + return new UAuthSessionChain( + ChainId, + TenantId, + UserId, + RotationCount, // Unchanged on first attach + SecurityVersionAtCreation, + ClaimsSnapshot, + activeSessionId: sessionId, + isRevoked: false, + revokedAt: null + ); + } + + public ISessionChain RotateSession(AuthSessionId sessionId) { if (IsRevoked) return this; @@ -72,7 +90,7 @@ public UAuthSessionChain ActivateSession(AuthSessionId sessionId) ); } - public UAuthSessionChain Revoke(DateTime at) + public ISessionChain Revoke(DateTime at) { if (IsRevoked) return this; diff --git a/src/CodeBeam.UltimateAuth.Core/Enums/LoginContinuationType.cs b/src/CodeBeam.UltimateAuth.Core/Enums/LoginContinuationType.cs new file mode 100644 index 0000000..ae29355 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Enums/LoginContinuationType.cs @@ -0,0 +1,9 @@ +namespace CodeBeam.UltimateAuth.Core +{ + public enum LoginContinuationType + { + Mfa, + Pkce, + External + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Enums/LoginStatus.cs b/src/CodeBeam.UltimateAuth.Core/Enums/LoginStatus.cs new file mode 100644 index 0000000..1eb3f43 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Enums/LoginStatus.cs @@ -0,0 +1,9 @@ +namespace CodeBeam.UltimateAuth.Core +{ + public enum LoginStatus + { + Success, + RequiresContinuation, + Failed + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Errors/Base/UAuthChainException.cs b/src/CodeBeam.UltimateAuth.Core/Errors/Base/UAuthChainException.cs new file mode 100644 index 0000000..2238697 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Errors/Base/UAuthChainException.cs @@ -0,0 +1,17 @@ +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Errors +{ + public abstract class UAuthChainException : UAuthDomainException + { + public ChainId ChainId { get; } + + protected UAuthChainException( + ChainId chainId, + string message) + : base(message) + { + ChainId = chainId; + } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthChainLinkMissingException.cs b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthChainLinkMissingException.cs new file mode 100644 index 0000000..68ce8bd --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthChainLinkMissingException.cs @@ -0,0 +1,14 @@ +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Errors +{ + public sealed class UAuthSessionChainLinkMissingException : UAuthSessionException + { + public UAuthSessionChainLinkMissingException(AuthSessionId sessionId) + : base( + sessionId, + $"Session '{sessionId}' is not associated with any session chain.") + { + } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionChainNotFoundException.cs b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionChainNotFoundException.cs new file mode 100644 index 0000000..758b7ef --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionChainNotFoundException.cs @@ -0,0 +1,12 @@ +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Errors +{ + public sealed class UAuthSessionChainNotFoundException : UAuthChainException + { + public UAuthSessionChainNotFoundException(ChainId chainId) + : base(chainId, $"Session chain '{chainId}' was not found.") + { + } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionChainRevokedException.cs b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionChainRevokedException.cs new file mode 100644 index 0000000..c755b93 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionChainRevokedException.cs @@ -0,0 +1,14 @@ +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Errors +{ + public sealed class UAuthSessionChainRevokedException : UAuthChainException + { + public ChainId ChainId { get; } + + public UAuthSessionChainRevokedException(ChainId chainId) + : base(chainId, $"Session chain '{chainId}' has been revoked.") + { + } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionDeviceMismatchException.cs b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionDeviceMismatchException.cs new file mode 100644 index 0000000..bb8660f --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionDeviceMismatchException.cs @@ -0,0 +1,22 @@ +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Errors +{ + public sealed class UAuthSessionDeviceMismatchException : UAuthSessionException + { + public DeviceInfo Expected { get; } + public DeviceInfo Actual { get; } + + public UAuthSessionDeviceMismatchException( + AuthSessionId sessionId, + DeviceInfo expected, + DeviceInfo actual) + : base( + sessionId, + $"Session '{sessionId}' device mismatch detected.") + { + Expected = expected; + Actual = actual; + } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Errors/UAuthSessionExpiredException.cs b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionExpiredException.cs similarity index 100% rename from src/CodeBeam.UltimateAuth.Core/Errors/UAuthSessionExpiredException.cs rename to src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionExpiredException.cs diff --git a/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionInvalidStateException.cs b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionInvalidStateException.cs new file mode 100644 index 0000000..bd396bd --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionInvalidStateException.cs @@ -0,0 +1,19 @@ +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Errors +{ + public sealed class UAuthSessionInvalidStateException : UAuthSessionException + { + public SessionState State { get; } + + public UAuthSessionInvalidStateException( + AuthSessionId sessionId, + SessionState state) + : base( + sessionId, + $"Session '{sessionId}' is in invalid state '{state}'.") + { + State = state; + } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Errors/UAuthSessionNotActiveException.cs b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionNotActiveException.cs similarity index 100% rename from src/CodeBeam.UltimateAuth.Core/Errors/UAuthSessionNotActiveException.cs rename to src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionNotActiveException.cs diff --git a/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionNotFoundException.cs b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionNotFoundException.cs new file mode 100644 index 0000000..8cc0a59 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionNotFoundException.cs @@ -0,0 +1,13 @@ +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Errors +{ + public sealed class UAuthSessionNotFoundException + : UAuthSessionException + { + public UAuthSessionNotFoundException(AuthSessionId sessionId) + : base(sessionId, $"Session '{sessionId}' was not found.") + { + } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Errors/UAuthSessionRevokedException.cs b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionRevokedException.cs similarity index 100% rename from src/CodeBeam.UltimateAuth.Core/Errors/UAuthSessionRevokedException.cs rename to src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionRevokedException.cs diff --git a/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionRootRevokedException.cs b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionRootRevokedException.cs new file mode 100644 index 0000000..f1c8978 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionRootRevokedException.cs @@ -0,0 +1,13 @@ +namespace CodeBeam.UltimateAuth.Core.Errors +{ + public sealed class UAuthSessionRootRevokedException : Exception + { + public object UserId { get; } + + public UAuthSessionRootRevokedException(object userId) + : base("All sessions for the user have been revoked.") + { + UserId = userId; + } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionSecurityMismatchException.cs b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionSecurityMismatchException.cs new file mode 100644 index 0000000..9ba17f1 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Errors/Session/UAuthSessionSecurityMismatchException.cs @@ -0,0 +1,17 @@ +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Errors; + +public sealed class UAuthSessionSecurityMismatchException : UAuthSessionException +{ + public long CurrentSecurityVersion { get; } + + public UAuthSessionSecurityMismatchException( + AuthSessionId sessionId, + long currentSecurityVersion) + : base( + sessionId, + $"Session '{sessionId}' is invalid due to security version mismatch.") + { + CurrentSecurityVersion = currentSecurityVersion; + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Errors/UAuthSecurityVersionMismatchException.cs b/src/CodeBeam.UltimateAuth.Core/Errors/UAuthSecurityVersionMismatchException.cs deleted file mode 100644 index b312dde..0000000 --- a/src/CodeBeam.UltimateAuth.Core/Errors/UAuthSecurityVersionMismatchException.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace CodeBeam.UltimateAuth.Core.Errors -{ - /// - /// Represents a domain-level authentication failure caused by a mismatch - /// between the session's stored security version and the user's current - /// security version. - /// A mismatch indicates that a critical security event has occurred - /// after the session was created—such as a password reset, MFA reset, - /// account recovery, or other action requiring all prior sessions - /// to be invalidated. - /// - public sealed class UAuthSecurityVersionMismatchException : UAuthDomainException - { - /// - /// Gets the security version captured when the session was created. - /// - public long SessionVersion { get; } - - /// - /// Gets the user's current security version, which has increased - /// since the session was issued. - /// - public long UserVersion { get; } - - /// - /// Initializes a new instance of the class - /// using the session's stored version and the user's current version. - /// - /// The security version value stored in the session. - /// The user's current security version. - public UAuthSecurityVersionMismatchException(long sessionVersion, long userVersion) : base($"Security version mismatch. Session={sessionVersion}, User={userVersion}") - { - SessionVersion = sessionVersion; - UserVersion = userVersion; - } - } -} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/AuthTokens.cs b/src/CodeBeam.UltimateAuth.Core/Models/AuthTokens.cs new file mode 100644 index 0000000..a84cd8f --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/AuthTokens.cs @@ -0,0 +1,19 @@ +using CodeBeam.UltimateAuth.Core.Contexts; + +namespace CodeBeam.UltimateAuth.Core.Models +{ + /// + /// Represents a set of authentication tokens issued as a result of a successful login. + /// This model is intentionally extensible to support additional token types in the future. + /// + public sealed record AuthTokens + { + /// + /// The issued access token. + /// Always present when is returned. + /// + public AccessToken AccessToken { get; init; } = default!; + + public RefreshToken? RefreshToken { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/LoginContinuation.cs b/src/CodeBeam.UltimateAuth.Core/Models/LoginContinuation.cs new file mode 100644 index 0000000..6e61c95 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/LoginContinuation.cs @@ -0,0 +1,20 @@ +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record LoginContinuation + { + /// + /// Gets the type of login continuation required. + /// + public LoginContinuationType Type { get; init; } + + /// + /// Opaque continuation token used to resume the login flow. + /// + public string ContinuationToken { get; init; } = default!; + + /// + /// Optional hint for UX (e.g. "Enter MFA code", "Verify device"). + /// + public string? Hint { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/LoginRequest.cs b/src/CodeBeam.UltimateAuth.Core/Models/LoginRequest.cs index 2e647a9..d757f05 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/LoginRequest.cs +++ b/src/CodeBeam.UltimateAuth.Core/Models/LoginRequest.cs @@ -1,11 +1,23 @@ -namespace CodeBeam.UltimateAuth.Core.Models +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Models { public sealed record LoginRequest { public string? TenantId { get; init; } public string Identifier { get; init; } = default!; // username, email etc. public string Secret { get; init; } = default!; // password - public string? DeviceId { get; init; } - public IReadOnlyDictionary? Metadata { get; init; } + public DateTime? At { get; init; } + public DeviceInfo DeviceInfo { get; init; } + public IReadOnlyDictionary? Metadata { get; init; } + + /// + /// Hint to request access/refresh tokens when the server mode supports it. + /// Server policy may still ignore this. + /// + public bool RequestTokens { get; init; } + + // Optional + public ChainId? ChainId { get; init; } } } diff --git a/src/CodeBeam.UltimateAuth.Core/Models/LoginResult.cs b/src/CodeBeam.UltimateAuth.Core/Models/LoginResult.cs index 0c92e99..0d80de4 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/LoginResult.cs +++ b/src/CodeBeam.UltimateAuth.Core/Models/LoginResult.cs @@ -5,12 +5,34 @@ namespace CodeBeam.UltimateAuth.Core.Models { public sealed record LoginResult { - public bool RequiresMfa { get; init; } - public string? MfaToken { get; init; } - + public LoginStatus Status { get; init; } public AuthSessionId? SessionId { get; init; } - public IssuedAccessToken? AccessToken { get; init; } - public IssuedRefreshToken? RefreshToken { get; init; } - } + public AccessToken? AccessToken { get; init; } + public RefreshToken? RefreshToken { get; init; } + public LoginContinuation? Continuation { get; init; } + + // Helpers + public bool IsSuccess => Status == LoginStatus.Success; + public bool RequiresContinuation => Continuation is not null; + public bool RequiresMfa => Continuation?.Type == LoginContinuationType.Mfa; + public bool RequiresPkce => Continuation?.Type == LoginContinuationType.Pkce; + public static LoginResult Failed() => new() { Status = LoginStatus.Failed }; + + public static LoginResult Success(AuthSessionId sessionId, AuthTokens? tokens = null) + => new() + { + Status = LoginStatus.Success, + SessionId = sessionId, + AccessToken = tokens?.AccessToken, + RefreshToken = tokens?.RefreshToken + }; + + public static LoginResult Continue(LoginContinuation continuation) + => new() + { + Status = LoginStatus.RequiresContinuation, + Continuation = continuation + }; + } } diff --git a/src/CodeBeam.UltimateAuth.Core/Models/LogoutAllRequest.cs b/src/CodeBeam.UltimateAuth.Core/Models/LogoutAllRequest.cs index 69ce8c2..db40511 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/LogoutAllRequest.cs +++ b/src/CodeBeam.UltimateAuth.Core/Models/LogoutAllRequest.cs @@ -5,12 +5,19 @@ namespace CodeBeam.UltimateAuth.Core.Models public sealed class LogoutAllRequest { public string? TenantId { get; init; } + + /// + /// The current session initiating the logout-all operation. + /// Used to resolve the active chain when ExceptCurrent is true. + /// public AuthSessionId? CurrentSessionId { get; init; } /// /// If true, the current session will NOT be revoked. /// public bool ExceptCurrent { get; init; } + + public DateTime? At { get; init; } } } diff --git a/src/CodeBeam.UltimateAuth.Core/Models/LogoutRequest.cs b/src/CodeBeam.UltimateAuth.Core/Models/LogoutRequest.cs index a869335..274d48c 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/LogoutRequest.cs +++ b/src/CodeBeam.UltimateAuth.Core/Models/LogoutRequest.cs @@ -6,5 +6,11 @@ public sealed record LogoutRequest { public string? TenantId { get; init; } public AuthSessionId SessionId { get; init; } + + /// + /// Optional logical timestamp for the logout operation. + /// If not provided, the flow service will use DateTime.UtcNow. + /// + public DateTime? At { get; init; } } } diff --git a/src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshResult.cs b/src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshResult.cs index 4b371cb..9bfcc44 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshResult.cs +++ b/src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshResult.cs @@ -4,7 +4,7 @@ namespace CodeBeam.UltimateAuth.Core.Models { public sealed record SessionRefreshResult { - public IssuedAccessToken AccessToken { get; init; } = default!; - public IssuedRefreshToken? RefreshToken { get; init; } + public AccessToken AccessToken { get; init; } = default!; + public RefreshToken? RefreshToken { get; init; } } } diff --git a/src/CodeBeam.UltimateAuth.Core/Models/SessionValidationResult.cs b/src/CodeBeam.UltimateAuth.Core/Models/SessionValidationResult.cs index 31560b1..7d722d7 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/SessionValidationResult.cs +++ b/src/CodeBeam.UltimateAuth.Core/Models/SessionValidationResult.cs @@ -2,34 +2,43 @@ namespace CodeBeam.UltimateAuth.Core.Models { - /// - /// Represents the outcome of validating a session, including the resolved session, - /// its chain and root structures, and the computed validation state. - /// - /// - /// Session, Chain and Root may be null if validation fails or if the session - /// does not exist. State always indicates the final resolved status. - /// public sealed class SessionValidationResult { - /// - /// The resolved session instance, or null if the session was not found. - /// - public ISession? Session { get; init; } + public SessionState State { get; } + public ISession? Session { get; } + public ISessionChain? Chain { get; } + public ISessionRoot? Root { get; } - /// - /// The session chain that owns the session, or null if unavailable. - /// - public ISessionChain? Chain { get; init; } + private SessionValidationResult( + SessionState state, + ISession? session, + ISessionChain? chain, + ISessionRoot? root) + { + State = state; + Session = session; + Chain = chain; + Root = root; + } - /// - /// The session root associated with the user, or null if unavailable. - /// - public ISessionRoot? Root { get; init; } + public bool IsValid => State == SessionState.Active; - /// - /// The final computed validation state for the session. - /// - public SessionState State { get; init; } + public static SessionValidationResult Active( + ISession session, + ISessionChain chain, + ISessionRoot root) + => new( + SessionState.Active, + session, + chain, + root); + + public static SessionValidationResult Invalid( + SessionState state) + => new( + state, + session: null, + chain: null, + root: null); } } diff --git a/src/CodeBeam.UltimateAuth.Core/Models/TokenIssueContext.cs b/src/CodeBeam.UltimateAuth.Core/Models/TokenIssueContext.cs index ebf2e0b..ec8a3f4 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/TokenIssueContext.cs +++ b/src/CodeBeam.UltimateAuth.Core/Models/TokenIssueContext.cs @@ -1,23 +1,11 @@ using CodeBeam.UltimateAuth.Core.Domain; -using System.Security.Claims; -namespace CodeBeam.UltimateAuth.Core.Models; - -public sealed record TokenIssueContext +namespace CodeBeam.UltimateAuth.Core.Models { - public string? TenantId { get; init; } - - public TUserId UserId { get; init; } = default!; - - public AuthSessionId? SessionId { get; init; } - - /// - /// Claims to embed into the access token (JWT or stored metadata). - /// - public IReadOnlyCollection Claims { get; init; } = Array.Empty(); - - /// - /// Indicates whether a refresh token should be issued. - /// - public bool IssueRefreshToken { get; init; } = true; + public sealed record TokenIssueContext + { + public string? TenantId { get; init; } + public ISession Session { get; init; } = default!; + public DateTime Now { get; init; } + } } diff --git a/src/CodeBeam.UltimateAuth.Core/Models/TokenIssueResult.cs b/src/CodeBeam.UltimateAuth.Core/Models/TokenIssueResult.cs deleted file mode 100644 index 9accd5c..0000000 --- a/src/CodeBeam.UltimateAuth.Core/Models/TokenIssueResult.cs +++ /dev/null @@ -1,14 +0,0 @@ -using CodeBeam.UltimateAuth.Core.Contexts; - -namespace CodeBeam.UltimateAuth.Core.Models -{ - public sealed record TokenIssueResult - { - public IssuedAccessToken AccessToken { get; init; } = default!; - - public IssuedRefreshToken? RefreshToken { get; init; } - - public static TokenIssueResult From(IssuedAccessToken access, IssuedRefreshToken? refresh) - => new() { AccessToken = access, RefreshToken = refresh }; - } -} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/TokenIssuerContext.cs b/src/CodeBeam.UltimateAuth.Core/Models/TokenIssuerContext.cs new file mode 100644 index 0000000..320ef27 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/TokenIssuerContext.cs @@ -0,0 +1,13 @@ +using System.Security.Claims; + +namespace CodeBeam.UltimateAuth.Core.Models +{ + public sealed record TokenIssuerContext + { + public string UserId { get; init; } = default!; + public string? TenantId { get; init; } + public IReadOnlyCollection Claims { get; init; } = Array.Empty(); + public string? SessionId { get; init; } + public DateTime IssuedAt { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/UserAuthenticationResult.cs b/src/CodeBeam.UltimateAuth.Core/Models/UserAuthenticationResult.cs new file mode 100644 index 0000000..77a0315 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Models/UserAuthenticationResult.cs @@ -0,0 +1,24 @@ +namespace CodeBeam.UltimateAuth.Core.Domain +{ + public sealed class UserAuthenticationResult + { + public bool Succeeded { get; init; } + + public TUserId? UserId { get; init; } + + public ClaimsSnapshot? Claims { get; init; } + + public bool RequiresMfa { get; init; } + + public static UserAuthenticationResult Fail() => new() { Succeeded = false }; + + public static UserAuthenticationResult Success(TUserId userId, ClaimsSnapshot claims, bool requiresMfa = false) + => new() + { + Succeeded = true, + UserId = userId, + Claims = claims, + RequiresMfa = requiresMfa + }; + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserService.cs b/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserService.cs index 4998288..41108e1 100644 --- a/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserService.cs +++ b/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserService.cs @@ -21,5 +21,11 @@ Task DeleteAsync( Task ValidateCredentialsAsync( ValidateCredentialsRequest request, CancellationToken cancellationToken = default); + + Task> AuthenticateAsync( + string? tenantId, + string identifier, + string secret, + CancellationToken cancellationToken = default); } } diff --git a/src/CodeBeam.UltimateAuth.Core/Users/UserRecord.cs b/src/CodeBeam.UltimateAuth.Core/Users/UserRecord.cs index b632a09..6789bdb 100644 --- a/src/CodeBeam.UltimateAuth.Core/Users/UserRecord.cs +++ b/src/CodeBeam.UltimateAuth.Core/Users/UserRecord.cs @@ -1,10 +1,15 @@ -namespace CodeBeam.UltimateAuth.Core.Users +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Users { public sealed class UserRecord { public required TUserId Id { get; init; } public required string Username { get; init; } public required string PasswordHash { get; init; } + public ClaimsSnapshot Claims { get; init; } = ClaimsSnapshot.Empty; + public bool RequiresMfa { get; init; } + public bool IsActive { get; init; } = true; public DateTime CreatedAt { get; init; } public bool IsDeleted { get; init; } } diff --git a/src/CodeBeam.UltimateAuth.Server/Abstractions/IDeviceResolver.cs b/src/CodeBeam.UltimateAuth.Server/Abstractions/IDeviceResolver.cs new file mode 100644 index 0000000..adb05d4 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Server/Abstractions/IDeviceResolver.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Http; +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Server.Abstractions +{ + /// + /// Resolves device and client metadata from the current HTTP context. + /// + public interface IDeviceResolver + { + DeviceInfo Resolve(HttpContext context); + } +} diff --git a/src/CodeBeam.UltimateAuth.Server/CodeBeam.UltimateAuth.Server.csproj b/src/CodeBeam.UltimateAuth.Server/CodeBeam.UltimateAuth.Server.csproj index 7d9e6f8..0032611 100644 --- a/src/CodeBeam.UltimateAuth.Server/CodeBeam.UltimateAuth.Server.csproj +++ b/src/CodeBeam.UltimateAuth.Server/CodeBeam.UltimateAuth.Server.csproj @@ -19,8 +19,4 @@ - - - - diff --git a/src/CodeBeam.UltimateAuth.Server/Endpoints/DefaultLoginEndpointHandler.cs b/src/CodeBeam.UltimateAuth.Server/Endpoints/DefaultLoginEndpointHandler.cs index 2b94841..343ece2 100644 --- a/src/CodeBeam.UltimateAuth.Server/Endpoints/DefaultLoginEndpointHandler.cs +++ b/src/CodeBeam.UltimateAuth.Server/Endpoints/DefaultLoginEndpointHandler.cs @@ -1,12 +1,61 @@ -using Microsoft.AspNetCore.Http; +using CodeBeam.UltimateAuth.Core; +using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Models; +using CodeBeam.UltimateAuth.Server.Abstractions; +using CodeBeam.UltimateAuth.Server.Endpoints; +using CodeBeam.UltimateAuth.Server.MultiTenancy; +using Microsoft.AspNetCore.Http; -namespace CodeBeam.UltimateAuth.Server.Endpoints +public sealed class DefaultLoginEndpointHandler : ILoginEndpointHandler { - public class DefaultLoginEndpointHandler : ILoginEndpointHandler + private readonly IUAuthFlowService _flow; + private readonly IDeviceResolver _deviceResolver; + private readonly ITenantResolver _tenantResolver; + private readonly IClock _clock; + + public DefaultLoginEndpointHandler( + IUAuthFlowService flow, + IDeviceResolver deviceResolver, + ITenantResolver tenantResolver, + IClock clock) + { + _flow = flow; + _deviceResolver = deviceResolver; + _tenantResolver = tenantResolver; + _clock = clock; + } + + public async Task LoginAsync(HttpContext ctx) { - public Task LoginAsync(HttpContext ctx) + var request = await ctx.Request.ReadFromJsonAsync(); + if (request is null) + return Results.BadRequest("Invalid login request."); + + var tenantCtx = await _tenantResolver.ResolveAsync(ctx); + + var flowRequest = request with { - return Task.FromResult(Results.StatusCode(StatusCodes.Status501NotImplemented)); - } + TenantId = tenantCtx.TenantId, + At = _clock.UtcNow, + DeviceInfo = _deviceResolver.Resolve(ctx) + }; + + var result = await _flow.LoginAsync(flowRequest, ctx.RequestAborted); + + return result.Status switch + { + LoginStatus.Success => Results.Ok(new + { + sessionId = result.SessionId, + accessToken = result.AccessToken, + refreshToken = result.RefreshToken + }), + + LoginStatus.RequiresContinuation => Results.Accepted(null, result.Continuation), + + LoginStatus.Failed => Results.Unauthorized(), + + _ => Results.StatusCode(500) + }; } } diff --git a/src/CodeBeam.UltimateAuth.Server/Events/.gitkeep b/src/CodeBeam.UltimateAuth.Server/Events/.gitkeep deleted file mode 100644 index 5f28270..0000000 --- a/src/CodeBeam.UltimateAuth.Server/Events/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/CodeBeam.UltimateAuth.Server/Extensions/ClaimsSnapshotExtensions.cs b/src/CodeBeam.UltimateAuth.Server/Extensions/ClaimsSnapshotExtensions.cs new file mode 100644 index 0000000..bfdbee3 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Server/Extensions/ClaimsSnapshotExtensions.cs @@ -0,0 +1,14 @@ +using CodeBeam.UltimateAuth.Core.Domain; +using System.Security.Claims; + +namespace CodeBeam.UltimateAuth.Server.Extensions +{ + public static class ClaimsSnapshotExtensions + { + public static IReadOnlyCollection AsClaims( + this ClaimsSnapshot snapshot) + => snapshot.AsDictionary() + .Select(kv => new Claim(kv.Key, kv.Value)) + .ToArray(); + } +} diff --git a/src/CodeBeam.UltimateAuth.Server/Internal/.gitkeep b/src/CodeBeam.UltimateAuth.Server/Internal/.gitkeep deleted file mode 100644 index 5f28270..0000000 --- a/src/CodeBeam.UltimateAuth.Server/Internal/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthSessionIssuer.cs b/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthSessionIssuer.cs index 3a224fa..c773c10 100644 --- a/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthSessionIssuer.cs +++ b/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthSessionIssuer.cs @@ -23,10 +23,11 @@ public UAuthSessionIssuer(IOpaqueTokenGenerator opaqueGenerator, IOptions> IssueAsync( - SessionIssueContext context, - UAuthSessionChain chain, - CancellationToken cancellationToken = default) + AuthenticatedSessionContext context, + ISessionChain chain, + CancellationToken cancellationToken = default) { if (_options.Mode == UAuthMode.PureJwt) { @@ -43,9 +44,7 @@ public Task> IssueAsync( context.Now.Add(_options.Session.MaxLifetime.Value); if (absoluteExpiry < expiresAt) - { expiresAt = absoluteExpiry; - } } var session = UAuthSession.Create( @@ -54,9 +53,9 @@ public Task> IssueAsync( userId: context.UserId, now: context.Now, expiresAt: expiresAt, - securityVersion: context.SecurityVersion, - device: context.Device, - metadata: SessionMetadata.Empty + claims: context.Claims, + device: context.DeviceInfo, + metadata: context.Metadata ); return Task.FromResult(new IssuedSession diff --git a/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthTokenIssuer.cs b/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthTokenIssuer.cs index d20b2aa..23e57dd 100644 --- a/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthTokenIssuer.cs +++ b/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthTokenIssuer.cs @@ -2,6 +2,7 @@ using CodeBeam.UltimateAuth.Core.Abstractions; using CodeBeam.UltimateAuth.Core.Contexts; using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Models; using CodeBeam.UltimateAuth.Server.Options; using Microsoft.Extensions.Options; using System.Security.Claims; @@ -28,7 +29,7 @@ public UAuthTokenIssuer(IOpaqueTokenGenerator opaqueGenerator, IJwtTokenGenerato _options = options.Value; } - public Task IssueAccessTokenAsync(TokenIssueContext context, CancellationToken cancellationToken = default) + public Task IssueAccessTokenAsync(TokenIssuerContext context, CancellationToken cancellationToken = default) { var now = DateTimeOffset.UtcNow; var expires = now.Add(_options.Tokens.AccessTokenLifetime); @@ -50,10 +51,10 @@ UAuthMode.SemiHybrid or }; } - public Task IssueRefreshTokenAsync(TokenIssueContext context, CancellationToken cancellationToken = default) + public Task IssueRefreshTokenAsync(TokenIssuerContext context, CancellationToken cancellationToken = default) { if (_options.Mode == UAuthMode.PureOpaque) - return Task.FromResult(null); + return Task.FromResult(null); var now = DateTimeOffset.UtcNow; var expires = now.Add(_options.Tokens.RefreshTokenLifetime); @@ -61,7 +62,7 @@ UAuthMode.SemiHybrid or string token = _opaqueGenerator.Generate(); string hash = _tokenHasher.Hash(token); - return Task.FromResult(new IssuedRefreshToken + return Task.FromResult(new RefreshToken { Token = token, TokenHash = hash, @@ -69,11 +70,11 @@ UAuthMode.SemiHybrid or }); } - private IssuedAccessToken IssueOpaqueAccessToken(DateTimeOffset expires, string? sessionId) + private AccessToken IssueOpaqueAccessToken(DateTimeOffset expires, string? sessionId) { string token = _opaqueGenerator.Generate(); - return new IssuedAccessToken + return new AccessToken { Token = token, Type = TokenType.Opaque, @@ -82,7 +83,7 @@ private IssuedAccessToken IssueOpaqueAccessToken(DateTimeOffset expires, string? }; } - private IssuedAccessToken IssueJwtAccessToken(TokenIssueContext context, DateTimeOffset expires) + private AccessToken IssueJwtAccessToken(TokenIssuerContext context, DateTimeOffset expires) { var claims = new List { @@ -114,7 +115,7 @@ private IssuedAccessToken IssueJwtAccessToken(TokenIssueContext context, DateTim string jwt = _jwtGenerator.CreateToken(descriptor); - return new IssuedAccessToken + return new AccessToken { Token = jwt, Type = TokenType.Jwt, diff --git a/src/CodeBeam.UltimateAuth.Server/Services/UAuthFlowService.cs b/src/CodeBeam.UltimateAuth.Server/Services/UAuthFlowService.cs index 2a5c9e9..9035335 100644 --- a/src/CodeBeam.UltimateAuth.Server/Services/UAuthFlowService.cs +++ b/src/CodeBeam.UltimateAuth.Server/Services/UAuthFlowService.cs @@ -1,4 +1,6 @@ using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Contexts; +using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.Models; namespace CodeBeam.UltimateAuth.Server.Services @@ -44,22 +46,89 @@ public Task ExternalLoginAsync(ExternalLoginRequest request, Cancel throw new NotImplementedException(); } - public async Task LoginAsync( - LoginRequest request, - CancellationToken ct = default) + public async Task LoginAsync(LoginRequest request, CancellationToken ct = default) { - // burayı birazdan dolduracağız - throw new NotImplementedException(); + var now = request.At ?? DateTime.UtcNow; + var device = request.DeviceInfo ?? DeviceInfo.Unknown; + + var authResult = await _users.AuthenticateAsync(request.TenantId, request.Identifier, request.Secret, ct); + + if (!authResult.Succeeded) + { + return LoginResult.Failed(); + } + + var sessionResult = await _sessions.IssueSessionAfterAuthenticationAsync(request.TenantId, + new AuthenticatedSessionContext + { + TenantId = request.TenantId, + UserId = authResult.UserId!, + Now = now, + DeviceInfo = device, + Claims = authResult.Claims, + ChainId = request.ChainId + }); + + AuthTokens? tokens = null; + + if (request.RequestTokens) + { + tokens = await _tokens.CreateTokensAsync( + new TokenIssueContext + { + TenantId = request.TenantId, + Session = sessionResult.Session, + Now = now + }, + ct); + } + + return LoginResult.Success(sessionResult.Session.SessionId, tokens); } - public Task LogoutAllAsync(LogoutAllRequest request, CancellationToken ct = default) + public async Task LogoutAsync(LogoutRequest request, CancellationToken ct = default) { - throw new NotImplementedException(); + var at = request.At ?? DateTime.UtcNow; + await _sessions.RevokeSessionAsync(request.TenantId, request.SessionId, at); } - public Task LogoutAsync(LogoutRequest request, CancellationToken ct = default) + public async Task LogoutAllAsync(LogoutAllRequest request, CancellationToken ct = default) { - throw new NotImplementedException(); + var at = request.At ?? DateTime.UtcNow; + + if (request.CurrentSessionId is null) + throw new InvalidOperationException( + "CurrentSessionId must be provided for logout-all operation."); + + var currentSessionId = request.CurrentSessionId.Value; + + var validation = await _sessions.ValidateSessionAsync( + request.TenantId, + currentSessionId, + at); + + if (validation.IsValid || + validation.Session is null) + throw new InvalidOperationException("Current session is not valid."); + + var userId = validation.Session.UserId; + + ChainId? currentChainId = null; + + if (request.ExceptCurrent) + { + if (request.CurrentSessionId is null) + throw new InvalidOperationException("CurrentSessionId must be provided when ExceptCurrent is true."); + + currentChainId = await _sessions.ResolveChainIdAsync( + request.TenantId, + currentSessionId); + + if (currentChainId is null) + throw new InvalidOperationException("Current session chain could not be resolved."); + } + + await _sessions.RevokeAllChainsAsync(request.TenantId, userId, exceptChainId: currentChainId, at); } public Task ReauthenticateAsync(ReauthRequest request, CancellationToken ct = default) diff --git a/src/CodeBeam.UltimateAuth.Server/Services/UAuthSessionService.cs b/src/CodeBeam.UltimateAuth.Server/Services/UAuthSessionService.cs index f94a7a2..708f650 100644 --- a/src/CodeBeam.UltimateAuth.Server/Services/UAuthSessionService.cs +++ b/src/CodeBeam.UltimateAuth.Server/Services/UAuthSessionService.cs @@ -1,4 +1,5 @@ using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Contexts; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.Models; using CodeBeam.UltimateAuth.Server.Sessions; @@ -7,52 +8,120 @@ namespace CodeBeam.UltimateAuth.Server.Services { internal sealed class UAuthSessionService : IUAuthSessionService { - private readonly ISessionOrchestrator _engine; + private readonly ISessionOrchestrator _orchestrator; public UAuthSessionService( - ISessionOrchestrator engine) + ISessionOrchestrator orchestrator) { - _engine = engine; + _orchestrator = orchestrator; } public Task> ValidateSessionAsync( string? tenantId, AuthSessionId sessionId, DateTime now) - => _engine.ValidateSessionAsync(tenantId, sessionId, now); + { + var context = new SessionValidationContext() + { + TenantId = tenantId, + Now = now + }; + + return _orchestrator.ValidateSessionAsync(context); + } public Task>> GetChainsAsync( string? tenantId, TUserId userId) - => _engine.GetChainsAsync(tenantId, userId); + => _orchestrator.GetChainsAsync( + tenantId, + userId); public Task>> GetSessionsAsync( string? tenantId, ChainId chainId) - => _engine.GetSessionsAsync(tenantId, chainId); + => _orchestrator.GetSessionsAsync( + tenantId, + chainId); - public Task?> GetCurrentSessionAsync( + public Task?> GetSessionAsync( string? tenantId, AuthSessionId sessionId) - => _engine.GetSessionAsync(tenantId, sessionId); + => _orchestrator.GetSessionAsync( + tenantId, + sessionId); public Task RevokeSessionAsync( string? tenantId, AuthSessionId sessionId, DateTime at) - => _engine.RevokeSessionAsync(tenantId, sessionId, at); + => _orchestrator.RevokeSessionAsync( + tenantId, + sessionId, + at); + + public Task ResolveChainIdAsync( + string? tenantId, + AuthSessionId sessionId) + => _orchestrator.ResolveChainIdAsync(tenantId, sessionId); + + public Task RevokeAllChainsAsync( + string? tenantId, + TUserId userId, + ChainId? exceptChainId, + DateTime at) + => _orchestrator.RevokeAllChainsAsync( + tenantId, + userId, + exceptChainId, + at); public Task RevokeChainAsync( string? tenantId, ChainId chainId, DateTime at) - => _engine.RevokeChainAsync(tenantId, chainId, at); + => _orchestrator.RevokeChainAsync( + tenantId, + chainId, + at); public Task RevokeRootAsync( string? tenantId, TUserId userId, DateTime at) - => _engine.RevokeRootAsync(tenantId, userId, at); - } + => _orchestrator.RevokeRootAsync( + tenantId, + userId, + at); + public Task?> GetCurrentSessionAsync(string? tenantId, AuthSessionId sessionId) + { + // TODO: Implement this method + throw new NotImplementedException(); + } + + public Task> IssueSessionAfterAuthenticationAsync( + string? tenantId, + AuthenticatedSessionContext context, + CancellationToken cancellationToken = default) + { + if (context.UserId is null) + throw new InvalidOperationException( + "Authenticated session context requires a valid user id."); + + // Authenticated → IssueContext map + var issueContext = new AuthenticatedSessionContext + { + TenantId = tenantId, + UserId = context.UserId, + Now = context.Now, + DeviceInfo = context.DeviceInfo, + Claims = context.Claims, + ChainId = context.ChainId + }; + + return _orchestrator.CreateLoginSessionAsync(issueContext); + } + + } } diff --git a/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenService.cs b/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenService.cs index 0f6348f..42b95d6 100644 --- a/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenService.cs +++ b/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenService.cs @@ -2,6 +2,7 @@ using CodeBeam.UltimateAuth.Core.Abstractions; using CodeBeam.UltimateAuth.Core.Contexts; using CodeBeam.UltimateAuth.Core.Models; +using CodeBeam.UltimateAuth.Server.Extensions; namespace CodeBeam.UltimateAuth.Server.Services { @@ -18,7 +19,7 @@ public UAuthTokenService(ITokenIssuer issuer, ITokenValidator validator, IUserId _userIdConverter = converterResolver.GetConverter(); } - public async Task IssueAsync( + public async Task CreateTokensAsync( TokenIssueContext context, CancellationToken ct = default) { @@ -27,10 +28,14 @@ public async Task IssueAsync( var access = await _issuer.IssueAccessTokenAsync(issuerCtx, ct); var refresh = await _issuer.IssueRefreshTokenAsync(issuerCtx, ct); - return TokenIssueResult.From(access, refresh); + return new AuthTokens + { + AccessToken = access, + RefreshToken = refresh + }; } - public async Task RefreshAsync( + public async Task RefreshAsync( TokenRefreshContext context, CancellationToken ct = default) { @@ -43,14 +48,14 @@ public async Task> ValidateAsync( CancellationToken ct = default) => await _validator.ValidateAsync(token, type, ct); - private TokenIssueContext ToIssuerContext(TokenIssueContext src) + private TokenIssuerContext ToIssuerContext(TokenIssueContext src) { - return new TokenIssueContext + return new TokenIssuerContext { - UserId = _userIdConverter.ToString(src.UserId), + UserId = _userIdConverter.ToString(src.Session.UserId), TenantId = src.TenantId, - SessionId = src.SessionId, - Claims = src.Claims + SessionId = src.Session.SessionId, + Claims = src.Session.Claims.AsClaims() }; } diff --git a/src/CodeBeam.UltimateAuth.Server/Services/UAuthUserService.cs b/src/CodeBeam.UltimateAuth.Server/Services/UAuthUserService.cs index f44bfea..040c99a 100644 --- a/src/CodeBeam.UltimateAuth.Server/Services/UAuthUserService.cs +++ b/src/CodeBeam.UltimateAuth.Server/Services/UAuthUserService.cs @@ -10,15 +10,18 @@ internal sealed class UAuthUserService : IUAuthUserService private readonly IUAuthUserStore _userStore; private readonly IUAuthPasswordHasher _passwordHasher; private readonly IUserIdFactory _userIdFactory; + private readonly IUserAuthenticator _authenticator; public UAuthUserService( IUAuthUserStore userStore, IUAuthPasswordHasher passwordHasher, - IUserIdFactory userIdFactory) + IUserIdFactory userIdFactory, + IUserAuthenticator authenticator) { _userStore = userStore; _passwordHasher = passwordHasher; _userIdFactory = userIdFactory; + _authenticator = authenticator; } public async Task RegisterAsync( @@ -55,7 +58,7 @@ public async Task ValidateCredentialsAsync( ValidateCredentialsRequest request, CancellationToken ct = default) { - var user = await _userStore.FindByUsernameAsync(request.Identifier, ct); + var user = await _userStore.FindByUsernameAsync(request.TenantId, request.Identifier, ct); if (user is null) return false; @@ -64,10 +67,24 @@ public async Task ValidateCredentialsAsync( user.PasswordHash); } - public Task DeleteAsync( + public async Task DeleteAsync( TUserId userId, CancellationToken ct = default) { - return _userStore.DeleteAsync(userId, ct); + await _userStore.DeleteAsync(userId, ct); + } + + public async Task> AuthenticateAsync( + string? tenantId, + string identifier, + string secret, + CancellationToken cancellationToken = default) + { + return await _authenticator.AuthenticateAsync( + tenantId, + identifier, + secret, + cancellationToken); } } + diff --git a/src/CodeBeam.UltimateAuth.Server/Sessions/ISessionOrchestrator.cs b/src/CodeBeam.UltimateAuth.Server/Sessions/ISessionOrchestrator.cs index 358b8fd..7520b67 100644 --- a/src/CodeBeam.UltimateAuth.Server/Sessions/ISessionOrchestrator.cs +++ b/src/CodeBeam.UltimateAuth.Server/Sessions/ISessionOrchestrator.cs @@ -1,34 +1,31 @@ using CodeBeam.UltimateAuth.Core.Contexts; using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Models; namespace CodeBeam.UltimateAuth.Server.Sessions { - /// - /// Orchestrates session, chain, and root lifecycles - /// according to UltimateAuth security rules. - /// - public interface ISessionOrchestrator + internal interface ISessionOrchestrator { - /// - /// Creates a new login session (initial authentication). - /// - Task> CreateLoginSessionAsync( - SessionIssueContext context); - - /// - /// Revokes a single session. - /// - Task RevokeSessionAsync( - string? tenantId, - AuthSessionId sessionId, - DateTime at); - - /// - /// Revokes all sessions of a user (global logout). - /// - Task RevokeAllSessionsAsync( - string? tenantId, - TUserId userId, - DateTime at); + Task> CreateLoginSessionAsync(AuthenticatedSessionContext context); + + Task> RotateSessionAsync(SessionRotationContext context); + + Task> ValidateSessionAsync(SessionValidationContext context); + + Task?> GetSessionAsync(string? tenantId, AuthSessionId sessionId); + + Task>> GetChainsAsync(string? tenantId, TUserId userId); + + Task ResolveChainIdAsync(string? tenantId,AuthSessionId sessionId); + + Task>> GetSessionsAsync(string? tenantId, ChainId chainId); + + Task RevokeSessionAsync(string? tenantId, AuthSessionId sessionId, DateTime at); + + Task RevokeChainAsync(string? tenantId, ChainId chainId, DateTime at); + + Task RevokeAllChainsAsync(string? tenantId, TUserId userId, ChainId? exceptChainId,DateTime at); + + Task RevokeRootAsync(string? tenantId, TUserId userId, DateTime at); } } diff --git a/src/CodeBeam.UltimateAuth.Server/Sessions/UAuthSessionOrchestrator.cs b/src/CodeBeam.UltimateAuth.Server/Sessions/UAuthSessionOrchestrator.cs index d16e03f..d2ed195 100644 --- a/src/CodeBeam.UltimateAuth.Server/Sessions/UAuthSessionOrchestrator.cs +++ b/src/CodeBeam.UltimateAuth.Server/Sessions/UAuthSessionOrchestrator.cs @@ -1,7 +1,8 @@ -using CodeBeam.UltimateAuth.Core; -using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Abstractions; using CodeBeam.UltimateAuth.Core.Contexts; using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Errors; +using CodeBeam.UltimateAuth.Core.Models; using CodeBeam.UltimateAuth.Server.Issuers; using CodeBeam.UltimateAuth.Server.Options; @@ -24,11 +25,10 @@ public UAuthSessionOrchestrator(ISessionStoreFactory factory, UAuthSessionIssuer _serverOptions = serverOptions; } - public async Task> CreateLoginSessionAsync(SessionIssueContext context) + public async Task> CreateLoginSessionAsync(AuthenticatedSessionContext context) { var kernel = _factory.Create(context.TenantId); - // 1️⃣ Load or create root var root = await kernel.GetSessionRootAsync( context.TenantId, context.UserId); @@ -42,173 +42,234 @@ public async Task> CreateLoginSessionAsync(SessionIssueCo } else if (root.IsRevoked) { - throw new InvalidOperationException( - "User session root is revoked."); + throw new UAuthSessionRootRevokedException(context.UserId!); } - // 2️⃣ Load or create chain (interface → concrete) - ISessionChain? loadedChain = null; + ISessionChain chain; if (context.ChainId is not null) { - loadedChain = await kernel.GetChainAsync( + chain = await kernel.GetChainAsync( context.TenantId, - context.ChainId.Value); - } + context.ChainId.Value) + ?? throw new UAuthSessionChainNotFoundException( + context.ChainId.Value); - if (loadedChain is not null && loadedChain.IsRevoked) - { - throw new InvalidOperationException( - "Session chain is revoked."); + if (chain.IsRevoked) + throw new UAuthSessionChainRevokedException( + chain.ChainId); } - - UAuthSessionChain chain; - - if (loadedChain is null) + else { chain = UAuthSessionChain.Create( ChainId.New(), context.TenantId, context.UserId, root.SecurityVersion, - context.ClaimsSnapshot); - } - else if (loadedChain is UAuthSessionChain concreteChain) - { - chain = concreteChain; - } - else - { - throw new InvalidOperationException( - "Unsupported ISessionChain implementation. " + - "UltimateAuth requires SessionChain."); + context.Claims); } - // TODO: Add cancellation token support var issuedSession = await _sessionIssuer.IssueAsync( context, chain); - // 4️⃣ Persist session - await kernel.SaveSessionAsync( - context.TenantId, - issuedSession.Session); + await kernel.ExecuteAsync(async () => + { + await kernel.SaveSessionAsync( + context.TenantId, + issuedSession.Session); - // 5️⃣ Update & persist chain - var updatedChain = chain.ActivateSession( - issuedSession.Session.SessionId); + var updatedChain = chain.AttachSession( + issuedSession.Session.SessionId); - await kernel.SaveChainAsync( - context.TenantId, - updatedChain); + await kernel.SaveChainAsync( + context.TenantId, + updatedChain); - // 6️⃣ Persist root (idempotent) - await kernel.SaveSessionRootAsync( - context.TenantId, - root); + await kernel.SaveSessionRootAsync( + context.TenantId, + root); + }); return issuedSession; } - public async Task> RotateSessionAsync(string? tenantId, AuthSessionId currentSessionId, SessionIssueContext context) + public async Task> RotateSessionAsync(SessionRotationContext context) { - if (_serverOptions.Mode == UAuthMode.PureJwt) - throw new InvalidOperationException( - "Session rotation is not available in PureJwt mode."); - - var kernel = _factory.Create(tenantId); + var kernel = _factory.Create(context.TenantId); - // 1️⃣ Load current session var currentSession = await kernel.GetSessionAsync( - tenantId, - currentSessionId); + context.TenantId, + context.CurrentSessionId); if (currentSession is null) - throw new InvalidOperationException("Session not found."); + throw new UAuthSessionNotFoundException(context.CurrentSessionId); if (currentSession.IsRevoked) - throw new InvalidOperationException("Session is revoked."); + throw new UAuthSessionRevokedException(context.CurrentSessionId); - if (currentSession.GetState(context.Now) != SessionState.Active) - throw new InvalidOperationException("Session is not active."); + var state = currentSession.GetState(context.Now); + if (state != SessionState.Active) + throw new UAuthSessionInvalidStateException( + context.CurrentSessionId, state); - // 2️⃣ Load chain id var chainId = await kernel.GetChainIdBySessionAsync( - tenantId, - currentSessionId); + context.TenantId, + context.CurrentSessionId); if (chainId is null) - throw new InvalidOperationException("Session chain not found."); + throw new UAuthSessionChainLinkMissingException(context.CurrentSessionId); - // 3️⃣ Load chain - var loadedChain = await kernel.GetChainAsync( - tenantId, + var chain = await kernel.GetChainAsync( + context.TenantId, chainId.Value); - if (loadedChain is null || loadedChain.IsRevoked) - throw new InvalidOperationException("Session chain is revoked."); - - if (loadedChain is not UAuthSessionChain chain) - throw new InvalidOperationException( - "Unsupported ISessionChain implementation."); + if (chain is null || chain.IsRevoked) + throw new UAuthSessionChainRevokedException(chainId.Value); - // 4️⃣ Load root var root = await kernel.GetSessionRootAsync( - tenantId, - context.UserId); + context.TenantId, + currentSession.UserId); if (root is null || root.IsRevoked) - throw new InvalidOperationException("Session root is revoked."); + throw new UAuthSessionRootRevokedException( + currentSession.UserId!); - // 5️⃣ Security version check if (currentSession.SecurityVersionAtCreation != root.SecurityVersion) - throw new InvalidOperationException( - "Session security version mismatch."); + throw new UAuthSessionSecurityMismatchException( + context.CurrentSessionId, + root.SecurityVersion); + + var issueContext = new AuthenticatedSessionContext + { + TenantId = root.TenantId, + UserId = currentSession.UserId, + Now = context.Now, + DeviceInfo = context.Device, + Claims = context.Claims + }; - // TODO: Add cancellation token support var issuedSession = await _sessionIssuer.IssueAsync( - context, + issueContext, chain); - // 7️⃣ Persist new session - await kernel.SaveSessionAsync( - tenantId, - issuedSession.Session); + await kernel.ExecuteAsync(async () => + { + await kernel.RevokeSessionAsync( + context.TenantId, + context.CurrentSessionId, + context.Now); - // 8️⃣ Revoke old session - await kernel.RevokeSessionAsync( - tenantId, - currentSessionId, - context.Now); + await kernel.SaveSessionAsync( + context.TenantId, + issuedSession.Session); - // 9️⃣ Activate new session in chain - var updatedChain = chain.ActivateSession( - issuedSession.Session.SessionId); + var rotatedChain = chain.RotateSession( + issuedSession.Session.SessionId); - await kernel.SaveChainAsync( - tenantId, - updatedChain); + await kernel.SaveChainAsync( + context.TenantId, + rotatedChain); - // 🔟 Root persistence (idempotent) - await kernel.SaveSessionRootAsync( - tenantId, - root); + await kernel.SaveSessionRootAsync( + context.TenantId, + root); + }); return issuedSession; } - public Task?> GetSessionAsync( - string? tenantId, - AuthSessionId sessionId) + public async Task> ValidateSessionAsync( + SessionValidationContext context) + { + var kernel = _factory.Create(context.TenantId); + + // 1️⃣ Load session + var session = await kernel.GetSessionAsync( + context.TenantId, + context.SessionId); + + if (session is null) + return SessionValidationResult.Invalid(SessionState.NotFound); + + var state = session.GetState(context.Now); + + if (state != SessionState.Active) + return SessionValidationResult.Invalid(state); + + // 2️⃣ Resolve chain + var chainId = await kernel.GetChainIdBySessionAsync( + context.TenantId, + context.SessionId); + + if (chainId is null) + return SessionValidationResult.Invalid(SessionState.Invalid); + + var chain = await kernel.GetChainAsync( + context.TenantId, + chainId.Value); + + if (chain is null || chain.IsRevoked) + return SessionValidationResult.Invalid(SessionState.Revoked); + + // 3️⃣ Resolve root + var root = await kernel.GetSessionRootAsync( + context.TenantId, + session.UserId); + + if (root is null || root.IsRevoked) + return SessionValidationResult.Invalid(SessionState.Revoked); + + // 4️⃣ Security version check + if (session.SecurityVersionAtCreation != root.SecurityVersion) + return SessionValidationResult.Invalid(SessionState.SecurityMismatch); + + // 5️⃣ Device check + if (!session.Device.Matches(context.Device)) + return SessionValidationResult.Invalid(SessionState.DeviceMismatch); + + // 6️⃣ Touch session (best-effort) + if (session.ShouldUpdateLastSeen(context.Now)) + { + var updated = session.Touch(context.Now); + await kernel.SaveSessionAsync(context.TenantId, updated); + session = updated; + } + + // 7️⃣ Success + return SessionValidationResult.Active( + session, + chain, + root); + } + + public Task?> GetSessionAsync(string? tenantId, AuthSessionId sessionId) { var kernel = _factory.Create(tenantId); return kernel.GetSessionAsync(tenantId, sessionId); } - public async Task RevokeSessionAsync( + public Task>> GetSessionsAsync(string? tenantId, ChainId chainId) + { + var kernel = _factory.Create(tenantId); + return kernel.GetSessionsByChainAsync(tenantId, chainId); + } + + public Task>> GetChainsAsync(string? tenantId, TUserId userId) + { + var kernel = _factory.Create(tenantId); + return kernel.GetChainsByUserAsync(tenantId, userId); + } + + public async Task ResolveChainIdAsync( string? tenantId, - AuthSessionId sessionId, - DateTime at) + AuthSessionId sessionId) + { + var kernel = _factory.Create(tenantId); + return await kernel.GetChainIdBySessionAsync(tenantId, sessionId); + } + + public async Task RevokeSessionAsync(string? tenantId, AuthSessionId sessionId, DateTime at) { var kernel = _factory.Create(tenantId); await kernel.RevokeSessionAsync(tenantId, sessionId, at); @@ -223,13 +284,55 @@ public async Task RevokeAllSessionsAsync( await kernel.RevokeSessionRootAsync(tenantId, userId, at); } - public async Task RevokeChainAsync( + public async Task RevokeChainAsync(string? tenantId, ChainId chainId, DateTime at) + { + var kernel = _factory.Create(tenantId); + await kernel.RevokeChainAsync(tenantId, chainId, at); + } + + public async Task RevokeAllChainsAsync( string? tenantId, - ChainId chainId, + TUserId userId, + ChainId? exceptChainId, DateTime at) { var kernel = _factory.Create(tenantId); - await kernel.RevokeChainAsync(tenantId, chainId, at); + + var chains = await kernel.GetChainsByUserAsync(tenantId, userId); + + await kernel.ExecuteAsync(async () => + { + foreach (var chain in chains) + { + if (exceptChainId.HasValue && + chain.ChainId.Equals(exceptChainId.Value)) + { + continue; + } + + if (!chain.IsRevoked) + { + await kernel.RevokeChainAsync( + tenantId, + chain.ChainId, + at); + } + } + }); + } + + public async Task RevokeRootAsync(string? tenantId, TUserId userId, DateTime at) + { + var kernel = _factory.Create(tenantId); + + await kernel.ExecuteAsync(async () => + { + await kernel.RevokeSessionRootAsync( + tenantId, + userId, + at); + }); } + } } diff --git a/src/CodeBeam.UltimateAuth.Server/Users/AspNetIdentityUserStore.cs b/src/CodeBeam.UltimateAuth.Server/Users/AspNetIdentityUserStore.cs new file mode 100644 index 0000000..071142d --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Server/Users/AspNetIdentityUserStore.cs @@ -0,0 +1,38 @@ +using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Domain; +using Microsoft.AspNetCore.Identity; + +namespace CodeBeam.UltimateAuth.Server.Users +{ + public sealed class AspNetIdentityUserStore // : IUAuthUserStore + { + //private readonly UserManager _users; + + //public AspNetIdentityUserStore(UserManager users) + //{ + // _users = users; + //} + + //public async Task?> FindByUsernameAsync( + // string? tenantId, + // string username, + // CancellationToken cancellationToken = default) + //{ + // var user = await _users.FindByNameAsync(username); + // if (user is null) + // return null; + + // var claims = await _users.GetClaimsAsync(user); + + // return new UAuthUserRecord + // { + // UserId = user.Id, + // Username = user.UserName!, + // PasswordHash = user.PasswordHash!, + // Claims = ClaimsSnapshot.From( + // claims.Select(c => (c.Type, c.Value)).ToArray()) + // }; + //} + } + +} diff --git a/src/CodeBeam.UltimateAuth.Server/Users/DefaultUserAuthenticator.cs b/src/CodeBeam.UltimateAuth.Server/Users/DefaultUserAuthenticator.cs new file mode 100644 index 0000000..63051a4 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Server/Users/DefaultUserAuthenticator.cs @@ -0,0 +1,40 @@ +using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Server.Users +{ + public sealed class DefaultUserAuthenticator : IUserAuthenticator + { + private readonly IUAuthUserStore _userStore; + private readonly IUAuthPasswordHasher _passwordHasher; + + public DefaultUserAuthenticator(IUAuthUserStore userStore, IUAuthPasswordHasher passwordHasher) + { + _userStore = userStore; + _passwordHasher = passwordHasher; + } + + public async Task> AuthenticateAsync( + string? tenantId, + string username, + string secret, + CancellationToken cancellationToken = default) + { + var user = await _userStore.FindByUsernameAsync( + tenantId, + username, + cancellationToken); + + if (user is null) + return UserAuthenticationResult.Fail(); + + if (!user.IsActive) + return UserAuthenticationResult.Fail(); + + if (!_passwordHasher.Verify(secret, user.PasswordHash)) + return UserAuthenticationResult.Fail(); + + return UserAuthenticationResult.Success(user.Id, user.Claims, user.RequiresMfa); + } + } +} diff --git a/src/CodeBeam.UltimateAuth.Server/Users/UAuthUserAccessor.cs b/src/CodeBeam.UltimateAuth.Server/Users/UAuthUserAccessor.cs index a292f37..9aa2c3c 100644 --- a/src/CodeBeam.UltimateAuth.Server/Users/UAuthUserAccessor.cs +++ b/src/CodeBeam.UltimateAuth.Server/Users/UAuthUserAccessor.cs @@ -43,7 +43,7 @@ public async Task ResolveAsync(HttpContext context) } // 👤 Load user - var user = await _userStore.FindByIdAsync(session.UserId); + var user = await _userStore.FindByIdAsync(sessionCtx.TenantId, session.UserId); if (user is null) { diff --git a/src/CodeBeam.UltimateAuth.Server/Utility/SystemClock.cs b/src/CodeBeam.UltimateAuth.Server/Utility/SystemClock.cs new file mode 100644 index 0000000..8dbf12c --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Server/Utility/SystemClock.cs @@ -0,0 +1,9 @@ +using CodeBeam.UltimateAuth.Core.Abstractions; + +namespace CodeBeam.UltimateAuth.Server.Utility +{ + public sealed class SystemClock : IClock + { + public DateTime UtcNow => DateTime.UtcNow; + } +} diff --git a/src/CodeBeam.UltimateAuth.Server/Utility/UAuthDeviceResolver.cs b/src/CodeBeam.UltimateAuth.Server/Utility/UAuthDeviceResolver.cs new file mode 100644 index 0000000..153a338 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Server/Utility/UAuthDeviceResolver.cs @@ -0,0 +1,49 @@ +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Server.Abstractions; +using Microsoft.AspNetCore.Http; + +namespace CodeBeam.UltimateAuth.Server.Utility +{ + public sealed class DefaultDeviceResolver : IDeviceResolver + { + public DeviceInfo Resolve(HttpContext context) + { + var request = context.Request; + + return new DeviceInfo + { + DeviceId = ResolveDeviceId(context), + Platform = ResolvePlatform(request), + OperatingSystem = null, // optional UA parsing later + Browser = request.Headers.UserAgent.ToString(), + IpAddress = context.Connection.RemoteIpAddress?.ToString(), + UserAgent = request.Headers.UserAgent.ToString(), + IsTrusted = null + }; + } + + private static string ResolveDeviceId(HttpContext context) + { + if (context.Request.Headers.TryGetValue("X-Device-Id", out var header)) + return header.ToString(); + + if (context.Request.Cookies.TryGetValue("ua_device", out var cookie)) + return cookie; + + return "unknown"; + } + + private static string? ResolvePlatform(HttpRequest request) + { + var ua = request.Headers.UserAgent.ToString().ToLowerInvariant(); + + if (ua.Contains("android")) return "android"; + if (ua.Contains("iphone") || ua.Contains("ipad")) return "ios"; + if (ua.Contains("windows")) return "windows"; + if (ua.Contains("mac os")) return "macos"; + if (ua.Contains("linux")) return "linux"; + + return "web"; + } + } +} From 643be92b4c9954b25b41e791449f4df48a14cf0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Tue, 16 Dec 2025 20:22:10 +0300 Subject: [PATCH 4/4] Folder Classification --- .../Abstractions/.gitkeep | 1 - .../IUAuthUserManagementService.cs | 2 +- .../Abstractions/IUAuthUserProfileService.cs | 2 +- .../Users/Models/AdminUserFilter.cs | 2 +- .../Users/Models/ChangePasswordRequest.cs | 2 +- .../Users/Models/ConfigureMfaRequest.cs | 2 +- .../Users/Models/ResetPasswordRequest.cs | 2 +- .../Users/Models/UpdateProfileRequest.cs | 2 +- .../Users/Models/UserDto.cs | 2 +- .../Users/Models/UserProfileDto.cs | 2 +- .../Abstractions/IUserAuthenticator.cs | 13 ---- .../{ => Infrastructure}/IClock.cs | 0 .../{ => Infrastructure}/ITokenHasher.cs | 0 .../IUAuthPasswordHasher.cs | 0 .../Abstractions/Issuers/ISessionIssuer.cs | 2 +- .../Abstractions/Issuers/ITokenIssuer.cs | 15 ---- .../Principals/IUserAuthenticator.cs | 9 +++ .../{ => Principals}/IUserIdConverter.cs | 0 .../IUserIdConverterResolver.cs | 0 .../{ => Principals}/IUserIdFactory.cs | 0 .../Services/IUAuthFlowService.cs | 70 ++++++------------- .../Services/IUAuthSessionService.cs | 68 +++++------------- .../Services/IUAuthTokenService.cs | 15 ++-- .../Services/IUAuthUserService.cs | 20 ++++++ .../Abstractions/Stores/IOpaqueTokenStore.cs | 2 +- .../Abstractions/Stores/ISessionStore.cs | 2 +- .../Abstractions/Stores/IUAuthUserStore.cs | 3 +- .../{ => Validators}/ITokenValidator.cs | 2 +- .../CodeBeam.UltimateAuth.Core.csproj | 6 +- .../Login}/ExternalLoginRequest.cs | 2 +- .../Login}/LoginContinuation.cs | 2 +- .../Login}/LoginContinuationType.cs | 2 +- .../Login}/LoginRequest.cs | 2 +- .../Login}/LoginResult.cs | 4 +- .../{Enums => Contracts/Login}/LoginStatus.cs | 2 +- .../Login}/ReauthRequest.cs | 2 +- .../Contracts/Login/ReauthResult.cs | 7 ++ .../Logout}/LogoutAllRequest.cs | 2 +- .../Logout}/LogoutRequest.cs | 2 +- .../Mfa}/BeginMfaRequest.cs | 2 +- .../Mfa}/CompleteMfaRequest.cs | 2 +- .../Mfa}/MfaChallengeResult.cs | 2 +- .../Pkce}/PkceChallengeResult.cs | 2 +- .../Pkce}/PkceConsumeRequest.cs | 2 +- .../Pkce}/PkceCreateRequest.cs | 2 +- .../Pkce}/PkceVerificationResult.cs | 2 +- .../Pkce}/PkceVerifyRequest.cs | 2 +- .../Session}/AuthenticatedSessionContext.cs | 2 +- .../Session}/IssuedSession.cs | 2 +- .../Contracts/Session}/SessionContext.cs | 2 +- .../Session}/SessionRefreshRequest.cs | 2 +- .../Session}/SessionRefreshResult.cs | 4 +- .../Session}/SessionResult.cs | 2 +- .../Session}/SessionRotationContext.cs | 2 +- .../Session}/SessionStoreContext.cs | 2 +- .../Session}/SessionValidationContext.cs | 2 +- .../Session}/SessionValidationResult.cs | 2 +- .../Issued => Contracts/Token}/AccessToken.cs | 2 +- .../{Models => Contracts/Token}/AuthTokens.cs | 4 +- .../Token}/OpaqueTokenRecord.cs | 2 +- .../Token}/RefreshToken.cs | 2 +- .../Token}/TokenInvalidReason.cs | 2 +- .../Token}/TokenIssueContext.cs | 2 +- .../Token}/TokenRefreshContext.cs | 2 +- .../{Enums => Contracts/Token}/TokenType.cs | 2 +- .../Token}/TokenValidationResult.cs | 2 +- .../User}/RegisterUserRequest.cs | 2 +- .../User}/UserAuthenticationResult.cs | 4 +- .../User}/UserContext.cs | 4 +- .../User}/ValidateCredentialsRequest.cs | 2 +- .../Domain/Session/DeviceInfo.cs | 4 +- .../{Abstractions => Domain/User}/IUser.cs | 2 +- ...UltimateAuthServiceCollectionExtensions.cs | 3 +- .../Base64Url.cs | 2 +- .../GuidUserIdFactory.cs | 2 +- .../RandomIdGenerator.cs | 2 +- .../StringUserIdFactory.cs | 2 +- .../UAuthUserIdConverter.cs | 2 +- .../UAuthUserIdConverterResolver.cs | 2 +- .../UserIdFactory.cs | 2 +- .../{Users => Infrastructure}/UserRecord.cs | 2 +- .../Models/ReauthResult.cs | 13 ---- .../{Enums => Options}/UAuthMode.cs | 0 .../Users/Abstractions/IUAuthUserService.cs | 31 -------- .../CodeBeam.UltimateAuth.Server.csproj | 4 ++ .../Endpoints/DefaultLoginEndpointHandler.cs | 5 +- .../HttpContextSessionExtensions.cs | 11 ++- .../BearerSessionIdResolver.cs | 2 +- .../CompositeSessionIdResolver.cs | 2 +- .../CookieSessionIdResolver.cs | 2 +- .../DefaultUserAuthenticator.cs | 4 +- .../HeaderSessionIdResolver.cs | 2 +- .../ISessionIdResolver.cs | 2 +- .../ISessionOrchestrator.cs | 5 +- .../Infrastructure/ITokenIssuer.cs | 14 ++++ .../IUserAccessor.cs | 2 +- .../QuerySessionIdResolver.cs | 2 +- .../SystemClock.cs | 2 +- .../Infrastructure/TokenIssuanceContext.cs} | 4 +- .../UAuthDeviceResolver.cs | 2 +- .../UAuthSessionIdResolver.cs | 2 +- .../UAuthSessionOrchestrator.cs | 5 +- .../UAuthUserAccessor.cs | 4 +- .../{Users => Infrastructure}/UAuthUserId.cs | 2 +- .../Issuers/UAuthSessionIssuer.cs | 2 +- .../Issuers/UAuthTokenIssuer.cs | 10 +-- .../SessionResolutionMiddleware.cs | 5 +- .../Middlewares/UserMiddleware.cs | 2 +- .../Services/UAuthFlowService.cs | 3 +- .../Services/UAuthSessionService.cs | 8 +-- .../Services/UAuthTokenService.cs | 11 ++- .../Services/UAuthTokenValidator.cs | 2 +- .../Services/UAuthUserService.cs | 5 +- .../AspNetIdentityUserStore.cs | 2 +- 114 files changed, 223 insertions(+), 317 deletions(-) delete mode 100644 src/CodeBeam.UltimateAuth.AspNetCore/Abstractions/.gitkeep rename src/{CodeBeam.UltimateAuth.Core/Users => CodeBeam.UltimateAuth.AspNetCore}/Abstractions/IUAuthUserManagementService.cs (93%) rename src/{CodeBeam.UltimateAuth.Core/Users => CodeBeam.UltimateAuth.AspNetCore}/Abstractions/IUAuthUserProfileService.cs (91%) rename src/{CodeBeam.UltimateAuth.Core => CodeBeam.UltimateAuth.AspNetCore}/Users/Models/AdminUserFilter.cs (79%) rename src/{CodeBeam.UltimateAuth.Core => CodeBeam.UltimateAuth.AspNetCore}/Users/Models/ChangePasswordRequest.cs (87%) rename src/{CodeBeam.UltimateAuth.Core => CodeBeam.UltimateAuth.AspNetCore}/Users/Models/ConfigureMfaRequest.cs (84%) rename src/{CodeBeam.UltimateAuth.Core => CodeBeam.UltimateAuth.AspNetCore}/Users/Models/ResetPasswordRequest.cs (85%) rename src/{CodeBeam.UltimateAuth.Core => CodeBeam.UltimateAuth.AspNetCore}/Users/Models/UpdateProfileRequest.cs (76%) rename src/{CodeBeam.UltimateAuth.Core => CodeBeam.UltimateAuth.AspNetCore}/Users/Models/UserDto.cs (89%) rename src/{CodeBeam.UltimateAuth.Core => CodeBeam.UltimateAuth.AspNetCore}/Users/Models/UserProfileDto.cs (86%) delete mode 100644 src/CodeBeam.UltimateAuth.Core/Abstractions/IUserAuthenticator.cs rename src/CodeBeam.UltimateAuth.Core/Abstractions/{ => Infrastructure}/IClock.cs (100%) rename src/CodeBeam.UltimateAuth.Core/Abstractions/{ => Infrastructure}/ITokenHasher.cs (100%) rename src/CodeBeam.UltimateAuth.Core/Abstractions/{ => Infrastructure}/IUAuthPasswordHasher.cs (100%) delete mode 100644 src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenIssuer.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Abstractions/Principals/IUserAuthenticator.cs rename src/CodeBeam.UltimateAuth.Core/Abstractions/{ => Principals}/IUserIdConverter.cs (100%) rename src/CodeBeam.UltimateAuth.Core/Abstractions/{ => Principals}/IUserIdConverterResolver.cs (100%) rename src/CodeBeam.UltimateAuth.Core/Abstractions/{ => Principals}/IUserIdFactory.cs (100%) create mode 100644 src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthUserService.cs rename src/CodeBeam.UltimateAuth.Core/Abstractions/{ => Validators}/ITokenValidator.cs (89%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Login}/ExternalLoginRequest.cs (84%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Login}/LoginContinuation.cs (91%) rename src/CodeBeam.UltimateAuth.Core/{Enums => Contracts/Login}/LoginContinuationType.cs (66%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Login}/LoginRequest.cs (94%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Login}/LoginResult.cs (93%) rename src/CodeBeam.UltimateAuth.Core/{Enums => Contracts/Login}/LoginStatus.cs (67%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Login}/ReauthRequest.cs (84%) create mode 100644 src/CodeBeam.UltimateAuth.Core/Contracts/Login/ReauthResult.cs rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Logout}/LogoutAllRequest.cs (92%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Logout}/LogoutRequest.cs (90%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Mfa}/BeginMfaRequest.cs (69%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Mfa}/CompleteMfaRequest.cs (77%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Mfa}/MfaChallengeResult.cs (80%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Pkce}/PkceChallengeResult.cs (77%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Pkce}/PkceConsumeRequest.cs (70%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Pkce}/PkceCreateRequest.cs (70%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Pkce}/PkceVerificationResult.cs (68%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Pkce}/PkceVerifyRequest.cs (77%) rename src/CodeBeam.UltimateAuth.Core/{Contexts => Contracts/Session}/AuthenticatedSessionContext.cs (95%) rename src/CodeBeam.UltimateAuth.Core/{Contexts/Issued => Contracts/Session}/IssuedSession.cs (93%) rename src/{CodeBeam.UltimateAuth.Server/Sessions => CodeBeam.UltimateAuth.Core/Contracts/Session}/SessionContext.cs (94%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Session}/SessionRefreshRequest.cs (77%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Session}/SessionRefreshResult.cs (66%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Session}/SessionResult.cs (97%) rename src/CodeBeam.UltimateAuth.Core/{Contexts => Contracts/Session}/SessionRotationContext.cs (90%) rename src/CodeBeam.UltimateAuth.Core/{Contexts => Contracts/Session}/SessionStoreContext.cs (96%) rename src/CodeBeam.UltimateAuth.Core/{Contexts => Contracts/Session}/SessionValidationContext.cs (86%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Session}/SessionValidationResult.cs (96%) rename src/CodeBeam.UltimateAuth.Core/{Contexts/Issued => Contracts/Token}/AccessToken.cs (94%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Token}/AuthTokens.cs (85%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Token}/OpaqueTokenRecord.cs (92%) rename src/CodeBeam.UltimateAuth.Core/{Contexts/Issued => Contracts/Token}/RefreshToken.cs (92%) rename src/CodeBeam.UltimateAuth.Core/{Enums => Contracts/Token}/TokenInvalidReason.cs (83%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Token}/TokenIssueContext.cs (85%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Token}/TokenRefreshContext.cs (77%) rename src/CodeBeam.UltimateAuth.Core/{Enums => Contracts/Token}/TokenType.cs (63%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/Token}/TokenValidationResult.cs (97%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/User}/RegisterUserRequest.cs (94%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/User}/UserAuthenticationResult.cs (88%) rename src/CodeBeam.UltimateAuth.Core/{Contexts => Contracts/User}/UserContext.cs (74%) rename src/CodeBeam.UltimateAuth.Core/{Models => Contracts/User}/ValidateCredentialsRequest.cs (92%) rename src/CodeBeam.UltimateAuth.Core/{Abstractions => Domain/User}/IUser.cs (93%) rename src/CodeBeam.UltimateAuth.Core/{Utilities => Infrastructure}/Base64Url.cs (96%) rename src/CodeBeam.UltimateAuth.Core/{Utilities => Infrastructure}/GuidUserIdFactory.cs (77%) rename src/CodeBeam.UltimateAuth.Core/{Utilities => Infrastructure}/RandomIdGenerator.cs (97%) rename src/CodeBeam.UltimateAuth.Core/{Utilities => Infrastructure}/StringUserIdFactory.cs (79%) rename src/CodeBeam.UltimateAuth.Core/{Utilities => Infrastructure}/UAuthUserIdConverter.cs (98%) rename src/CodeBeam.UltimateAuth.Core/{Utilities => Infrastructure}/UAuthUserIdConverterResolver.cs (97%) rename src/CodeBeam.UltimateAuth.Core/{Users => Infrastructure}/UserIdFactory.cs (82%) rename src/CodeBeam.UltimateAuth.Core/{Users => Infrastructure}/UserRecord.cs (91%) delete mode 100644 src/CodeBeam.UltimateAuth.Core/Models/ReauthResult.cs rename src/CodeBeam.UltimateAuth.Core/{Enums => Options}/UAuthMode.cs (100%) delete mode 100644 src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserService.cs rename src/CodeBeam.UltimateAuth.Server/{Sessions => Infrastructure}/BearerSessionIdResolver.cs (92%) rename src/CodeBeam.UltimateAuth.Server/{Sessions => Infrastructure}/CompositeSessionIdResolver.cs (92%) rename src/CodeBeam.UltimateAuth.Server/{Sessions => Infrastructure}/CookieSessionIdResolver.cs (92%) rename src/CodeBeam.UltimateAuth.Server/{Users => Infrastructure}/DefaultUserAuthenticator.cs (93%) rename src/CodeBeam.UltimateAuth.Server/{Sessions => Infrastructure}/HeaderSessionIdResolver.cs (92%) rename src/CodeBeam.UltimateAuth.Server/{Sessions => Infrastructure}/ISessionIdResolver.cs (77%) rename src/CodeBeam.UltimateAuth.Server/{Sessions => Infrastructure}/ISessionOrchestrator.cs (89%) create mode 100644 src/CodeBeam.UltimateAuth.Server/Infrastructure/ITokenIssuer.cs rename src/CodeBeam.UltimateAuth.Server/{Users => Infrastructure}/IUserAccessor.cs (71%) rename src/CodeBeam.UltimateAuth.Server/{Sessions => Infrastructure}/QuerySessionIdResolver.cs (92%) rename src/CodeBeam.UltimateAuth.Server/{Utility => Infrastructure}/SystemClock.cs (75%) rename src/{CodeBeam.UltimateAuth.Core/Models/TokenIssuerContext.cs => CodeBeam.UltimateAuth.Server/Infrastructure/TokenIssuanceContext.cs} (77%) rename src/CodeBeam.UltimateAuth.Server/{Utility => Infrastructure}/UAuthDeviceResolver.cs (96%) rename src/CodeBeam.UltimateAuth.Server/{Sessions => Infrastructure}/UAuthSessionIdResolver.cs (96%) rename src/CodeBeam.UltimateAuth.Server/{Sessions => Infrastructure}/UAuthSessionOrchestrator.cs (98%) rename src/CodeBeam.UltimateAuth.Server/{Users => Infrastructure}/UAuthUserAccessor.cs (95%) rename src/CodeBeam.UltimateAuth.Server/{Users => Infrastructure}/UAuthUserId.cs (86%) rename src/CodeBeam.UltimateAuth.Server/{Users => Stores}/AspNetIdentityUserStore.cs (96%) diff --git a/src/CodeBeam.UltimateAuth.AspNetCore/Abstractions/.gitkeep b/src/CodeBeam.UltimateAuth.AspNetCore/Abstractions/.gitkeep deleted file mode 100644 index 5f28270..0000000 --- a/src/CodeBeam.UltimateAuth.AspNetCore/Abstractions/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserManagementService.cs b/src/CodeBeam.UltimateAuth.AspNetCore/Abstractions/IUAuthUserManagementService.cs similarity index 93% rename from src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserManagementService.cs rename to src/CodeBeam.UltimateAuth.AspNetCore/Abstractions/IUAuthUserManagementService.cs index 255d4ab..44c57f1 100644 --- a/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserManagementService.cs +++ b/src/CodeBeam.UltimateAuth.AspNetCore/Abstractions/IUAuthUserManagementService.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Users +namespace CodeBeam.UltimateAuth.Server.Users { /// /// Administrative user management operations. diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserProfileService.cs b/src/CodeBeam.UltimateAuth.AspNetCore/Abstractions/IUAuthUserProfileService.cs similarity index 91% rename from src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserProfileService.cs rename to src/CodeBeam.UltimateAuth.AspNetCore/Abstractions/IUAuthUserProfileService.cs index ea2dcb3..68dedea 100644 --- a/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserProfileService.cs +++ b/src/CodeBeam.UltimateAuth.AspNetCore/Abstractions/IUAuthUserProfileService.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Users.Abstractions +namespace CodeBeam.UltimateAuth.Server.Users { /// /// User self-service operations (profile, password, MFA). diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Models/AdminUserFilter.cs b/src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/AdminUserFilter.cs similarity index 79% rename from src/CodeBeam.UltimateAuth.Core/Users/Models/AdminUserFilter.cs rename to src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/AdminUserFilter.cs index e6256f2..87cec2b 100644 --- a/src/CodeBeam.UltimateAuth.Core/Users/Models/AdminUserFilter.cs +++ b/src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/AdminUserFilter.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Users.Models +namespace CodeBeam.UltimateAuth.Server.Users { public sealed class AdminUserFilter { diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Models/ChangePasswordRequest.cs b/src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/ChangePasswordRequest.cs similarity index 87% rename from src/CodeBeam.UltimateAuth.Core/Users/Models/ChangePasswordRequest.cs rename to src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/ChangePasswordRequest.cs index e4a9b37..b53dfb6 100644 --- a/src/CodeBeam.UltimateAuth.Core/Users/Models/ChangePasswordRequest.cs +++ b/src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/ChangePasswordRequest.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Users +namespace CodeBeam.UltimateAuth.Server.Users { public sealed class ChangePasswordRequest { diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Models/ConfigureMfaRequest.cs b/src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/ConfigureMfaRequest.cs similarity index 84% rename from src/CodeBeam.UltimateAuth.Core/Users/Models/ConfigureMfaRequest.cs rename to src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/ConfigureMfaRequest.cs index 4f35557..ff2b1d4 100644 --- a/src/CodeBeam.UltimateAuth.Core/Users/Models/ConfigureMfaRequest.cs +++ b/src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/ConfigureMfaRequest.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Users +namespace CodeBeam.UltimateAuth.Server.Users { public sealed class ConfigureMfaRequest { diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Models/ResetPasswordRequest.cs b/src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/ResetPasswordRequest.cs similarity index 85% rename from src/CodeBeam.UltimateAuth.Core/Users/Models/ResetPasswordRequest.cs rename to src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/ResetPasswordRequest.cs index 48733c0..672d176 100644 --- a/src/CodeBeam.UltimateAuth.Core/Users/Models/ResetPasswordRequest.cs +++ b/src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/ResetPasswordRequest.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Users +namespace CodeBeam.UltimateAuth.Server.Users { public sealed class ResetPasswordRequest { diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Models/UpdateProfileRequest.cs b/src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/UpdateProfileRequest.cs similarity index 76% rename from src/CodeBeam.UltimateAuth.Core/Users/Models/UpdateProfileRequest.cs rename to src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/UpdateProfileRequest.cs index db6b710..f7688c9 100644 --- a/src/CodeBeam.UltimateAuth.Core/Users/Models/UpdateProfileRequest.cs +++ b/src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/UpdateProfileRequest.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Users +namespace CodeBeam.UltimateAuth.Server.Users { public sealed class UpdateProfileRequest { diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Models/UserDto.cs b/src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/UserDto.cs similarity index 89% rename from src/CodeBeam.UltimateAuth.Core/Users/Models/UserDto.cs rename to src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/UserDto.cs index f81ba14..8aa7cb5 100644 --- a/src/CodeBeam.UltimateAuth.Core/Users/Models/UserDto.cs +++ b/src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/UserDto.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Users +namespace CodeBeam.UltimateAuth.Server.Users { public sealed class UserDto { diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Models/UserProfileDto.cs b/src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/UserProfileDto.cs similarity index 86% rename from src/CodeBeam.UltimateAuth.Core/Users/Models/UserProfileDto.cs rename to src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/UserProfileDto.cs index 6724614..9b2496e 100644 --- a/src/CodeBeam.UltimateAuth.Core/Users/Models/UserProfileDto.cs +++ b/src/CodeBeam.UltimateAuth.AspNetCore/Users/Models/UserProfileDto.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Users +namespace CodeBeam.UltimateAuth.Server.Users { public sealed class UserProfileDto { diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/IUserAuthenticator.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/IUserAuthenticator.cs deleted file mode 100644 index 139d773..0000000 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/IUserAuthenticator.cs +++ /dev/null @@ -1,13 +0,0 @@ -using CodeBeam.UltimateAuth.Core.Domain; - -namespace CodeBeam.UltimateAuth.Core.Abstractions -{ - public interface IUserAuthenticator - { - Task> AuthenticateAsync( - string? tenantId, - string identifier, - string secret, - CancellationToken cancellationToken = default); - } -} diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/IClock.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Infrastructure/IClock.cs similarity index 100% rename from src/CodeBeam.UltimateAuth.Core/Abstractions/IClock.cs rename to src/CodeBeam.UltimateAuth.Core/Abstractions/Infrastructure/IClock.cs diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/ITokenHasher.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Infrastructure/ITokenHasher.cs similarity index 100% rename from src/CodeBeam.UltimateAuth.Core/Abstractions/ITokenHasher.cs rename to src/CodeBeam.UltimateAuth.Core/Abstractions/Infrastructure/ITokenHasher.cs diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/IUAuthPasswordHasher.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Infrastructure/IUAuthPasswordHasher.cs similarity index 100% rename from src/CodeBeam.UltimateAuth.Core/Abstractions/IUAuthPasswordHasher.cs rename to src/CodeBeam.UltimateAuth.Core/Abstractions/Infrastructure/IUAuthPasswordHasher.cs diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ISessionIssuer.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ISessionIssuer.cs index 732633d..726bbfb 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ISessionIssuer.cs +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ISessionIssuer.cs @@ -1,4 +1,4 @@ -using CodeBeam.UltimateAuth.Core.Contexts; +using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; namespace CodeBeam.UltimateAuth.Core.Abstractions diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenIssuer.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenIssuer.cs deleted file mode 100644 index d1ac6bb..0000000 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenIssuer.cs +++ /dev/null @@ -1,15 +0,0 @@ -using CodeBeam.UltimateAuth.Core.Contexts; -using CodeBeam.UltimateAuth.Core.Models; - -namespace CodeBeam.UltimateAuth.Core.Abstractions -{ - /// - /// Issues access and refresh tokens according to the active auth mode. - /// Does not perform persistence or validation. - /// - public interface ITokenIssuer - { - Task IssueAccessTokenAsync(TokenIssuerContext context, CancellationToken cancellationToken = default); - Task IssueRefreshTokenAsync(TokenIssuerContext context, CancellationToken cancellationToken = default); - } -} diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Principals/IUserAuthenticator.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Principals/IUserAuthenticator.cs new file mode 100644 index 0000000..0708937 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Principals/IUserAuthenticator.cs @@ -0,0 +1,9 @@ +using CodeBeam.UltimateAuth.Core.Contracts; + +namespace CodeBeam.UltimateAuth.Core.Abstractions +{ + public interface IUserAuthenticator + { + Task> AuthenticateAsync(string? tenantId, string identifier, string secret, CancellationToken cancellationToken = default); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/IUserIdConverter.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Principals/IUserIdConverter.cs similarity index 100% rename from src/CodeBeam.UltimateAuth.Core/Abstractions/IUserIdConverter.cs rename to src/CodeBeam.UltimateAuth.Core/Abstractions/Principals/IUserIdConverter.cs diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/IUserIdConverterResolver.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Principals/IUserIdConverterResolver.cs similarity index 100% rename from src/CodeBeam.UltimateAuth.Core/Abstractions/IUserIdConverterResolver.cs rename to src/CodeBeam.UltimateAuth.Core/Abstractions/Principals/IUserIdConverterResolver.cs diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/IUserIdFactory.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Principals/IUserIdFactory.cs similarity index 100% rename from src/CodeBeam.UltimateAuth.Core/Abstractions/IUserIdFactory.cs rename to src/CodeBeam.UltimateAuth.Core/Abstractions/Principals/IUserIdFactory.cs diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthFlowService.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthFlowService.cs index 00eb1d0..530bacd 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthFlowService.cs +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthFlowService.cs @@ -1,4 +1,4 @@ -using CodeBeam.UltimateAuth.Core.Models; +using CodeBeam.UltimateAuth.Core.Contracts; namespace CodeBeam.UltimateAuth.Core.Abstractions { @@ -8,52 +8,26 @@ namespace CodeBeam.UltimateAuth.Core.Abstractions /// public interface IUAuthFlowService { - // ---------- LOGIN ---------- - Task LoginAsync( - LoginRequest request, - CancellationToken ct = default); - - Task ExternalLoginAsync( - ExternalLoginRequest request, - CancellationToken ct = default); - - // ---------- MFA ---------- - Task BeginMfaAsync( - BeginMfaRequest request, - CancellationToken ct = default); - - Task CompleteMfaAsync( - CompleteMfaRequest request, - CancellationToken ct = default); - - // ---------- SESSION FLOW ---------- - Task LogoutAsync( - LogoutRequest request, - CancellationToken ct = default); - - Task LogoutAllAsync( - LogoutAllRequest request, - CancellationToken ct = default); - - Task RefreshSessionAsync( - SessionRefreshRequest request, - CancellationToken ct = default); - - Task ReauthenticateAsync( - ReauthRequest request, - CancellationToken ct = default); - - // ---------- PKCE ---------- - Task CreatePkceChallengeAsync( - PkceCreateRequest request, - CancellationToken ct = default); - - Task VerifyPkceAsync( - PkceVerifyRequest request, - CancellationToken ct = default); - - Task ConsumePkceAsync( - PkceConsumeRequest request, - CancellationToken ct = default); + Task LoginAsync(LoginRequest request, CancellationToken ct = default); + + Task ExternalLoginAsync(ExternalLoginRequest request, CancellationToken ct = default); + + Task BeginMfaAsync(BeginMfaRequest request, CancellationToken ct = default); + + Task CompleteMfaAsync(CompleteMfaRequest request, CancellationToken ct = default); + + Task LogoutAsync(LogoutRequest request, CancellationToken ct = default); + + Task LogoutAllAsync(LogoutAllRequest request, CancellationToken ct = default); + + Task RefreshSessionAsync(SessionRefreshRequest request, CancellationToken ct = default); + + Task ReauthenticateAsync(ReauthRequest request, CancellationToken ct = default); + + Task CreatePkceChallengeAsync(PkceCreateRequest request, CancellationToken ct = default); + + Task VerifyPkceAsync(PkceVerifyRequest request, CancellationToken ct = default); + + Task ConsumePkceAsync(PkceConsumeRequest request, CancellationToken ct = default); } } diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthSessionService.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthSessionService.cs index 60e6413..25ad4f5 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthSessionService.cs +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthSessionService.cs @@ -1,6 +1,5 @@ -using CodeBeam.UltimateAuth.Core.Contexts; +using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; -using CodeBeam.UltimateAuth.Core.Models; namespace CodeBeam.UltimateAuth.Core.Abstractions { @@ -10,54 +9,25 @@ namespace CodeBeam.UltimateAuth.Core.Abstractions /// The type used to uniquely identify the user. public interface IUAuthSessionService { - // ---------- READ ---------- - Task> ValidateSessionAsync( - string? tenantId, - AuthSessionId sessionId, - DateTime now); - - Task>> GetChainsAsync( - string? tenantId, - TUserId userId); - - Task>> GetSessionsAsync( - string? tenantId, - ChainId chainId); - - Task?> GetCurrentSessionAsync( - string? tenantId, - AuthSessionId sessionId); - - // ---------- WRITE / REVOKE ---------- - Task RevokeSessionAsync( - string? tenantId, - AuthSessionId sessionId, - DateTime at); - - Task RevokeChainAsync( - string? tenantId, - ChainId chainId, - DateTime at); - - Task ResolveChainIdAsync( - string? tenantId, - AuthSessionId sessionId); - - Task RevokeAllChainsAsync( - string? tenantId, - TUserId userId, - ChainId? exceptChainId, - DateTime at); + Task> ValidateSessionAsync(string? tenantId, AuthSessionId sessionId, DateTime at); + + Task>> GetChainsAsync(string? tenantId, TUserId userId); + + Task>> GetSessionsAsync(string? tenantId, ChainId chainId); + + Task?> GetCurrentSessionAsync(string? tenantId, AuthSessionId sessionId); + + Task RevokeSessionAsync(string? tenantId, AuthSessionId sessionId, DateTime at); + + Task RevokeChainAsync(string? tenantId, ChainId chainId, DateTime at); + + Task ResolveChainIdAsync(string? tenantId, AuthSessionId sessionId); + + Task RevokeAllChainsAsync(string? tenantId, TUserId userId, ChainId? exceptChainId, DateTime at); // Hard revoke - admin - Task RevokeRootAsync( - string? tenantId, - TUserId userId, - DateTime at); - - Task> IssueSessionAfterAuthenticationAsync( - string? tenantId, - AuthenticatedSessionContext context, - CancellationToken cancellationToken = default); + Task RevokeRootAsync(string? tenantId, TUserId userId, DateTime at); + + Task> IssueSessionAfterAuthenticationAsync(string? tenantId, AuthenticatedSessionContext context, CancellationToken cancellationToken = default); } } diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthTokenService.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthTokenService.cs index aca3d88..bca0bd6 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthTokenService.cs +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthTokenService.cs @@ -1,4 +1,4 @@ -using CodeBeam.UltimateAuth.Core.Models; +using CodeBeam.UltimateAuth.Core.Contracts; namespace CodeBeam.UltimateAuth.Core.Abstractions { @@ -12,23 +12,16 @@ public interface IUAuthTokenService /// Issues access (and optionally refresh) tokens /// for a validated session. /// - Task CreateTokensAsync( - TokenIssueContext context, - CancellationToken cancellationToken = default); + Task CreateTokensAsync(TokenIssueContext context, CancellationToken cancellationToken = default); /// /// Refreshes tokens using a refresh token. /// - Task RefreshAsync( - TokenRefreshContext context, - CancellationToken cancellationToken = default); + Task RefreshAsync(TokenRefreshContext context, CancellationToken cancellationToken = default); /// /// Validates an access token (JWT or opaque). /// - Task> ValidateAsync( - string token, - TokenType type, - CancellationToken cancellationToken = default); + Task> ValidateAsync(string token, TokenType type, CancellationToken cancellationToken = default); } } diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthUserService.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthUserService.cs new file mode 100644 index 0000000..ee74667 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Services/IUAuthUserService.cs @@ -0,0 +1,20 @@ +using CodeBeam.UltimateAuth.Core.Contracts; + +namespace CodeBeam.UltimateAuth.Core.Abstractions +{ + /// + /// Minimal user operations required for authentication. + /// Does NOT include role or permission management. + /// For user management, CodeBeam.UltimateAuth.Users package is recommended. + /// + public interface IUAuthUserService + { + Task RegisterAsync(RegisterUserRequest request, CancellationToken cancellationToken = default); + + Task DeleteAsync(TUserId userId, CancellationToken cancellationToken = default); + + Task ValidateCredentialsAsync(ValidateCredentialsRequest request, CancellationToken cancellationToken = default); + + Task> AuthenticateAsync(string? tenantId, string identifier, string secret, CancellationToken cancellationToken = default); + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IOpaqueTokenStore.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IOpaqueTokenStore.cs index 2657788..2f5e141 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IOpaqueTokenStore.cs +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IOpaqueTokenStore.cs @@ -1,4 +1,4 @@ -using CodeBeam.UltimateAuth.Core.Models; +using CodeBeam.UltimateAuth.Core.Contracts; namespace CodeBeam.UltimateAuth.Core.Abstractions { diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/ISessionStore.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/ISessionStore.cs index e25ba9c..5b65e7d 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/ISessionStore.cs +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/ISessionStore.cs @@ -1,4 +1,4 @@ -using CodeBeam.UltimateAuth.Core.Contexts; +using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; namespace CodeBeam.UltimateAuth.Core.Abstractions diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUAuthUserStore.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUAuthUserStore.cs index 386675f..53f78c5 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUAuthUserStore.cs +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/IUAuthUserStore.cs @@ -1,4 +1,5 @@ -using CodeBeam.UltimateAuth.Core.Users; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Infrastructure; namespace CodeBeam.UltimateAuth.Core.Abstractions { diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/ITokenValidator.cs b/src/CodeBeam.UltimateAuth.Core/Abstractions/Validators/ITokenValidator.cs similarity index 89% rename from src/CodeBeam.UltimateAuth.Core/Abstractions/ITokenValidator.cs rename to src/CodeBeam.UltimateAuth.Core/Abstractions/Validators/ITokenValidator.cs index 9b8a7e2..d2010fb 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/ITokenValidator.cs +++ b/src/CodeBeam.UltimateAuth.Core/Abstractions/Validators/ITokenValidator.cs @@ -1,4 +1,4 @@ -using CodeBeam.UltimateAuth.Core.Models; +using CodeBeam.UltimateAuth.Core.Contracts; namespace CodeBeam.UltimateAuth.Core.Abstractions { diff --git a/src/CodeBeam.UltimateAuth.Core/CodeBeam.UltimateAuth.Core.csproj b/src/CodeBeam.UltimateAuth.Core/CodeBeam.UltimateAuth.Core.csproj index 488b584..c03f964 100644 --- a/src/CodeBeam.UltimateAuth.Core/CodeBeam.UltimateAuth.Core.csproj +++ b/src/CodeBeam.UltimateAuth.Core/CodeBeam.UltimateAuth.Core.csproj @@ -8,9 +8,9 @@ - - - + + + diff --git a/src/CodeBeam.UltimateAuth.Core/Models/ExternalLoginRequest.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Login/ExternalLoginRequest.cs similarity index 84% rename from src/CodeBeam.UltimateAuth.Core/Models/ExternalLoginRequest.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Login/ExternalLoginRequest.cs index 249cdac..6126e7d 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/ExternalLoginRequest.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Login/ExternalLoginRequest.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record ExternalLoginRequest { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/LoginContinuation.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Login/LoginContinuation.cs similarity index 91% rename from src/CodeBeam.UltimateAuth.Core/Models/LoginContinuation.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Login/LoginContinuation.cs index 6e61c95..ec5fb02 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/LoginContinuation.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Login/LoginContinuation.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record LoginContinuation { diff --git a/src/CodeBeam.UltimateAuth.Core/Enums/LoginContinuationType.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Login/LoginContinuationType.cs similarity index 66% rename from src/CodeBeam.UltimateAuth.Core/Enums/LoginContinuationType.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Login/LoginContinuationType.cs index ae29355..662fbef 100644 --- a/src/CodeBeam.UltimateAuth.Core/Enums/LoginContinuationType.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Login/LoginContinuationType.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core +namespace CodeBeam.UltimateAuth.Core.Contracts { public enum LoginContinuationType { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/LoginRequest.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Login/LoginRequest.cs similarity index 94% rename from src/CodeBeam.UltimateAuth.Core/Models/LoginRequest.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Login/LoginRequest.cs index d757f05..97a75f0 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/LoginRequest.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Login/LoginRequest.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Domain; -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record LoginRequest { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/LoginResult.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Login/LoginResult.cs similarity index 93% rename from src/CodeBeam.UltimateAuth.Core/Models/LoginResult.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Login/LoginResult.cs index 0d80de4..cc0d44f 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/LoginResult.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Login/LoginResult.cs @@ -1,7 +1,7 @@ -using CodeBeam.UltimateAuth.Core.Contexts; +using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record LoginResult { diff --git a/src/CodeBeam.UltimateAuth.Core/Enums/LoginStatus.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Login/LoginStatus.cs similarity index 67% rename from src/CodeBeam.UltimateAuth.Core/Enums/LoginStatus.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Login/LoginStatus.cs index 1eb3f43..94a3902 100644 --- a/src/CodeBeam.UltimateAuth.Core/Enums/LoginStatus.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Login/LoginStatus.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core +namespace CodeBeam.UltimateAuth.Core.Contracts { public enum LoginStatus { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/ReauthRequest.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Login/ReauthRequest.cs similarity index 84% rename from src/CodeBeam.UltimateAuth.Core/Models/ReauthRequest.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Login/ReauthRequest.cs index 9479357..b1d2565 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/ReauthRequest.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Login/ReauthRequest.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Domain; -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record ReauthRequest { diff --git a/src/CodeBeam.UltimateAuth.Core/Contracts/Login/ReauthResult.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Login/ReauthResult.cs new file mode 100644 index 0000000..d14eb10 --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Login/ReauthResult.cs @@ -0,0 +1,7 @@ +namespace CodeBeam.UltimateAuth.Core.Contracts +{ + public sealed record ReauthResult + { + public bool Success { get; init; } + } +} diff --git a/src/CodeBeam.UltimateAuth.Core/Models/LogoutAllRequest.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Logout/LogoutAllRequest.cs similarity index 92% rename from src/CodeBeam.UltimateAuth.Core/Models/LogoutAllRequest.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Logout/LogoutAllRequest.cs index db40511..7bfa2da 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/LogoutAllRequest.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Logout/LogoutAllRequest.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Domain; -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed class LogoutAllRequest { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/LogoutRequest.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Logout/LogoutRequest.cs similarity index 90% rename from src/CodeBeam.UltimateAuth.Core/Models/LogoutRequest.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Logout/LogoutRequest.cs index 274d48c..90e6973 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/LogoutRequest.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Logout/LogoutRequest.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Domain; -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record LogoutRequest { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/BeginMfaRequest.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Mfa/BeginMfaRequest.cs similarity index 69% rename from src/CodeBeam.UltimateAuth.Core/Models/BeginMfaRequest.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Mfa/BeginMfaRequest.cs index cb19c3c..86af91a 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/BeginMfaRequest.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Mfa/BeginMfaRequest.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record BeginMfaRequest { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/CompleteMfaRequest.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Mfa/CompleteMfaRequest.cs similarity index 77% rename from src/CodeBeam.UltimateAuth.Core/Models/CompleteMfaRequest.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Mfa/CompleteMfaRequest.cs index 82eaee4..5d575d0 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/CompleteMfaRequest.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Mfa/CompleteMfaRequest.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record CompleteMfaRequest { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/MfaChallengeResult.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Mfa/MfaChallengeResult.cs similarity index 80% rename from src/CodeBeam.UltimateAuth.Core/Models/MfaChallengeResult.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Mfa/MfaChallengeResult.cs index 1fa4653..9bb085c 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/MfaChallengeResult.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Mfa/MfaChallengeResult.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record MfaChallengeResult { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/PkceChallengeResult.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Pkce/PkceChallengeResult.cs similarity index 77% rename from src/CodeBeam.UltimateAuth.Core/Models/PkceChallengeResult.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Pkce/PkceChallengeResult.cs index 1e67e38..1a4d986 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/PkceChallengeResult.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Pkce/PkceChallengeResult.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record PkceChallengeResult { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/PkceConsumeRequest.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Pkce/PkceConsumeRequest.cs similarity index 70% rename from src/CodeBeam.UltimateAuth.Core/Models/PkceConsumeRequest.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Pkce/PkceConsumeRequest.cs index 4366c55..153e865 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/PkceConsumeRequest.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Pkce/PkceConsumeRequest.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record PkceConsumeRequest { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/PkceCreateRequest.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Pkce/PkceCreateRequest.cs similarity index 70% rename from src/CodeBeam.UltimateAuth.Core/Models/PkceCreateRequest.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Pkce/PkceCreateRequest.cs index 7230a8b..bd8eb88 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/PkceCreateRequest.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Pkce/PkceCreateRequest.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record PkceCreateRequest { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/PkceVerificationResult.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Pkce/PkceVerificationResult.cs similarity index 68% rename from src/CodeBeam.UltimateAuth.Core/Models/PkceVerificationResult.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Pkce/PkceVerificationResult.cs index 2d14c05..c094b0a 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/PkceVerificationResult.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Pkce/PkceVerificationResult.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record PkceVerificationResult { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/PkceVerifyRequest.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Pkce/PkceVerifyRequest.cs similarity index 77% rename from src/CodeBeam.UltimateAuth.Core/Models/PkceVerifyRequest.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Pkce/PkceVerifyRequest.cs index a6b6776..9a1d588 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/PkceVerifyRequest.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Pkce/PkceVerifyRequest.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record PkceVerifyRequest { diff --git a/src/CodeBeam.UltimateAuth.Core/Contexts/AuthenticatedSessionContext.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/AuthenticatedSessionContext.cs similarity index 95% rename from src/CodeBeam.UltimateAuth.Core/Contexts/AuthenticatedSessionContext.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Session/AuthenticatedSessionContext.cs index 6bfc961..6422fbb 100644 --- a/src/CodeBeam.UltimateAuth.Core/Contexts/AuthenticatedSessionContext.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/AuthenticatedSessionContext.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Domain; -namespace CodeBeam.UltimateAuth.Core.Contexts +namespace CodeBeam.UltimateAuth.Core.Contracts { /// /// Represents the context in which a session is issued diff --git a/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/IssuedSession.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/IssuedSession.cs similarity index 93% rename from src/CodeBeam.UltimateAuth.Core/Contexts/Issued/IssuedSession.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Session/IssuedSession.cs index 157663a..cc2f0f8 100644 --- a/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/IssuedSession.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/IssuedSession.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Domain; -namespace CodeBeam.UltimateAuth.Core.Contexts +namespace CodeBeam.UltimateAuth.Core.Contracts { /// /// Represents the result of a session issuance operation. diff --git a/src/CodeBeam.UltimateAuth.Server/Sessions/SessionContext.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionContext.cs similarity index 94% rename from src/CodeBeam.UltimateAuth.Server/Sessions/SessionContext.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionContext.cs index 3aad385..03af37b 100644 --- a/src/CodeBeam.UltimateAuth.Server/Sessions/SessionContext.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionContext.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Domain; -namespace CodeBeam.UltimateAuth.Server.Sessions +namespace CodeBeam.UltimateAuth.Core.Contracts { /// /// Lightweight session context resolved from the incoming request. diff --git a/src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshRequest.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionRefreshRequest.cs similarity index 77% rename from src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshRequest.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionRefreshRequest.cs index deee12a..9343883 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshRequest.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionRefreshRequest.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record SessionRefreshRequest { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshResult.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionRefreshResult.cs similarity index 66% rename from src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshResult.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionRefreshResult.cs index 9bfcc44..d207f59 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/SessionRefreshResult.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionRefreshResult.cs @@ -1,6 +1,6 @@ -using CodeBeam.UltimateAuth.Core.Contexts; +using CodeBeam.UltimateAuth.Core.Contracts; -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record SessionRefreshResult { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/SessionResult.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionResult.cs similarity index 97% rename from src/CodeBeam.UltimateAuth.Core/Models/SessionResult.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionResult.cs index 5dbfb06..cb43f4e 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/SessionResult.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionResult.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Domain; -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { // TODO: IsNewChain, IsNewRoot flags? /// diff --git a/src/CodeBeam.UltimateAuth.Core/Contexts/SessionRotationContext.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionRotationContext.cs similarity index 90% rename from src/CodeBeam.UltimateAuth.Core/Contexts/SessionRotationContext.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionRotationContext.cs index 775ee63..2510afa 100644 --- a/src/CodeBeam.UltimateAuth.Core/Contexts/SessionRotationContext.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionRotationContext.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Domain; -namespace CodeBeam.UltimateAuth.Core.Contexts +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record SessionRotationContext { diff --git a/src/CodeBeam.UltimateAuth.Core/Contexts/SessionStoreContext.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionStoreContext.cs similarity index 96% rename from src/CodeBeam.UltimateAuth.Core/Contexts/SessionStoreContext.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionStoreContext.cs index fa5a426..78910d4 100644 --- a/src/CodeBeam.UltimateAuth.Core/Contexts/SessionStoreContext.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionStoreContext.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Domain; -namespace CodeBeam.UltimateAuth.Core.Contexts +namespace CodeBeam.UltimateAuth.Core.Contracts { /// /// Context information required by the session store when diff --git a/src/CodeBeam.UltimateAuth.Core/Contexts/SessionValidationContext.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionValidationContext.cs similarity index 86% rename from src/CodeBeam.UltimateAuth.Core/Contexts/SessionValidationContext.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionValidationContext.cs index 33d6b66..aa8b3dd 100644 --- a/src/CodeBeam.UltimateAuth.Core/Contexts/SessionValidationContext.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionValidationContext.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Domain; -namespace CodeBeam.UltimateAuth.Core.Contexts +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record SessionValidationContext { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/SessionValidationResult.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionValidationResult.cs similarity index 96% rename from src/CodeBeam.UltimateAuth.Core/Models/SessionValidationResult.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionValidationResult.cs index 7d722d7..26e9020 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/SessionValidationResult.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Session/SessionValidationResult.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Domain; -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed class SessionValidationResult { diff --git a/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/AccessToken.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/AccessToken.cs similarity index 94% rename from src/CodeBeam.UltimateAuth.Core/Contexts/Issued/AccessToken.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Token/AccessToken.cs index 26da31f..3284350 100644 --- a/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/AccessToken.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/AccessToken.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Contexts +namespace CodeBeam.UltimateAuth.Core.Contracts { /// /// Represents an issued access token (JWT or opaque). diff --git a/src/CodeBeam.UltimateAuth.Core/Models/AuthTokens.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/AuthTokens.cs similarity index 85% rename from src/CodeBeam.UltimateAuth.Core/Models/AuthTokens.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Token/AuthTokens.cs index a84cd8f..344fedd 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/AuthTokens.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/AuthTokens.cs @@ -1,6 +1,4 @@ -using CodeBeam.UltimateAuth.Core.Contexts; - -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { /// /// Represents a set of authentication tokens issued as a result of a successful login. diff --git a/src/CodeBeam.UltimateAuth.Core/Models/OpaqueTokenRecord.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/OpaqueTokenRecord.cs similarity index 92% rename from src/CodeBeam.UltimateAuth.Core/Models/OpaqueTokenRecord.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Token/OpaqueTokenRecord.cs index 419436e..ed13a6a 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/OpaqueTokenRecord.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/OpaqueTokenRecord.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using System.Security.Claims; -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed class OpaqueTokenRecord { diff --git a/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/RefreshToken.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/RefreshToken.cs similarity index 92% rename from src/CodeBeam.UltimateAuth.Core/Contexts/Issued/RefreshToken.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Token/RefreshToken.cs index 943e424..1e9d87a 100644 --- a/src/CodeBeam.UltimateAuth.Core/Contexts/Issued/RefreshToken.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/RefreshToken.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Contexts +namespace CodeBeam.UltimateAuth.Core.Contracts { /// /// Represents an issued refresh token. diff --git a/src/CodeBeam.UltimateAuth.Core/Enums/TokenInvalidReason.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/TokenInvalidReason.cs similarity index 83% rename from src/CodeBeam.UltimateAuth.Core/Enums/TokenInvalidReason.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Token/TokenInvalidReason.cs index eb1dcd7..96ce78b 100644 --- a/src/CodeBeam.UltimateAuth.Core/Enums/TokenInvalidReason.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/TokenInvalidReason.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core +namespace CodeBeam.UltimateAuth.Core.Contracts { public enum TokenInvalidReason { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/TokenIssueContext.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/TokenIssueContext.cs similarity index 85% rename from src/CodeBeam.UltimateAuth.Core/Models/TokenIssueContext.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Token/TokenIssueContext.cs index ec8a3f4..ad3dc76 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/TokenIssueContext.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/TokenIssueContext.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Domain; -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record TokenIssueContext { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/TokenRefreshContext.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/TokenRefreshContext.cs similarity index 77% rename from src/CodeBeam.UltimateAuth.Core/Models/TokenRefreshContext.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Token/TokenRefreshContext.cs index fa561d3..9507442 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/TokenRefreshContext.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/TokenRefreshContext.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record TokenRefreshContext { diff --git a/src/CodeBeam.UltimateAuth.Core/Enums/TokenType.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/TokenType.cs similarity index 63% rename from src/CodeBeam.UltimateAuth.Core/Enums/TokenType.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Token/TokenType.cs index db0ea7c..dc94f72 100644 --- a/src/CodeBeam.UltimateAuth.Core/Enums/TokenType.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/TokenType.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core +namespace CodeBeam.UltimateAuth.Core.Contracts { public enum TokenType { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/TokenValidationResult.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/TokenValidationResult.cs similarity index 97% rename from src/CodeBeam.UltimateAuth.Core/Models/TokenValidationResult.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/Token/TokenValidationResult.cs index 353f4f0..b8e7c29 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/TokenValidationResult.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/Token/TokenValidationResult.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using System.Security.Claims; -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed record TokenValidationResult { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/RegisterUserRequest.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/User/RegisterUserRequest.cs similarity index 94% rename from src/CodeBeam.UltimateAuth.Core/Models/RegisterUserRequest.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/User/RegisterUserRequest.cs index 6a9ed71..a5565b9 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/RegisterUserRequest.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/User/RegisterUserRequest.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { /// /// Request to register a new user with credentials. diff --git a/src/CodeBeam.UltimateAuth.Core/Models/UserAuthenticationResult.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/User/UserAuthenticationResult.cs similarity index 88% rename from src/CodeBeam.UltimateAuth.Core/Models/UserAuthenticationResult.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/User/UserAuthenticationResult.cs index 77a0315..64e2c34 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/UserAuthenticationResult.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/User/UserAuthenticationResult.cs @@ -1,4 +1,6 @@ -namespace CodeBeam.UltimateAuth.Core.Domain +using CodeBeam.UltimateAuth.Core.Domain; + +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed class UserAuthenticationResult { diff --git a/src/CodeBeam.UltimateAuth.Core/Contexts/UserContext.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/User/UserContext.cs similarity index 74% rename from src/CodeBeam.UltimateAuth.Core/Contexts/UserContext.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/User/UserContext.cs index 34f30a2..0c87265 100644 --- a/src/CodeBeam.UltimateAuth.Core/Contexts/UserContext.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/User/UserContext.cs @@ -1,6 +1,6 @@ -using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Domain; -namespace CodeBeam.UltimateAuth.Core.Contexts +namespace CodeBeam.UltimateAuth.Core.Contracts { public sealed class UserContext { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/ValidateCredentialsRequest.cs b/src/CodeBeam.UltimateAuth.Core/Contracts/User/ValidateCredentialsRequest.cs similarity index 92% rename from src/CodeBeam.UltimateAuth.Core/Models/ValidateCredentialsRequest.cs rename to src/CodeBeam.UltimateAuth.Core/Contracts/User/ValidateCredentialsRequest.cs index 711e3f0..fc1dd7e 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/ValidateCredentialsRequest.cs +++ b/src/CodeBeam.UltimateAuth.Core/Contracts/User/ValidateCredentialsRequest.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Core.Contracts { /// /// Request to validate user credentials. diff --git a/src/CodeBeam.UltimateAuth.Core/Domain/Session/DeviceInfo.cs b/src/CodeBeam.UltimateAuth.Core/Domain/Session/DeviceInfo.cs index c9322b2..c70c7e5 100644 --- a/src/CodeBeam.UltimateAuth.Core/Domain/Session/DeviceInfo.cs +++ b/src/CodeBeam.UltimateAuth.Core/Domain/Session/DeviceInfo.cs @@ -1,6 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace CodeBeam.UltimateAuth.Core.Domain +namespace CodeBeam.UltimateAuth.Core.Domain { /// /// Represents metadata describing the device or client environment initiating diff --git a/src/CodeBeam.UltimateAuth.Core/Abstractions/IUser.cs b/src/CodeBeam.UltimateAuth.Core/Domain/User/IUser.cs similarity index 93% rename from src/CodeBeam.UltimateAuth.Core/Abstractions/IUser.cs rename to src/CodeBeam.UltimateAuth.Core/Domain/User/IUser.cs index b3fe970..5222ca9 100644 --- a/src/CodeBeam.UltimateAuth.Core/Abstractions/IUser.cs +++ b/src/CodeBeam.UltimateAuth.Core/Domain/User/IUser.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Abstractions +namespace CodeBeam.UltimateAuth.Core.Domain { /// /// Represents the minimal user abstraction required by UltimateAuth. diff --git a/src/CodeBeam.UltimateAuth.Core/Extensions/UltimateAuthServiceCollectionExtensions.cs b/src/CodeBeam.UltimateAuth.Core/Extensions/UltimateAuthServiceCollectionExtensions.cs index a8f872d..315d384 100644 --- a/src/CodeBeam.UltimateAuth.Core/Extensions/UltimateAuthServiceCollectionExtensions.cs +++ b/src/CodeBeam.UltimateAuth.Core/Extensions/UltimateAuthServiceCollectionExtensions.cs @@ -1,9 +1,8 @@ using CodeBeam.UltimateAuth.Core.Abstractions; using CodeBeam.UltimateAuth.Core.Options; -using CodeBeam.UltimateAuth.Core.Utilities; +using CodeBeam.UltimateAuth.Core.Infrastructure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; namespace CodeBeam.UltimateAuth.Core.Extensions diff --git a/src/CodeBeam.UltimateAuth.Core/Utilities/Base64Url.cs b/src/CodeBeam.UltimateAuth.Core/Infrastructure/Base64Url.cs similarity index 96% rename from src/CodeBeam.UltimateAuth.Core/Utilities/Base64Url.cs rename to src/CodeBeam.UltimateAuth.Core/Infrastructure/Base64Url.cs index 8b120f0..48fb6c8 100644 --- a/src/CodeBeam.UltimateAuth.Core/Utilities/Base64Url.cs +++ b/src/CodeBeam.UltimateAuth.Core/Infrastructure/Base64Url.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Core.Utilities +namespace CodeBeam.UltimateAuth.Core.Infrastructure { /// /// Provides Base64 URL-safe encoding and decoding utilities. diff --git a/src/CodeBeam.UltimateAuth.Core/Utilities/GuidUserIdFactory.cs b/src/CodeBeam.UltimateAuth.Core/Infrastructure/GuidUserIdFactory.cs similarity index 77% rename from src/CodeBeam.UltimateAuth.Core/Utilities/GuidUserIdFactory.cs rename to src/CodeBeam.UltimateAuth.Core/Infrastructure/GuidUserIdFactory.cs index 195e5e0..afe906b 100644 --- a/src/CodeBeam.UltimateAuth.Core/Utilities/GuidUserIdFactory.cs +++ b/src/CodeBeam.UltimateAuth.Core/Infrastructure/GuidUserIdFactory.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Abstractions; -namespace CodeBeam.UltimateAuth.Core.Utilities +namespace CodeBeam.UltimateAuth.Core.Infrastructure { public sealed class GuidUserIdFactory : IUserIdFactory { diff --git a/src/CodeBeam.UltimateAuth.Core/Utilities/RandomIdGenerator.cs b/src/CodeBeam.UltimateAuth.Core/Infrastructure/RandomIdGenerator.cs similarity index 97% rename from src/CodeBeam.UltimateAuth.Core/Utilities/RandomIdGenerator.cs rename to src/CodeBeam.UltimateAuth.Core/Infrastructure/RandomIdGenerator.cs index 5c63d26..b2faa23 100644 --- a/src/CodeBeam.UltimateAuth.Core/Utilities/RandomIdGenerator.cs +++ b/src/CodeBeam.UltimateAuth.Core/Infrastructure/RandomIdGenerator.cs @@ -1,6 +1,6 @@ using System.Security.Cryptography; -namespace CodeBeam.UltimateAuth.Core.Utilities +namespace CodeBeam.UltimateAuth.Core.Infrastructure { /// /// Provides cryptographically secure random ID generation. diff --git a/src/CodeBeam.UltimateAuth.Core/Utilities/StringUserIdFactory.cs b/src/CodeBeam.UltimateAuth.Core/Infrastructure/StringUserIdFactory.cs similarity index 79% rename from src/CodeBeam.UltimateAuth.Core/Utilities/StringUserIdFactory.cs rename to src/CodeBeam.UltimateAuth.Core/Infrastructure/StringUserIdFactory.cs index 624490e..a622edf 100644 --- a/src/CodeBeam.UltimateAuth.Core/Utilities/StringUserIdFactory.cs +++ b/src/CodeBeam.UltimateAuth.Core/Infrastructure/StringUserIdFactory.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Abstractions; -namespace CodeBeam.UltimateAuth.Core.Utilities +namespace CodeBeam.UltimateAuth.Core.Infrastructure { public sealed class StringUserIdFactory : IUserIdFactory { diff --git a/src/CodeBeam.UltimateAuth.Core/Utilities/UAuthUserIdConverter.cs b/src/CodeBeam.UltimateAuth.Core/Infrastructure/UAuthUserIdConverter.cs similarity index 98% rename from src/CodeBeam.UltimateAuth.Core/Utilities/UAuthUserIdConverter.cs rename to src/CodeBeam.UltimateAuth.Core/Infrastructure/UAuthUserIdConverter.cs index a885a29..01085ff 100644 --- a/src/CodeBeam.UltimateAuth.Core/Utilities/UAuthUserIdConverter.cs +++ b/src/CodeBeam.UltimateAuth.Core/Infrastructure/UAuthUserIdConverter.cs @@ -4,7 +4,7 @@ using System.Text; using System.Text.Json; -namespace CodeBeam.UltimateAuth.Core.Utilities +namespace CodeBeam.UltimateAuth.Core.Infrastructure { /// /// Default implementation of that provides diff --git a/src/CodeBeam.UltimateAuth.Core/Utilities/UAuthUserIdConverterResolver.cs b/src/CodeBeam.UltimateAuth.Core/Infrastructure/UAuthUserIdConverterResolver.cs similarity index 97% rename from src/CodeBeam.UltimateAuth.Core/Utilities/UAuthUserIdConverterResolver.cs rename to src/CodeBeam.UltimateAuth.Core/Infrastructure/UAuthUserIdConverterResolver.cs index 8e2f079..8ac22a1 100644 --- a/src/CodeBeam.UltimateAuth.Core/Utilities/UAuthUserIdConverterResolver.cs +++ b/src/CodeBeam.UltimateAuth.Core/Infrastructure/UAuthUserIdConverterResolver.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Abstractions; using Microsoft.Extensions.DependencyInjection; -namespace CodeBeam.UltimateAuth.Core.Utilities +namespace CodeBeam.UltimateAuth.Core.Infrastructure { /// /// Resolves instances from the DI container. diff --git a/src/CodeBeam.UltimateAuth.Core/Users/UserIdFactory.cs b/src/CodeBeam.UltimateAuth.Core/Infrastructure/UserIdFactory.cs similarity index 82% rename from src/CodeBeam.UltimateAuth.Core/Users/UserIdFactory.cs rename to src/CodeBeam.UltimateAuth.Core/Infrastructure/UserIdFactory.cs index a134eb7..7a14b54 100644 --- a/src/CodeBeam.UltimateAuth.Core/Users/UserIdFactory.cs +++ b/src/CodeBeam.UltimateAuth.Core/Infrastructure/UserIdFactory.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Abstractions; using CodeBeam.UltimateAuth.Core.Domain; -namespace CodeBeam.UltimateAuth.Core.Utilities +namespace CodeBeam.UltimateAuth.Core.Infrastructure { public sealed class UserIdFactory : IUserIdFactory { diff --git a/src/CodeBeam.UltimateAuth.Core/Users/UserRecord.cs b/src/CodeBeam.UltimateAuth.Core/Infrastructure/UserRecord.cs similarity index 91% rename from src/CodeBeam.UltimateAuth.Core/Users/UserRecord.cs rename to src/CodeBeam.UltimateAuth.Core/Infrastructure/UserRecord.cs index 6789bdb..3f80324 100644 --- a/src/CodeBeam.UltimateAuth.Core/Users/UserRecord.cs +++ b/src/CodeBeam.UltimateAuth.Core/Infrastructure/UserRecord.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Domain; -namespace CodeBeam.UltimateAuth.Core.Users +namespace CodeBeam.UltimateAuth.Core.Infrastructure { public sealed class UserRecord { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/ReauthResult.cs b/src/CodeBeam.UltimateAuth.Core/Models/ReauthResult.cs deleted file mode 100644 index 97846c0..0000000 --- a/src/CodeBeam.UltimateAuth.Core/Models/ReauthResult.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CodeBeam.UltimateAuth.Core.Models -{ - public sealed record ReauthResult - { - public bool Success { get; init; } - } -} diff --git a/src/CodeBeam.UltimateAuth.Core/Enums/UAuthMode.cs b/src/CodeBeam.UltimateAuth.Core/Options/UAuthMode.cs similarity index 100% rename from src/CodeBeam.UltimateAuth.Core/Enums/UAuthMode.cs rename to src/CodeBeam.UltimateAuth.Core/Options/UAuthMode.cs diff --git a/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserService.cs b/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserService.cs deleted file mode 100644 index 41108e1..0000000 --- a/src/CodeBeam.UltimateAuth.Core/Users/Abstractions/IUAuthUserService.cs +++ /dev/null @@ -1,31 +0,0 @@ -using CodeBeam.UltimateAuth.Core.Domain; -using CodeBeam.UltimateAuth.Core.Models; - -namespace CodeBeam.UltimateAuth.Core.Abstractions -{ - /// - /// Minimal user operations required for authentication. - /// Does NOT include role or permission management. - /// For user management, CodeBeam.UltimateAuth.Users package is recommended. - /// - public interface IUAuthUserService - { - Task RegisterAsync( - RegisterUserRequest request, - CancellationToken cancellationToken = default); - - Task DeleteAsync( - TUserId userId, - CancellationToken cancellationToken = default); - - Task ValidateCredentialsAsync( - ValidateCredentialsRequest request, - CancellationToken cancellationToken = default); - - Task> AuthenticateAsync( - string? tenantId, - string identifier, - string secret, - CancellationToken cancellationToken = default); - } -} diff --git a/src/CodeBeam.UltimateAuth.Server/CodeBeam.UltimateAuth.Server.csproj b/src/CodeBeam.UltimateAuth.Server/CodeBeam.UltimateAuth.Server.csproj index 0032611..957c721 100644 --- a/src/CodeBeam.UltimateAuth.Server/CodeBeam.UltimateAuth.Server.csproj +++ b/src/CodeBeam.UltimateAuth.Server/CodeBeam.UltimateAuth.Server.csproj @@ -19,4 +19,8 @@ + + + + diff --git a/src/CodeBeam.UltimateAuth.Server/Endpoints/DefaultLoginEndpointHandler.cs b/src/CodeBeam.UltimateAuth.Server/Endpoints/DefaultLoginEndpointHandler.cs index 343ece2..cb63cef 100644 --- a/src/CodeBeam.UltimateAuth.Server/Endpoints/DefaultLoginEndpointHandler.cs +++ b/src/CodeBeam.UltimateAuth.Server/Endpoints/DefaultLoginEndpointHandler.cs @@ -1,6 +1,5 @@ -using CodeBeam.UltimateAuth.Core; -using CodeBeam.UltimateAuth.Core.Abstractions; -using CodeBeam.UltimateAuth.Core.Models; +using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Server.Abstractions; using CodeBeam.UltimateAuth.Server.Endpoints; using CodeBeam.UltimateAuth.Server.MultiTenancy; diff --git a/src/CodeBeam.UltimateAuth.Server/Extensions/HttpContextSessionExtensions.cs b/src/CodeBeam.UltimateAuth.Server/Extensions/HttpContextSessionExtensions.cs index 474f783..4d3063f 100644 --- a/src/CodeBeam.UltimateAuth.Server/Extensions/HttpContextSessionExtensions.cs +++ b/src/CodeBeam.UltimateAuth.Server/Extensions/HttpContextSessionExtensions.cs @@ -1,17 +1,14 @@ -using CodeBeam.UltimateAuth.Server.Middlewares; -using CodeBeam.UltimateAuth.Server.Sessions; +using CodeBeam.UltimateAuth.Core.Contracts; +using CodeBeam.UltimateAuth.Server.Middlewares; using Microsoft.AspNetCore.Http; namespace CodeBeam.UltimateAuth.Server.Extensions { public static class HttpContextSessionExtensions { - public static SessionContext GetSessionContext( - this HttpContext context) + public static SessionContext GetSessionContext(this HttpContext context) { - if (context.Items.TryGetValue( - SessionResolutionMiddleware.SessionContextKey, - out var value) + if (context.Items.TryGetValue(SessionResolutionMiddleware.SessionContextKey, out var value) && value is SessionContext session) { return session; diff --git a/src/CodeBeam.UltimateAuth.Server/Sessions/BearerSessionIdResolver.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/BearerSessionIdResolver.cs similarity index 92% rename from src/CodeBeam.UltimateAuth.Server/Sessions/BearerSessionIdResolver.cs rename to src/CodeBeam.UltimateAuth.Server/Infrastructure/BearerSessionIdResolver.cs index 49293e4..f2ffcec 100644 --- a/src/CodeBeam.UltimateAuth.Server/Sessions/BearerSessionIdResolver.cs +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/BearerSessionIdResolver.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using Microsoft.AspNetCore.Http; -namespace CodeBeam.UltimateAuth.Server.Sessions +namespace CodeBeam.UltimateAuth.Server.Infrastructure { public sealed class BearerSessionIdResolver : ISessionIdResolver { diff --git a/src/CodeBeam.UltimateAuth.Server/Sessions/CompositeSessionIdResolver.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/CompositeSessionIdResolver.cs similarity index 92% rename from src/CodeBeam.UltimateAuth.Server/Sessions/CompositeSessionIdResolver.cs rename to src/CodeBeam.UltimateAuth.Server/Infrastructure/CompositeSessionIdResolver.cs index b69111b..dd5c6f6 100644 --- a/src/CodeBeam.UltimateAuth.Server/Sessions/CompositeSessionIdResolver.cs +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/CompositeSessionIdResolver.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using Microsoft.AspNetCore.Http; -namespace CodeBeam.UltimateAuth.Server.Sessions +namespace CodeBeam.UltimateAuth.Server.Infrastructure { public sealed class CompositeSessionIdResolver : ISessionIdResolver { diff --git a/src/CodeBeam.UltimateAuth.Server/Sessions/CookieSessionIdResolver.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/CookieSessionIdResolver.cs similarity index 92% rename from src/CodeBeam.UltimateAuth.Server/Sessions/CookieSessionIdResolver.cs rename to src/CodeBeam.UltimateAuth.Server/Infrastructure/CookieSessionIdResolver.cs index cb33ac7..c7d533e 100644 --- a/src/CodeBeam.UltimateAuth.Server/Sessions/CookieSessionIdResolver.cs +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/CookieSessionIdResolver.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using Microsoft.AspNetCore.Http; -namespace CodeBeam.UltimateAuth.Server.Sessions +namespace CodeBeam.UltimateAuth.Server.Infrastructure { public sealed class CookieSessionIdResolver : ISessionIdResolver { diff --git a/src/CodeBeam.UltimateAuth.Server/Users/DefaultUserAuthenticator.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/DefaultUserAuthenticator.cs similarity index 93% rename from src/CodeBeam.UltimateAuth.Server/Users/DefaultUserAuthenticator.cs rename to src/CodeBeam.UltimateAuth.Server/Infrastructure/DefaultUserAuthenticator.cs index 63051a4..fbfdcfd 100644 --- a/src/CodeBeam.UltimateAuth.Server/Users/DefaultUserAuthenticator.cs +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/DefaultUserAuthenticator.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Abstractions; -using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Contracts; -namespace CodeBeam.UltimateAuth.Server.Users +namespace CodeBeam.UltimateAuth.Server.Infrastructure { public sealed class DefaultUserAuthenticator : IUserAuthenticator { diff --git a/src/CodeBeam.UltimateAuth.Server/Sessions/HeaderSessionIdResolver.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/HeaderSessionIdResolver.cs similarity index 92% rename from src/CodeBeam.UltimateAuth.Server/Sessions/HeaderSessionIdResolver.cs rename to src/CodeBeam.UltimateAuth.Server/Infrastructure/HeaderSessionIdResolver.cs index aad25f4..ca6521e 100644 --- a/src/CodeBeam.UltimateAuth.Server/Sessions/HeaderSessionIdResolver.cs +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/HeaderSessionIdResolver.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using Microsoft.AspNetCore.Http; -namespace CodeBeam.UltimateAuth.Server.Sessions +namespace CodeBeam.UltimateAuth.Server.Infrastructure { public sealed class HeaderSessionIdResolver : ISessionIdResolver { diff --git a/src/CodeBeam.UltimateAuth.Server/Sessions/ISessionIdResolver.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/ISessionIdResolver.cs similarity index 77% rename from src/CodeBeam.UltimateAuth.Server/Sessions/ISessionIdResolver.cs rename to src/CodeBeam.UltimateAuth.Server/Infrastructure/ISessionIdResolver.cs index bddb506..46c063b 100644 --- a/src/CodeBeam.UltimateAuth.Server/Sessions/ISessionIdResolver.cs +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/ISessionIdResolver.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using Microsoft.AspNetCore.Http; -namespace CodeBeam.UltimateAuth.Server.Sessions +namespace CodeBeam.UltimateAuth.Server.Infrastructure { public interface ISessionIdResolver { diff --git a/src/CodeBeam.UltimateAuth.Server/Sessions/ISessionOrchestrator.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/ISessionOrchestrator.cs similarity index 89% rename from src/CodeBeam.UltimateAuth.Server/Sessions/ISessionOrchestrator.cs rename to src/CodeBeam.UltimateAuth.Server/Infrastructure/ISessionOrchestrator.cs index 7520b67..b388f55 100644 --- a/src/CodeBeam.UltimateAuth.Server/Sessions/ISessionOrchestrator.cs +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/ISessionOrchestrator.cs @@ -1,8 +1,7 @@ -using CodeBeam.UltimateAuth.Core.Contexts; +using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; -using CodeBeam.UltimateAuth.Core.Models; -namespace CodeBeam.UltimateAuth.Server.Sessions +namespace CodeBeam.UltimateAuth.Server.Infrastructure { internal interface ISessionOrchestrator { diff --git a/src/CodeBeam.UltimateAuth.Server/Infrastructure/ITokenIssuer.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/ITokenIssuer.cs new file mode 100644 index 0000000..803bb4b --- /dev/null +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/ITokenIssuer.cs @@ -0,0 +1,14 @@ +using CodeBeam.UltimateAuth.Core.Contracts; + +namespace CodeBeam.UltimateAuth.Server.Infrastructure +{ + /// + /// Issues access and refresh tokens according to the active auth mode. + /// Does not perform persistence or validation. + /// + public interface ITokenIssuer + { + Task IssueAccessTokenAsync(TokenIssuanceContext context, CancellationToken cancellationToken = default); + Task IssueRefreshTokenAsync(TokenIssuanceContext context, CancellationToken cancellationToken = default); + } +} diff --git a/src/CodeBeam.UltimateAuth.Server/Users/IUserAccessor.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/IUserAccessor.cs similarity index 71% rename from src/CodeBeam.UltimateAuth.Server/Users/IUserAccessor.cs rename to src/CodeBeam.UltimateAuth.Server/Infrastructure/IUserAccessor.cs index 05a0e5a..87de8da 100644 --- a/src/CodeBeam.UltimateAuth.Server/Users/IUserAccessor.cs +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/IUserAccessor.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Http; -namespace CodeBeam.UltimateAuth.Server.Users +namespace CodeBeam.UltimateAuth.Server.Infrastructure { public interface IUserAccessor { diff --git a/src/CodeBeam.UltimateAuth.Server/Sessions/QuerySessionIdResolver.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/QuerySessionIdResolver.cs similarity index 92% rename from src/CodeBeam.UltimateAuth.Server/Sessions/QuerySessionIdResolver.cs rename to src/CodeBeam.UltimateAuth.Server/Infrastructure/QuerySessionIdResolver.cs index 3019d8c..237a9b1 100644 --- a/src/CodeBeam.UltimateAuth.Server/Sessions/QuerySessionIdResolver.cs +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/QuerySessionIdResolver.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using Microsoft.AspNetCore.Http; -namespace CodeBeam.UltimateAuth.Server.Sessions +namespace CodeBeam.UltimateAuth.Server.Infrastructure { public sealed class QuerySessionIdResolver : ISessionIdResolver { diff --git a/src/CodeBeam.UltimateAuth.Server/Utility/SystemClock.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/SystemClock.cs similarity index 75% rename from src/CodeBeam.UltimateAuth.Server/Utility/SystemClock.cs rename to src/CodeBeam.UltimateAuth.Server/Infrastructure/SystemClock.cs index 8dbf12c..5a3b064 100644 --- a/src/CodeBeam.UltimateAuth.Server/Utility/SystemClock.cs +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/SystemClock.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Abstractions; -namespace CodeBeam.UltimateAuth.Server.Utility +namespace CodeBeam.UltimateAuth.Server.Infrastructure { public sealed class SystemClock : IClock { diff --git a/src/CodeBeam.UltimateAuth.Core/Models/TokenIssuerContext.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/TokenIssuanceContext.cs similarity index 77% rename from src/CodeBeam.UltimateAuth.Core/Models/TokenIssuerContext.cs rename to src/CodeBeam.UltimateAuth.Server/Infrastructure/TokenIssuanceContext.cs index 320ef27..67e8ea6 100644 --- a/src/CodeBeam.UltimateAuth.Core/Models/TokenIssuerContext.cs +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/TokenIssuanceContext.cs @@ -1,8 +1,8 @@ using System.Security.Claims; -namespace CodeBeam.UltimateAuth.Core.Models +namespace CodeBeam.UltimateAuth.Server.Infrastructure { - public sealed record TokenIssuerContext + public sealed record TokenIssuanceContext { public string UserId { get; init; } = default!; public string? TenantId { get; init; } diff --git a/src/CodeBeam.UltimateAuth.Server/Utility/UAuthDeviceResolver.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/UAuthDeviceResolver.cs similarity index 96% rename from src/CodeBeam.UltimateAuth.Server/Utility/UAuthDeviceResolver.cs rename to src/CodeBeam.UltimateAuth.Server/Infrastructure/UAuthDeviceResolver.cs index 153a338..15ef29a 100644 --- a/src/CodeBeam.UltimateAuth.Server/Utility/UAuthDeviceResolver.cs +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/UAuthDeviceResolver.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Server.Abstractions; using Microsoft.AspNetCore.Http; -namespace CodeBeam.UltimateAuth.Server.Utility +namespace CodeBeam.UltimateAuth.Server.Infrastructure { public sealed class DefaultDeviceResolver : IDeviceResolver { diff --git a/src/CodeBeam.UltimateAuth.Server/Sessions/UAuthSessionIdResolver.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/UAuthSessionIdResolver.cs similarity index 96% rename from src/CodeBeam.UltimateAuth.Server/Sessions/UAuthSessionIdResolver.cs rename to src/CodeBeam.UltimateAuth.Server/Infrastructure/UAuthSessionIdResolver.cs index 75afe79..9219aaa 100644 --- a/src/CodeBeam.UltimateAuth.Server/Sessions/UAuthSessionIdResolver.cs +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/UAuthSessionIdResolver.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Server.Options; using Microsoft.Extensions.Options; -namespace CodeBeam.UltimateAuth.Server.Sessions +namespace CodeBeam.UltimateAuth.Server.Infrastructure { public sealed class UAuthSessionIdResolver : ISessionIdResolver { diff --git a/src/CodeBeam.UltimateAuth.Server/Sessions/UAuthSessionOrchestrator.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/UAuthSessionOrchestrator.cs similarity index 98% rename from src/CodeBeam.UltimateAuth.Server/Sessions/UAuthSessionOrchestrator.cs rename to src/CodeBeam.UltimateAuth.Server/Infrastructure/UAuthSessionOrchestrator.cs index d2ed195..7bcd741 100644 --- a/src/CodeBeam.UltimateAuth.Server/Sessions/UAuthSessionOrchestrator.cs +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/UAuthSessionOrchestrator.cs @@ -1,12 +1,11 @@ using CodeBeam.UltimateAuth.Core.Abstractions; -using CodeBeam.UltimateAuth.Core.Contexts; +using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.Errors; -using CodeBeam.UltimateAuth.Core.Models; using CodeBeam.UltimateAuth.Server.Issuers; using CodeBeam.UltimateAuth.Server.Options; -namespace CodeBeam.UltimateAuth.Server.Sessions +namespace CodeBeam.UltimateAuth.Server.Infrastructure { /// /// Default UltimateAuth session store implementation. diff --git a/src/CodeBeam.UltimateAuth.Server/Users/UAuthUserAccessor.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/UAuthUserAccessor.cs similarity index 95% rename from src/CodeBeam.UltimateAuth.Server/Users/UAuthUserAccessor.cs rename to src/CodeBeam.UltimateAuth.Server/Infrastructure/UAuthUserAccessor.cs index 9aa2c3c..d9c85bb 100644 --- a/src/CodeBeam.UltimateAuth.Server/Users/UAuthUserAccessor.cs +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/UAuthUserAccessor.cs @@ -1,10 +1,10 @@ using CodeBeam.UltimateAuth.Core.Abstractions; -using CodeBeam.UltimateAuth.Core.Contexts; +using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Server.Extensions; using CodeBeam.UltimateAuth.Server.Middlewares; using Microsoft.AspNetCore.Http; -namespace CodeBeam.UltimateAuth.Server.Users +namespace CodeBeam.UltimateAuth.Server.Infrastructure { public sealed class UAuthUserAccessor : IUserAccessor { diff --git a/src/CodeBeam.UltimateAuth.Server/Users/UAuthUserId.cs b/src/CodeBeam.UltimateAuth.Server/Infrastructure/UAuthUserId.cs similarity index 86% rename from src/CodeBeam.UltimateAuth.Server/Users/UAuthUserId.cs rename to src/CodeBeam.UltimateAuth.Server/Infrastructure/UAuthUserId.cs index eaaa711..d42d35b 100644 --- a/src/CodeBeam.UltimateAuth.Server/Users/UAuthUserId.cs +++ b/src/CodeBeam.UltimateAuth.Server/Infrastructure/UAuthUserId.cs @@ -1,4 +1,4 @@ -namespace CodeBeam.UltimateAuth.Server.Users +namespace CodeBeam.UltimateAuth.Server.Infrastructure { public readonly record struct UAuthUserId(Guid Value) { diff --git a/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthSessionIssuer.cs b/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthSessionIssuer.cs index c773c10..201d6b5 100644 --- a/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthSessionIssuer.cs +++ b/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthSessionIssuer.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core; using CodeBeam.UltimateAuth.Core.Abstractions; -using CodeBeam.UltimateAuth.Core.Contexts; +using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.Domain.Session; using CodeBeam.UltimateAuth.Server.Options; diff --git a/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthTokenIssuer.cs b/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthTokenIssuer.cs index 23e57dd..e965827 100644 --- a/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthTokenIssuer.cs +++ b/src/CodeBeam.UltimateAuth.Server/Issuers/UAuthTokenIssuer.cs @@ -1,8 +1,8 @@ using CodeBeam.UltimateAuth.Core; using CodeBeam.UltimateAuth.Core.Abstractions; -using CodeBeam.UltimateAuth.Core.Contexts; +using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; -using CodeBeam.UltimateAuth.Core.Models; +using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Server.Options; using Microsoft.Extensions.Options; using System.Security.Claims; @@ -29,7 +29,7 @@ public UAuthTokenIssuer(IOpaqueTokenGenerator opaqueGenerator, IJwtTokenGenerato _options = options.Value; } - public Task IssueAccessTokenAsync(TokenIssuerContext context, CancellationToken cancellationToken = default) + public Task IssueAccessTokenAsync(TokenIssuanceContext context, CancellationToken cancellationToken = default) { var now = DateTimeOffset.UtcNow; var expires = now.Add(_options.Tokens.AccessTokenLifetime); @@ -51,7 +51,7 @@ UAuthMode.SemiHybrid or }; } - public Task IssueRefreshTokenAsync(TokenIssuerContext context, CancellationToken cancellationToken = default) + public Task IssueRefreshTokenAsync(TokenIssuanceContext context, CancellationToken cancellationToken = default) { if (_options.Mode == UAuthMode.PureOpaque) return Task.FromResult(null); @@ -83,7 +83,7 @@ private AccessToken IssueOpaqueAccessToken(DateTimeOffset expires, string? sessi }; } - private AccessToken IssueJwtAccessToken(TokenIssuerContext context, DateTimeOffset expires) + private AccessToken IssueJwtAccessToken(TokenIssuanceContext context, DateTimeOffset expires) { var claims = new List { diff --git a/src/CodeBeam.UltimateAuth.Server/Middlewares/SessionResolutionMiddleware.cs b/src/CodeBeam.UltimateAuth.Server/Middlewares/SessionResolutionMiddleware.cs index 3ec6a3b..2f5380e 100644 --- a/src/CodeBeam.UltimateAuth.Server/Middlewares/SessionResolutionMiddleware.cs +++ b/src/CodeBeam.UltimateAuth.Server/Middlewares/SessionResolutionMiddleware.cs @@ -1,5 +1,6 @@ -using CodeBeam.UltimateAuth.Server.Extensions; -using CodeBeam.UltimateAuth.Server.Sessions; +using CodeBeam.UltimateAuth.Core.Contracts; +using CodeBeam.UltimateAuth.Server.Extensions; +using CodeBeam.UltimateAuth.Server.Infrastructure; using Microsoft.AspNetCore.Http; namespace CodeBeam.UltimateAuth.Server.Middlewares diff --git a/src/CodeBeam.UltimateAuth.Server/Middlewares/UserMiddleware.cs b/src/CodeBeam.UltimateAuth.Server/Middlewares/UserMiddleware.cs index aa87a46..079fbc8 100644 --- a/src/CodeBeam.UltimateAuth.Server/Middlewares/UserMiddleware.cs +++ b/src/CodeBeam.UltimateAuth.Server/Middlewares/UserMiddleware.cs @@ -1,4 +1,4 @@ -using CodeBeam.UltimateAuth.Server.Users; +using CodeBeam.UltimateAuth.Server.Infrastructure; using Microsoft.AspNetCore.Http; namespace CodeBeam.UltimateAuth.Server.Middlewares diff --git a/src/CodeBeam.UltimateAuth.Server/Services/UAuthFlowService.cs b/src/CodeBeam.UltimateAuth.Server/Services/UAuthFlowService.cs index 9035335..084e3b0 100644 --- a/src/CodeBeam.UltimateAuth.Server/Services/UAuthFlowService.cs +++ b/src/CodeBeam.UltimateAuth.Server/Services/UAuthFlowService.cs @@ -1,7 +1,6 @@ using CodeBeam.UltimateAuth.Core.Abstractions; -using CodeBeam.UltimateAuth.Core.Contexts; +using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; -using CodeBeam.UltimateAuth.Core.Models; namespace CodeBeam.UltimateAuth.Server.Services { diff --git a/src/CodeBeam.UltimateAuth.Server/Services/UAuthSessionService.cs b/src/CodeBeam.UltimateAuth.Server/Services/UAuthSessionService.cs index 708f650..43cdf35 100644 --- a/src/CodeBeam.UltimateAuth.Server/Services/UAuthSessionService.cs +++ b/src/CodeBeam.UltimateAuth.Server/Services/UAuthSessionService.cs @@ -1,8 +1,7 @@ using CodeBeam.UltimateAuth.Core.Abstractions; -using CodeBeam.UltimateAuth.Core.Contexts; +using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; -using CodeBeam.UltimateAuth.Core.Models; -using CodeBeam.UltimateAuth.Server.Sessions; +using CodeBeam.UltimateAuth.Server.Infrastructure; namespace CodeBeam.UltimateAuth.Server.Services { @@ -10,8 +9,7 @@ internal sealed class UAuthSessionService : IUAuthSessionService _orchestrator; - public UAuthSessionService( - ISessionOrchestrator orchestrator) + public UAuthSessionService(ISessionOrchestrator orchestrator) { _orchestrator = orchestrator; } diff --git a/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenService.cs b/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenService.cs index 42b95d6..6d35fbf 100644 --- a/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenService.cs +++ b/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenService.cs @@ -1,8 +1,7 @@ -using CodeBeam.UltimateAuth.Core; -using CodeBeam.UltimateAuth.Core.Abstractions; -using CodeBeam.UltimateAuth.Core.Contexts; -using CodeBeam.UltimateAuth.Core.Models; +using CodeBeam.UltimateAuth.Core.Abstractions; +using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Server.Extensions; +using CodeBeam.UltimateAuth.Server.Infrastructure; namespace CodeBeam.UltimateAuth.Server.Services { @@ -48,9 +47,9 @@ public async Task> ValidateAsync( CancellationToken ct = default) => await _validator.ValidateAsync(token, type, ct); - private TokenIssuerContext ToIssuerContext(TokenIssueContext src) + private TokenIssuanceContext ToIssuerContext(TokenIssueContext src) { - return new TokenIssuerContext + return new TokenIssuanceContext { UserId = _userIdConverter.ToString(src.Session.UserId), TenantId = src.TenantId, diff --git a/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenValidator.cs b/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenValidator.cs index d6414b2..d48cba0 100644 --- a/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenValidator.cs +++ b/src/CodeBeam.UltimateAuth.Server/Services/UAuthTokenValidator.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core; using CodeBeam.UltimateAuth.Core.Abstractions; using CodeBeam.UltimateAuth.Core.Domain; -using CodeBeam.UltimateAuth.Core.Models; +using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Server.Options; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.JsonWebTokens; diff --git a/src/CodeBeam.UltimateAuth.Server/Services/UAuthUserService.cs b/src/CodeBeam.UltimateAuth.Server/Services/UAuthUserService.cs index 040c99a..bb02e0b 100644 --- a/src/CodeBeam.UltimateAuth.Server/Services/UAuthUserService.cs +++ b/src/CodeBeam.UltimateAuth.Server/Services/UAuthUserService.cs @@ -1,7 +1,6 @@ using CodeBeam.UltimateAuth.Core.Abstractions; -using CodeBeam.UltimateAuth.Core.Domain; -using CodeBeam.UltimateAuth.Core.Models; -using CodeBeam.UltimateAuth.Core.Users; +using CodeBeam.UltimateAuth.Core.Contracts; +using CodeBeam.UltimateAuth.Core.Infrastructure; namespace CodeBeam.UltimateAuth.Server.Users; diff --git a/src/CodeBeam.UltimateAuth.Server/Users/AspNetIdentityUserStore.cs b/src/CodeBeam.UltimateAuth.Server/Stores/AspNetIdentityUserStore.cs similarity index 96% rename from src/CodeBeam.UltimateAuth.Server/Users/AspNetIdentityUserStore.cs rename to src/CodeBeam.UltimateAuth.Server/Stores/AspNetIdentityUserStore.cs index 071142d..ac2fc6f 100644 --- a/src/CodeBeam.UltimateAuth.Server/Users/AspNetIdentityUserStore.cs +++ b/src/CodeBeam.UltimateAuth.Server/Stores/AspNetIdentityUserStore.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using Microsoft.AspNetCore.Identity; -namespace CodeBeam.UltimateAuth.Server.Users +namespace CodeBeam.UltimateAuth.Server.Stores { public sealed class AspNetIdentityUserStore // : IUAuthUserStore {