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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using CodeBeam.UltimateAuth.Core.Domain;

namespace CodeBeam.UltimateAuth.Core.Abstractions
{
/// <summary>
/// Low-level JWT creation abstraction.
/// Can be replaced for asymmetric keys, external KMS, etc.
/// </summary>
public interface IJwtTokenGenerator
{
string CreateToken(UAuthJwtTokenDescriptor descriptor);

Check warning on line 11 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/IJwtTokenGenerator.cs

View workflow job for this annotation

GitHub Actions / Build & Test (10.0.x)

Missing XML comment for publicly visible type or member 'IJwtTokenGenerator.CreateToken(UAuthJwtTokenDescriptor)'

Check warning on line 11 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/IJwtTokenGenerator.cs

View workflow job for this annotation

GitHub Actions / Build & Test (9.0.x)

Missing XML comment for publicly visible type or member 'IJwtTokenGenerator.CreateToken(UAuthJwtTokenDescriptor)'

Check warning on line 11 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/IJwtTokenGenerator.cs

View workflow job for this annotation

GitHub Actions / Build & Test (8.0.x)

Missing XML comment for publicly visible type or member 'IJwtTokenGenerator.CreateToken(UAuthJwtTokenDescriptor)'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace CodeBeam.UltimateAuth.Core.Abstractions
{
/// <summary>
/// Generates cryptographically secure random tokens
/// for opaque identifiers, refresh tokens, session ids.
/// </summary>
public interface IOpaqueTokenGenerator
{
string Generate(int byteLength = 32);

Check warning on line 9 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/IOpaqueTokenGenerator.cs

View workflow job for this annotation

GitHub Actions / Build & Test (10.0.x)

Missing XML comment for publicly visible type or member 'IOpaqueTokenGenerator.Generate(int)'

Check warning on line 9 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/IOpaqueTokenGenerator.cs

View workflow job for this annotation

GitHub Actions / Build & Test (9.0.x)

Missing XML comment for publicly visible type or member 'IOpaqueTokenGenerator.Generate(int)'

Check warning on line 9 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/IOpaqueTokenGenerator.cs

View workflow job for this annotation

GitHub Actions / Build & Test (8.0.x)

Missing XML comment for publicly visible type or member 'IOpaqueTokenGenerator.Generate(int)'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using CodeBeam.UltimateAuth.Core.Contexts;
using CodeBeam.UltimateAuth.Core.Domain;

namespace CodeBeam.UltimateAuth.Core.Abstractions
{
/// <summary>
/// Issues and manages authentication sessions.
/// </summary>
public interface ISessionIssuer<TUserId>
{
Task<IssuedSession<TUserId>> IssueAsync(SessionIssueContext<TUserId> context, UAuthSessionChain<TUserId> chain, CancellationToken cancellationToken = default);

Check warning on line 11 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ISessionIssuer.cs

View workflow job for this annotation

GitHub Actions / Build & Test (10.0.x)

Missing XML comment for publicly visible type or member 'ISessionIssuer<TUserId>.IssueAsync(SessionIssueContext<TUserId>, UAuthSessionChain<TUserId>, CancellationToken)'

Check warning on line 11 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ISessionIssuer.cs

View workflow job for this annotation

GitHub Actions / Build & Test (9.0.x)

Missing XML comment for publicly visible type or member 'ISessionIssuer<TUserId>.IssueAsync(SessionIssueContext<TUserId>, UAuthSessionChain<TUserId>, CancellationToken)'

Check warning on line 11 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ISessionIssuer.cs

View workflow job for this annotation

GitHub Actions / Build & Test (8.0.x)

Missing XML comment for publicly visible type or member 'ISessionIssuer<TUserId>.IssueAsync(SessionIssueContext<TUserId>, UAuthSessionChain<TUserId>, CancellationToken)'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace CodeBeam.UltimateAuth.Core.Abstractions
{
/// <summary>
/// Hashes and verifies sensitive tokens.
/// Used for refresh tokens, session ids, opaque tokens.
/// </summary>
public interface ITokenHasher
{
string Hash(string plaintext);

Check warning on line 9 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenHasher.cs

View workflow job for this annotation

GitHub Actions / Build & Test (10.0.x)

Missing XML comment for publicly visible type or member 'ITokenHasher.Hash(string)'

Check warning on line 9 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenHasher.cs

View workflow job for this annotation

GitHub Actions / Build & Test (9.0.x)

Missing XML comment for publicly visible type or member 'ITokenHasher.Hash(string)'

Check warning on line 9 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenHasher.cs

View workflow job for this annotation

GitHub Actions / Build & Test (8.0.x)

Missing XML comment for publicly visible type or member 'ITokenHasher.Hash(string)'
bool Verify(string plaintext, string hash);

Check warning on line 10 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenHasher.cs

View workflow job for this annotation

GitHub Actions / Build & Test (10.0.x)

Missing XML comment for publicly visible type or member 'ITokenHasher.Verify(string, string)'

Check warning on line 10 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenHasher.cs

View workflow job for this annotation

GitHub Actions / Build & Test (9.0.x)

Missing XML comment for publicly visible type or member 'ITokenHasher.Verify(string, string)'

Check warning on line 10 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenHasher.cs

View workflow job for this annotation

GitHub Actions / Build & Test (8.0.x)

Missing XML comment for publicly visible type or member 'ITokenHasher.Verify(string, string)'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using CodeBeam.UltimateAuth.Core.Contexts;

namespace CodeBeam.UltimateAuth.Core.Abstractions
{
/// <summary>
/// Issues access and refresh tokens according to the active auth mode.
/// Does not perform persistence or validation.
/// </summary>
public interface ITokenIssuer
{
Task<IssuedAccessToken> IssueAccessTokenAsync(TokenIssueContext context, CancellationToken cancellationToken = default);

Check warning on line 11 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenIssuer.cs

View workflow job for this annotation

GitHub Actions / Build & Test (10.0.x)

Missing XML comment for publicly visible type or member 'ITokenIssuer.IssueAccessTokenAsync(TokenIssueContext, CancellationToken)'

Check warning on line 11 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenIssuer.cs

View workflow job for this annotation

GitHub Actions / Build & Test (9.0.x)

Missing XML comment for publicly visible type or member 'ITokenIssuer.IssueAccessTokenAsync(TokenIssueContext, CancellationToken)'

Check warning on line 11 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenIssuer.cs

View workflow job for this annotation

GitHub Actions / Build & Test (8.0.x)

Missing XML comment for publicly visible type or member 'ITokenIssuer.IssueAccessTokenAsync(TokenIssueContext, CancellationToken)'
Task<IssuedRefreshToken?> IssueRefreshTokenAsync(TokenIssueContext context, CancellationToken cancellationToken = default);

Check warning on line 12 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenIssuer.cs

View workflow job for this annotation

GitHub Actions / Build & Test (10.0.x)

Missing XML comment for publicly visible type or member 'ITokenIssuer.IssueRefreshTokenAsync(TokenIssueContext, CancellationToken)'

Check warning on line 12 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenIssuer.cs

View workflow job for this annotation

GitHub Actions / Build & Test (9.0.x)

Missing XML comment for publicly visible type or member 'ITokenIssuer.IssueRefreshTokenAsync(TokenIssueContext, CancellationToken)'

Check warning on line 12 in src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ITokenIssuer.cs

View workflow job for this annotation

GitHub Actions / Build & Test (8.0.x)

Missing XML comment for publicly visible type or member 'ITokenIssuer.IssueRefreshTokenAsync(TokenIssueContext, CancellationToken)'
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ public sealed class DefaultSessionStoreFactory : ISessionStoreFactory
/// <typeparam name="TUserId">The type used to uniquely identify the user.</typeparam>
/// <returns>Never returns; always throws.</returns>
/// <exception cref="InvalidOperationException">Thrown when no session store implementation has been configured.</exception>
public ISessionStore<TUserId> Create<TUserId>(string? tenantId)
public ISessionStoreKernel<TUserId> Create<TUserId>(string? tenantId)
{
throw new InvalidOperationException(
"No ISessionStore<TUserId> implementation registered. " +
"Call AddUltimateAuthServer().AddSessionStore<TStore>() to provide a real implementation."
"No session store has been configured." +
"Call AddUltimateAuthServer().AddSessionStore(...) to register one."
);
}
}
Expand Down
146 changes: 32 additions & 114 deletions src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/ISessionStore.cs
Original file line number Diff line number Diff line change
@@ -1,140 +1,58 @@
using CodeBeam.UltimateAuth.Core.Domain;
using CodeBeam.UltimateAuth.Core.Contexts;
using CodeBeam.UltimateAuth.Core.Domain;

namespace CodeBeam.UltimateAuth.Core.Abstractions
{
/// <summary>
/// Defines the low-level persistence operations for sessions, session chains, and session roots in a multi-tenant or single-tenant environment.
/// Store implementations provide durable and atomic data access.
/// High-level session store abstraction used by UltimateAuth.
/// Encapsulates session, chain, and root orchestration.
/// </summary>
public interface ISessionStore<TUserId>
{
/// <summary>
/// Retrieves a session by its identifier within the given tenant context.
/// Retrieves an active session by id.
/// </summary>
/// <param name="tenantId">The tenant identifier, or <c>null</c> for single-tenant mode.</param>
/// <param name="sessionId">The session identifier.</param>
/// <returns>The session instance or <c>null</c> if not found.</returns>
Task<ISession<TUserId>?> GetSessionAsync(string? tenantId, AuthSessionId sessionId);
Task<ISession<TUserId>?> GetSessionAsync(
string? tenantId,
AuthSessionId sessionId);

/// <summary>
/// Persists a new session or updates an existing one within the tenant scope.
/// Implementations must ensure atomic writes.
/// Creates a new session and associates it with the appropriate chain and root.
/// </summary>
/// <param name="tenantId">The tenant identifier, or <c>null</c>.</param>
/// <param name="session">The session to persist.</param>
Task SaveSessionAsync(string? tenantId, ISession<TUserId> session);
Task CreateSessionAsync(
IssuedSession<TUserId> issuedSession,
SessionStoreContext<TUserId> context);

/// <summary>
/// Marks the specified session as revoked, preventing future authentication.
/// Revocation timestamp must be stored reliably.
/// Refreshes (rotates) the active session within its chain.
/// </summary>
/// <param name="tenantId">The tenant identifier, or <c>null</c>.</param>
/// <param name="sessionId">The session identifier.</param>
/// <param name="at">The UTC timestamp of revocation.</param>
Task RevokeSessionAsync(string? tenantId, AuthSessionId sessionId, DateTime at);

/// <summary>
/// Returns all sessions belonging to the specified chain, ordered according to store implementation rules.
/// </summary>
/// <param name="tenantId">The tenant identifier, or <c>null</c>.</param>
/// <param name="chainId">The chain identifier.</param>
/// <returns>A read-only list of sessions.</returns>
Task<IReadOnlyList<ISession<TUserId>>> GetSessionsByChainAsync(string? tenantId, ChainId chainId);

/// <summary>
/// Retrieves a session chain by identifier. Returns <c>null</c> if the chain does not exist in the provided tenant context.
/// </summary>
/// <param name="tenantId">The tenant identifier, or <c>null</c>.</param>
/// <param name="chainId">The chain identifier.</param>
/// <returns>The chain or <c>null</c>.</returns>
Task<ISessionChain<TUserId>?> GetChainAsync(string? tenantId, ChainId chainId);

/// <summary>
/// Inserts a new session chain into the store. Implementations must ensure consistency with the related sessions and session root.
/// </summary>
/// <param name="tenantId">The tenant identifier, or <c>null</c>.</param>
/// <param name="chain">The chain to save.</param>
Task SaveChainAsync(string? tenantId, ISessionChain<TUserId> chain);

/// <summary>
/// Updates an existing session chain, typically after session rotation or revocation. Implementations must preserve atomicity.
/// </summary>
/// <param name="tenantId">The tenant identifier, or <c>null</c>.</param>
/// <param name="chain">The updated session chain.</param>
Task UpdateChainAsync(string? tenantId, ISessionChain<TUserId> chain);

/// <summary>
/// Marks the entire session chain as revoked, invalidating all associated sessions for the device or app family.
/// </summary>
/// <param name="tenantId">The tenant identifier, or <c>null</c>.</param>
/// <param name="chainId">The chain to revoke.</param>
/// <param name="at">The UTC timestamp of revocation.</param>
Task RevokeChainAsync(string? tenantId, ChainId chainId, DateTime at);

/// <summary>
/// Retrieves the active session identifier for the specified chain.
/// This is typically an O(1) lookup and used for session rotation.
/// </summary>
/// <param name="tenantId">The tenant identifier, or <c>null</c>.</param>
/// <param name="chainId">The chain whose active session is requested.</param>
/// <returns>The active session identifier or <c>null</c>.</returns>
Task<AuthSessionId?> GetActiveSessionIdAsync(string? tenantId, ChainId chainId);

/// <summary>
/// Sets or replaces the active session identifier for the specified chain.
/// Must be atomic to prevent race conditions during refresh.
/// </summary>
/// <param name="tenantId">The tenant identifier, or <c>null</c>.</param>
/// <param name="chainId">The chain whose active session is being set.</param>
/// <param name="sessionId">The new active session identifier.</param>
Task SetActiveSessionIdAsync(string? tenantId, ChainId chainId, AuthSessionId sessionId);

/// <summary>
/// Retrieves all session chains belonging to the specified user within the tenant scope.
/// </summary>
/// <param name="tenantId">The tenant identifier, or <c>null</c>.</param>
/// <param name="userId">The user whose chains are being retrieved.</param>
/// <returns>A read-only list of session chains.</returns>
Task<IReadOnlyList<ISessionChain<TUserId>>> GetChainsByUserAsync(string? tenantId, TUserId userId);
Task RotateSessionAsync(
AuthSessionId currentSessionId,
IssuedSession<TUserId> newSession,
SessionStoreContext<TUserId> context);

/// <summary>
/// Retrieves the session root for the user, which represents the full set of chains and their associated security metadata.
/// Returns <c>null</c> if the root does not exist.
/// Revokes a single session.
/// </summary>
/// <param name="tenantId">The tenant identifier, or <c>null</c>.</param>
/// <param name="userId">The user identifier.</param>
/// <returns>The session root or <c>null</c>.</returns>
Task<ISessionRoot<TUserId>?> GetSessionRootAsync(string? tenantId, TUserId userId);
Task RevokeSessionAsync(
string? tenantId,
AuthSessionId sessionId,
DateTime at);

/// <summary>
/// Persists a session root structure, usually after chain creation, rotation, or security operations.
/// Revokes all sessions for a specific user (all devices).
/// </summary>
/// <param name="tenantId">The tenant identifier, or <c>null</c>.</param>
/// <param name="root">The session root to save.</param>
Task SaveSessionRootAsync(string? tenantId, ISessionRoot<TUserId> root);
Task RevokeAllSessionsAsync(
string? tenantId,
TUserId userId,
DateTime at);

/// <summary>
/// Revokes the session root, invalidating all chains and sessions belonging to the specified user in the tenant scope.
/// Revokes all sessions within a specific chain (single device).
/// </summary>
/// <param name="tenantId">The tenant identifier, or <c>null</c>.</param>
/// <param name="userId">The user whose root should be revoked.</param>
/// <param name="at">The UTC timestamp of revocation.</param>
Task RevokeSessionRootAsync(string? tenantId, TUserId userId, DateTime at);

/// <summary>
/// Removes expired sessions from the store while leaving chains and session roots intact. Cleanup strategy is determined by the store implementation.
/// </summary>
/// <param name="tenantId">The tenant identifier, or <c>null</c>.</param>
/// <param name="now">The current UTC timestamp.</param>
Task DeleteExpiredSessionsAsync(string? tenantId, DateTime now);

/// <summary>
/// Retrieves the chain identifier associated with the specified session.
/// </summary>
/// <param name="tenantId">The tenant identifier, or <c>null</c>.</param>
/// <param name="sessionId">The session identifier.</param>
/// <returns>The chain identifier or <c>null</c>.</returns>
Task<ChainId?> GetChainIdBySessionAsync(string? tenantId, AuthSessionId sessionId);
Task RevokeChainAsync(
string? tenantId,
ChainId chainId,
DateTime at);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/// <summary>
/// Provides a factory abstraction for creating tenant-scoped session store
/// instances capable of persisting sessions, chains, and session roots.
/// Implementations typically resolve concrete <see cref="ISessionStore{TUserId}"/> types from the dependency injection container.
/// Implementations typically resolve concrete <see cref="ISessionStoreKernel{TUserId}"/> types from the dependency injection container.
/// </summary>
public interface ISessionStoreFactory
{
Expand All @@ -15,11 +15,11 @@ public interface ISessionStoreFactory
/// The tenant identifier for multi-tenant environments, or <c>null</c> for single-tenant mode.
/// </param>
/// <returns>
/// An <see cref="ISessionStore{TUserId}"/> implementation able to perform session persistence operations.
/// An <see cref="ISessionStoreKernel{TUserId}"/> implementation able to perform session persistence operations.
/// </returns>
/// <exception cref="InvalidOperationException">
/// Thrown if no compatible session store implementation is registered.
/// </exception>
ISessionStore<TUserId> Create<TUserId>(string? tenantId);
ISessionStoreKernel<TUserId> Create<TUserId>(string? tenantId);
}
}
Loading