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
2 changes: 1 addition & 1 deletion UltimateAuth.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<File Path="Readme.md" />
<File Path="Roadmap.md" />
</Folder>
<Project Path="src/CodeBeam.UltimateAuth.AspNetCore/CodeBeam.UltimateAuth.Server.Users.csproj" Id="30d5db36-6dc8-46f6-9139-8b6b3d6053d5" />
<Project Path="src/CodeBeam.UltimateAuth.Users/CodeBeam.UltimateAuth.Server.Users.csproj" Id="30d5db36-6dc8-46f6-9139-8b6b3d6053d5" />
<Project Path="src/CodeBeam.UltimateAuth.Client/CodeBeam.UltimateAuth.Client.csproj" Id="eb60a3b7-ba9d-48c9-98ad-b28e879b23bf" />
<Project Path="src/CodeBeam.UltimateAuth.Core/CodeBeam.UltimateAuth.Core.csproj" />
<Project Path="src/CodeBeam.UltimateAuth.Server/CodeBeam.UltimateAuth.Server.csproj" Id="0a8cdd12-a8c4-4530-87e8-ae778c46322b" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using CodeBeam.UltimateAuth.Core.Contracts;

namespace CodeBeam.UltimateAuth.Core.Abstractions
{
public interface IAuthAuthority
{
AuthorizationResult Decide(AuthContext context);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using CodeBeam.UltimateAuth.Core.Contracts;

namespace CodeBeam.UltimateAuth.Core.Abstractions
{
public interface IAuthorityInvariant
{
AuthorizationResult Decide(AuthContext context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using CodeBeam.UltimateAuth.Core.Contracts;

namespace CodeBeam.UltimateAuth.Core.Abstractions
{
public interface IAuthorityPolicy
{
bool AppliesTo(AuthContext context);
AuthorizationResult Decide(AuthContext context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
/// </summary>
public interface IClock
{
DateTime UtcNow { get; }
DateTimeOffset UtcNow { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using CodeBeam.UltimateAuth.Core.Contracts;

namespace CodeBeam.UltimateAuth.Core.Abstractions
{
public interface IRefreshTokenResolver<TUserId>
{
Task<ResolvedRefreshSession<TUserId>?> ResolveAsync(string? tenantId, string refreshToken, DateTimeOffset now, CancellationToken ct = default);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@

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

Task<IssuedSession<TUserId>> RotateSessionAsync(SessionRotationContext<TUserId> context, CancellationToken cancellationToken = default);

Task RevokeSessionAsync(string? tenantId, AuthSessionId sessionId, DateTimeOffset at, CancellationToken cancellationToken = default);

Task RevokeChainAsync(string? tenantId, ChainId chainId, DateTimeOffset at, CancellationToken cancellationToken = default);

Task RevokeAllChainsAsync(string? tenantId, TUserId userId, ChainId? exceptChainId, DateTimeOffset at, CancellationToken ct = default);

Task RevokeRootAsync(string? tenantId, TUserId userId, DateTimeOffset at,CancellationToken ct = default);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,24 @@ namespace CodeBeam.UltimateAuth.Core.Abstractions
/// <typeparam name="TUserId">The type used to uniquely identify the user.</typeparam>
public interface IUAuthSessionService<TUserId>
{
Task<SessionValidationResult<TUserId>> ValidateSessionAsync(string? tenantId, AuthSessionId sessionId, DateTime at);
Task<SessionValidationResult<TUserId>> ValidateSessionAsync(string? tenantId, AuthSessionId sessionId, DateTimeOffset at);

Task<IReadOnlyList<ISessionChain<TUserId>>> GetChainsAsync(string? tenantId, TUserId userId);

Task<IReadOnlyList<ISession<TUserId>>> GetSessionsAsync(string? tenantId, ChainId chainId);

Task<ISession<TUserId>?> GetCurrentSessionAsync(string? tenantId, AuthSessionId sessionId);

Task RevokeSessionAsync(string? tenantId, AuthSessionId sessionId, DateTime at);
Task RevokeSessionAsync(string? tenantId, AuthSessionId sessionId, DateTimeOffset at);

Task RevokeChainAsync(string? tenantId, ChainId chainId, DateTime at);
Task RevokeChainAsync(string? tenantId, ChainId chainId, DateTimeOffset at);

Task<ChainId?> ResolveChainIdAsync(string? tenantId, AuthSessionId sessionId);

Task RevokeAllChainsAsync(string? tenantId, TUserId userId, ChainId? exceptChainId, DateTime at);
Task RevokeAllChainsAsync(string? tenantId, TUserId userId, ChainId? exceptChainId, DateTimeOffset at);

// Hard revoke - admin
Task RevokeRootAsync(string? tenantId, TUserId userId, DateTime at);
Task RevokeRootAsync(string? tenantId, TUserId userId, DateTimeOffset at);

Task<IssuedSession<TUserId>> IssueSessionAfterAuthenticationAsync(string? tenantId, AuthenticatedSessionContext<TUserId> context, CancellationToken cancellationToken = default);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,22 @@ Task RotateSessionAsync(
Task RevokeSessionAsync(
string? tenantId,
AuthSessionId sessionId,
DateTime at);
DateTimeOffset at);

/// <summary>
/// Revokes all sessions for a specific user (all devices).
/// </summary>
Task RevokeAllSessionsAsync(
string? tenantId,
TUserId userId,
DateTime at);
DateTimeOffset at);

/// <summary>
/// Revokes all sessions within a specific chain (single device).
/// </summary>
Task RevokeChainAsync(
string? tenantId,
ChainId chainId,
DateTime at);
DateTimeOffset at);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ namespace CodeBeam.UltimateAuth.Core.Abstractions
/// </summary>
public interface ISessionStoreKernel<TUserId>
{
/// <summary>
/// Executes multiple store operations as a single atomic unit.
/// Implementations must ensure transactional consistency where supported.
/// </summary>
Task ExecuteAsync(Func<Task> action);

/// <summary>
/// Retrieves a session by its identifier within the given tenant context.
/// </summary>
Expand All @@ -31,7 +37,7 @@ public interface ISessionStoreKernel<TUserId>
/// <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);
Task RevokeSessionAsync(string? tenantId, AuthSessionId sessionId, DateTimeOffset at);

/// <summary>
/// Returns all sessions belonging to the specified chain, ordered according to store implementation rules.
Expand Down Expand Up @@ -62,7 +68,7 @@ public interface ISessionStoreKernel<TUserId>
/// <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);
Task RevokeChainAsync(string? tenantId, ChainId chainId, DateTimeOffset at);

/// <summary>
/// Retrieves the active session identifier for the specified chain.
Expand Down Expand Up @@ -112,14 +118,14 @@ public interface ISessionStoreKernel<TUserId>
/// <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);
Task RevokeSessionRootAsync(string? tenantId, TUserId userId, DateTimeOffset 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);
Task DeleteExpiredSessionsAsync(string? tenantId, DateTimeOffset at);

/// <summary>
/// Retrieves the chain identifier associated with the specified session.
Expand All @@ -128,11 +134,5 @@ public interface ISessionStoreKernel<TUserId>
/// <param name="sessionId">The session identifier.</param>
/// <returns>The chain identifier or <c>null</c>.</returns>
Task<ChainId?> GetChainIdBySessionAsync(string? tenantId, AuthSessionId sessionId);

/// <summary>
/// Executes multiple store operations as a single atomic unit.
/// Implementations must ensure transactional consistency where supported.
/// </summary>
Task ExecuteAsync(Func<Task> action);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CodeBeam.UltimateAuth.Core.Domain;
using CodeBeam.UltimateAuth.Core.Contracts;
using CodeBeam.UltimateAuth.Core.Domain;

namespace CodeBeam.UltimateAuth.Core.Abstractions
{
Expand All @@ -22,11 +23,11 @@ Task StoreRefreshTokenAsync(
/// Validates a provided refresh token against the stored hash.
/// Returns true if valid and not expired or revoked.
/// </summary>
Task<bool> ValidateRefreshTokenAsync(
Task<RefreshTokenValidationResult<TUserId>> ValidateRefreshTokenAsync(
string? tenantId,
TUserId userId,
AuthSessionId sessionId,
string providedRefreshToken);
string providedRefreshToken,
DateTimeOffset now);


/// <summary>
/// Revokes the refresh token associated with the specified session.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace CodeBeam.UltimateAuth.Core.Abstractions
{
public interface ITokenStoreFactory
{
ITokenStoreKernel Create(string? tenantId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using CodeBeam.UltimateAuth.Core.Domain;

namespace CodeBeam.UltimateAuth.Core.Abstractions
{
/// <summary>
/// Low-level persistence abstraction for token-related data.
/// Handles refresh tokens and optional access token identifiers (jti).
/// </summary>
public interface ITokenStoreKernel
{
Task SaveRefreshTokenAsync(
string? tenantId,
StoredRefreshToken token);

Task<StoredRefreshToken?> GetRefreshTokenAsync(
string? tenantId,
string tokenHash);

Task RevokeRefreshTokenAsync(
string? tenantId,
string tokenHash,
DateTimeOffset at);

Task RevokeAllRefreshTokensAsync(
string? tenantId,
string? userId,
DateTimeOffset at);

Task DeleteExpiredRefreshTokensAsync(
string? tenantId,
DateTimeOffset now);

Task StoreTokenIdAsync(
string? tenantId,
string jti,
DateTimeOffset expiresAt);

Task<bool> IsTokenIdRevokedAsync(
string? tenantId,
string jti);

Task RevokeTokenIdAsync(
string? tenantId,
string jti,
DateTimeOffset at);
}
}
61 changes: 61 additions & 0 deletions src/CodeBeam.UltimateAuth.Core/Contracts/Authority/AuthContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
namespace CodeBeam.UltimateAuth.Core.Contracts
{
public sealed record AuthContext
{
public string? TenantId { get; init; }

public AuthOperation Operation { get; init; }

public UAuthMode Mode { get; init; }

public SessionAccessContext? Session { get; init; }

public DeviceContext Device { get; init; }

public DateTimeOffset At { get; init; }

private AuthContext() { }

public static AuthContext System(string? tenantId, AuthOperation operation, DateTimeOffset at, UAuthMode mode = UAuthMode.Hybrid)
{
return new AuthContext
{
TenantId = tenantId,
Operation = operation,
Mode = mode,
At = at,
Session = null,
Device = null
};
}

public static AuthContext ForAuthenticatedUser(string? tenantId, AuthOperation operation, DateTimeOffset at, DeviceContext device, UAuthMode mode = UAuthMode.Hybrid)
{
return new AuthContext
{
TenantId = tenantId,
Operation = operation,
Mode = mode,
At = at,
Device = device,
Session = null
};
}

public static AuthContext ForSession(string? tenantId, AuthOperation operation, SessionAccessContext session, DateTimeOffset at,
DeviceContext device, UAuthMode mode = UAuthMode.Hybrid)
{
return new AuthContext
{
TenantId = tenantId,
Operation = operation,
Mode = mode,
At = at,
Session = session,
Device = device
};
}


}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace CodeBeam.UltimateAuth.Core.Contracts
{
public enum AuthOperation
{
Login,
Access,
Refresh,
Revoke,
Logout,
System
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace CodeBeam.UltimateAuth.Core.Contracts
{
public enum AuthorizationDecision
{
Allow,
Deny,
Challenge
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace CodeBeam.UltimateAuth.Core.Contracts
{
public sealed class AuthorizationResult
{
public AuthorizationDecision Decision { get; }
public string? Reason { get; }

private AuthorizationResult(AuthorizationDecision decision, string? reason)
{
Decision = decision;
Reason = reason;
}

public static AuthorizationResult Allow()
=> new(AuthorizationDecision.Allow, null);

public static AuthorizationResult Deny(string reason)
=> new(AuthorizationDecision.Deny, reason);

public static AuthorizationResult Challenge(string reason)
=> new(AuthorizationDecision.Challenge, reason);

// Developer happiness helpers
public bool IsAllowed => Decision == AuthorizationDecision.Allow;
public bool IsDenied => Decision == AuthorizationDecision.Deny;
public bool RequiresChallenge => Decision == AuthorizationDecision.Challenge;
}

}
Loading