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
@@ -1,5 +1,6 @@
using CodeBeam.UltimateAuth.Core.Domain;
using CodeBeam.UltimateAuth.Core.MultiTenancy;
using CodeBeam.UltimateAuth.Core.Options;

namespace CodeBeam.UltimateAuth.Core.Contracts;

Expand All @@ -16,6 +17,7 @@ public sealed class SessionIssuanceContext
public ClaimsSnapshot? Claims { get; init; }
public required SessionMetadata Metadata { get; init; }
public required UAuthMode Mode { get; init; }
public required UAuthSessionOptions SessionOptions { get; init; }

/// <summary>
/// Optional chain identifier.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using CodeBeam.UltimateAuth.Core.Domain;
using CodeBeam.UltimateAuth.Core.MultiTenancy;
using CodeBeam.UltimateAuth.Core.Options;

namespace CodeBeam.UltimateAuth.Core.Contracts;

Expand All @@ -13,4 +14,5 @@ public sealed record SessionRotationContext
public ClaimsSnapshot? Claims { get; init; }
public required SessionMetadata Metadata { get; init; } = SessionMetadata.Empty;
public required UAuthMode Mode { get; init; }
public required UAuthSessionOptions SessionOptions { get; init; }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using CodeBeam.UltimateAuth.Core.Domain;
using CodeBeam.UltimateAuth.Core.MultiTenancy;
using CodeBeam.UltimateAuth.Core.Options;

namespace CodeBeam.UltimateAuth.Core.Contracts;

Expand All @@ -9,4 +10,9 @@ public sealed record SessionValidationContext
public AuthSessionId SessionId { get; init; }
public DateTimeOffset Now { get; init; }
public required DeviceContext Device { get; init; }

/// <summary>
/// Required session options for validation. Remote session validators may not include.
/// </summary>
public UAuthSessionOptions? SessionOptions { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ public sealed class AuthFlowContext
public bool IsAuthenticated { get; }
public UserKey? UserKey { get; }

public UAuthServerOptions OriginalOptions { get; }
public EffectiveUAuthServerOptions EffectiveOptions { get; }
public UAuthServerOptions Options { get; }

public EffectiveAuthResponse Response { get; }
public ReturnUrlInfo ReturnUrlInfo { get; }
Expand All @@ -46,8 +45,7 @@ internal AuthFlowContext(
bool isAuthenticated,
UserKey? userKey,
SessionSecurityContext? session,
UAuthServerOptions originalOptions,
EffectiveUAuthServerOptions effectiveOptions,
UAuthServerOptions options,
EffectiveAuthResponse response,
PrimaryTokenKind primaryTokenKind,
ReturnUrlInfo returnUrlInfo)
Expand All @@ -65,8 +63,7 @@ internal AuthFlowContext(
IsAuthenticated = isAuthenticated;
UserKey = userKey;

OriginalOptions = originalOptions;
EffectiveOptions = effectiveOptions;
Options = options;

Response = response;
PrimaryTokenKind = primaryTokenKind;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using CodeBeam.UltimateAuth.Core.Contracts;
using CodeBeam.UltimateAuth.Core.Domain;
using CodeBeam.UltimateAuth.Core.Infrastructure;
using CodeBeam.UltimateAuth.Core.MultiTenancy;
using CodeBeam.UltimateAuth.Core.Options;
using CodeBeam.UltimateAuth.Server.Abstractions;
using CodeBeam.UltimateAuth.Server.Extensions;
Expand Down Expand Up @@ -50,19 +49,20 @@ public async ValueTask<AuthFlowContext> CreateAsync(HttpContext ctx, AuthFlowTyp
var user = ctx.GetUserContext();

var clientProfile = await _clientProfileReader.ReadAsync(ctx);
var originalOptions = _serverOptionsProvider.GetOriginal(ctx);
var effectiveOptions = _serverOptionsProvider.GetEffective(ctx, flowType, clientProfile);

var allowedModes = originalOptions.AllowedModes;
var effective = _serverOptionsProvider.GetEffective(ctx, clientProfile);
var options = effective.Options;

if (allowedModes is { Count: > 0 } && !allowedModes.Contains(effectiveOptions.Mode))
var effectiveMode = effective.Mode;
var allowedModes = options.AllowedModes;

if (allowedModes is { Count: > 0 } && !allowedModes.Contains(effectiveMode))
{
throw new InvalidOperationException($"Auth mode '{effectiveOptions.Mode}' is not allowed by server configuration.");
throw new InvalidOperationException($"Auth mode '{effectiveMode}' is not allowed by server configuration.");
}

var effectiveMode = effectiveOptions.Mode;
var primaryTokenKind = _primaryTokenResolver.Resolve(effectiveMode);
var response = _authResponseResolver.Resolve(effectiveMode, flowType, clientProfile, effectiveOptions);
var response = _authResponseResolver.Resolve(effectiveMode, flowType, clientProfile, effective);
var deviceInfo = await _deviceResolver.ResolveAsync(ctx);
var deviceContext = _deviceContextFactory.Create(deviceInfo);
var returnUrl = await ctx.GetReturnUrlAsync();
Expand All @@ -78,6 +78,7 @@ public async ValueTask<AuthFlowContext> CreateAsync(HttpContext ctx, AuthFlowTyp
Tenant = tenant,
SessionId = sessionCtx.SessionId!.Value,
Device = deviceContext,
SessionOptions = options.Session,
Now = _clock.UtcNow
},
ct);
Expand All @@ -100,8 +101,7 @@ public async ValueTask<AuthFlowContext> CreateAsync(HttpContext ctx, AuthFlowTyp
user?.IsAuthenticated ?? false,
user?.UserId,
sessionSecurityContext,
originalOptions,
effectiveOptions,
options,
response,
primaryTokenKind,
returnUrlInfo
Expand All @@ -113,19 +113,19 @@ public async ValueTask<AuthFlowContext> RecreateWithClientProfileAsync(AuthFlowC
var flowType = existing.FlowType;
var tenant = existing.Tenant;

var originalOptions = existing.OriginalOptions;
var effectiveOptions = _serverOptionsProvider.GetEffective(tenant, flowType, overriddenProfile);
var effective = _serverOptionsProvider.GetEffective(tenant, overriddenProfile);
var options = effective.Options;
var effectiveMode = effective.Mode;

var allowedModes = originalOptions.AllowedModes;
var allowedModes = options.AllowedModes;

if (allowedModes is { Count: > 0 } && !allowedModes.Contains(effectiveOptions.Mode))
if (allowedModes is { Count: > 0 } && !allowedModes.Contains(effectiveMode))
{
throw new InvalidOperationException($"Auth mode '{effectiveOptions.Mode}' is not allowed by server configuration.");
throw new InvalidOperationException($"Auth mode '{effectiveMode}' is not allowed by server configuration.");
}

var effectiveMode = effectiveOptions.Mode;
var primaryTokenKind = _primaryTokenResolver.Resolve(effectiveMode);
var response = _authResponseResolver.Resolve(effectiveMode, flowType, overriddenProfile, effectiveOptions);
var response = _authResponseResolver.Resolve(effectiveMode, flowType, overriddenProfile, effective);

var returnUrlInfo = existing.ReturnUrlInfo;
var deviceContext = existing.Device;
Expand All @@ -140,8 +140,7 @@ public async ValueTask<AuthFlowContext> RecreateWithClientProfileAsync(AuthFlowC
existing.IsAuthenticated,
existing.UserKey,
session,
originalOptions,
effectiveOptions,
options,
response,
primaryTokenKind,
returnUrlInfo
Expand All @@ -162,8 +161,7 @@ public ValueTask<AuthFlowContext> RecreateWithDeviceAsync(AuthFlowContext existi
existing.IsAuthenticated,
existing.UserKey,
existing.Session,
existing.OriginalOptions,
existing.EffectiveOptions,
existing.Options,
existing.Response,
existing.PrimaryTokenKind,
existing.ReturnUrlInfo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ public UAuthServerOptions GetOriginal(HttpContext context)
return _baseOptions.Value;
}

public EffectiveUAuthServerOptions GetEffective(HttpContext context, AuthFlowType flowType, UAuthClientProfile clientProfile)
public EffectiveUAuthServerOptions GetEffective(HttpContext context, UAuthClientProfile clientProfile)
{
var tenant = context.GetTenant();
return GetEffective(tenant, flowType, clientProfile);
return GetEffective(tenant, clientProfile);
}

public EffectiveUAuthServerOptions GetEffective(TenantKey tenant, AuthFlowType flowType, UAuthClientProfile clientProfile)
public EffectiveUAuthServerOptions GetEffective(TenantKey tenant, UAuthClientProfile clientProfile)
{
var original = _baseOptions.Value;
var effectiveMode = _modeResolver.Resolve(clientProfile, flowType);
var effectiveMode = _modeResolver.Resolve(clientProfile);
var options = original.Clone();

ConfigureDefaults.ApplyModeDefaults(effectiveMode, options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace CodeBeam.UltimateAuth.Server.Auth;

internal sealed class EffectiveAuthModeResolver : IEffectiveAuthModeResolver
{
public UAuthMode Resolve(UAuthClientProfile clientProfile, AuthFlowType flowType)
public UAuthMode Resolve(UAuthClientProfile clientProfile)
{
return clientProfile switch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ namespace CodeBeam.UltimateAuth.Server.Auth;

public interface IEffectiveAuthModeResolver
{
UAuthMode Resolve(UAuthClientProfile clientProfile, AuthFlowType flowType);
UAuthMode Resolve(UAuthClientProfile clientProfile);
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
Tenant = tenant,
SessionId = sessionId,
Device = _deviceContextFactory.Create(credential.Device),
SessionOptions = _serverOptions.Session,
Now = _clock.UtcNow
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using CodeBeam.UltimateAuth.Core.Abstractions;
using CodeBeam.UltimateAuth.Core.Contracts;
using CodeBeam.UltimateAuth.Core.Domain;
using CodeBeam.UltimateAuth.Server.Auth;
using CodeBeam.UltimateAuth.Server.Extensions;
using CodeBeam.UltimateAuth.Server.Infrastructure;
using CodeBeam.UltimateAuth.Server.Options;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ public async Task<IResult> CompleteAsync(HttpContext ctx)

private async Task<IResult> RedirectToLoginWithErrorAsync(HttpContext ctx, AuthFlowContext auth, string error)
{
var basePath = auth.OriginalOptions.Hub.LoginPath ?? "/login";
var basePath = auth.Options.Hub.LoginPath ?? "/login";
var hubKey = await ResolveHubKeyAsync(ctx);

if (!string.IsNullOrWhiteSpace(hubKey))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public async Task<IResult> RefreshAsync(HttpContext ctx)
_credentialWriter.Write(ctx, GrantKind.RefreshToken, result.RefreshToken);
}

if (flow.OriginalOptions.Diagnostics.EnableRefreshDetails)
if (flow.Options.Diagnostics.EnableRefreshDetails)
{
_refreshWriter.Write(ctx, result.Outcome);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public async Task<IResult> ValidateAsync(HttpContext context, CancellationToken
Tenant = tenant,
SessionId = sessionId,
Now = _clock.UtcNow,
Device = auth.Device
Device = auth.Device,
SessionOptions = auth.Options.Session
},
ct);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public static IApplicationBuilder UseUltimateAuthResourceApi(this IApplicationBu
app.UseUAuthExceptionHandling();
app.UseMiddleware<TenantMiddleware>();
app.UseMiddleware<SessionResolutionMiddleware>();
app.UseMiddleware<SessionValidationMiddleware>();
app.UseMiddleware<RemoteSessionValidationMiddleware>();
app.UseMiddleware<UserMiddleware>();

return app;
Expand Down
22 changes: 10 additions & 12 deletions src/CodeBeam.UltimateAuth.Server/Flows/Login/LoginOrchestrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ internal sealed class LoginOrchestrator : ILoginOrchestrator, IInternalLoginOrch
private readonly ISessionStoreFactory _storeFactory;
private readonly IAuthenticationSecurityManager _authenticationSecurityManager; // runtime risk
private readonly UAuthEventDispatcher _events;
private readonly UAuthServerOptions _options;
private readonly IClock _clock;

public LoginOrchestrator(
Expand All @@ -44,7 +43,6 @@ public LoginOrchestrator(
ISessionStoreFactory storeFactory,
IAuthenticationSecurityManager authenticationSecurityManager,
UAuthEventDispatcher events,
IOptions<UAuthServerOptions> options,
IClock clock)
{
_identifierResolver = identifierResolver;
Expand All @@ -57,7 +55,6 @@ public LoginOrchestrator(
_storeFactory = storeFactory;
_authenticationSecurityManager = authenticationSecurityManager;
_events = events;
_options = options.Value;
_clock = clock;
}

Expand Down Expand Up @@ -136,7 +133,7 @@ public async Task<LoginResult> LoginAsync(AuthFlowContext flow, LoginRequest req

if (chain is not null)
{
var chainState = chain.GetState(now, _options.Session.IdleTimeout);
var chainState = chain.GetState(now, flow.Options.Session.IdleTimeout);

if (chainState == SessionState.Active)
{
Expand Down Expand Up @@ -174,14 +171,14 @@ public async Task<LoginResult> LoginAsync(AuthFlowContext flow, LoginRequest req
request.CredentialType,
state => state.RegisterFailure(
now,
_options.Login.MaxFailedAttempts,
_options.Login.LockoutDuration,
_options.Login.FailureWindow,
_options.Login.ExtendLockOnFailure),
flow.Options.Login.MaxFailedAttempts,
flow.Options.Login.LockoutDuration,
flow.Options.Login.FailureWindow,
flow.Options.Login.ExtendLockOnFailure),
ct);
}

if (_options.Login.IncludeFailureDetails)
if (flow.Options.Login.IncludeFailureDetails)
{
var stateForResponse = factorState;

Expand All @@ -190,9 +187,9 @@ public async Task<LoginResult> LoginAsync(AuthFlowContext flow, LoginRequest req
lockedUntil = stateForResponse.LockedUntil;
remainingAttempts = 0;
}
else if (_options.Login.MaxFailedAttempts > 0)
else if (flow.Options.Login.MaxFailedAttempts > 0)
{
remainingAttempts = _options.Login.MaxFailedAttempts - stateForResponse.FailedAttempts;
remainingAttempts = flow.Options.Login.MaxFailedAttempts - stateForResponse.FailedAttempts;
}
}

Expand Down Expand Up @@ -247,7 +244,8 @@ public async Task<LoginResult> LoginAsync(AuthFlowContext flow, LoginRequest req
Claims = claims,
ChainId = chainId,
Metadata = SessionMetadata.Empty,
Mode = flow.EffectiveMode
Mode = flow.EffectiveMode,
SessionOptions = flow.Options.Session
};

var authContext = flow.ToAuthContext(now);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ private static SameSiteMode ResolveSameSite(UAuthCookieOptions cookie, AuthFlowC
if (cookie.SameSite is not null)
return cookie.SameSite.Value;

return context.OriginalOptions.HubDeploymentMode switch
return context.Options.HubDeploymentMode switch
{
UAuthHubDeploymentMode.Embedded => SameSiteMode.Strict,
UAuthHubDeploymentMode.Integrated => SameSiteMode.Lax,
Expand Down Expand Up @@ -65,16 +65,16 @@ private static void ApplyLifetime(CookieOptions target, UAuthCookieOptions src,
return kind switch
{
GrantKind.Session => ResolveSessionLifetime(context),
GrantKind.RefreshToken => context.EffectiveOptions.Options.Token.RefreshTokenLifetime,
GrantKind.AccessToken => context.EffectiveOptions.Options.Token.AccessTokenLifetime,
GrantKind.RefreshToken => context.Options.Token.RefreshTokenLifetime,
GrantKind.AccessToken => context.Options.Token.AccessTokenLifetime,
_ => null
};
}

private static TimeSpan? ResolveSessionLifetime(AuthFlowContext context)
{
var sessionIdle = context.EffectiveOptions.Options.Session.IdleTimeout;
var refresh = context.EffectiveOptions.Options.Token.RefreshTokenLifetime;
var sessionIdle = context.Options.Session.IdleTimeout;
var refresh = context.Options.Token.RefreshTokenLifetime;

return context.EffectiveMode switch
{
Expand Down
Loading
Loading