Skip to content

Commit c6f8577

Browse files
committed
Session & Token Options and Validators
1 parent 7325ede commit c6f8577

18 files changed

Lines changed: 472 additions & 152 deletions

File tree

samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Program.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@
6060
//o.Session.MaxLifetime = TimeSpan.FromSeconds(32);
6161
//o.Session.TouchInterval = TimeSpan.FromSeconds(9);
6262
//o.Session.IdleTimeout = TimeSpan.FromSeconds(15);
63-
o.Login.MaxFailedAttempts = 2;
6463
})
6564
.AddUltimateAuthUsersInMemory()
6665
.AddUltimateAuthUsersReference()

src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/IOpaqueTokenGenerator.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
/// </summary>
77
public interface IOpaqueTokenGenerator
88
{
9-
string Generate(int byteLength = 32);
9+
string Generate();
10+
string GenerateJwtId();
1011
}

src/CodeBeam.UltimateAuth.Core/Extensions/ServiceCollectionExtensions.cs

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ private static IServiceCollection AddUltimateAuthInternal(this IServiceCollectio
9191
services.AddSingleton<IValidateOptions<UAuthOptions>, UAuthOptionsValidator>();
9292
services.AddSingleton<IValidateOptions<UAuthSessionOptions>, UAuthSessionOptionsValidator>();
9393
services.AddSingleton<IValidateOptions<UAuthTokenOptions>, UAuthTokenOptionsValidator>();
94+
services.AddSingleton<IValidateOptions<UAuthLoginOptions>, UAuthLoginOptionsValidator>();
9495
services.AddSingleton<IValidateOptions<UAuthPkceOptions>, UAuthPkceOptionsValidator>();
9596
services.AddSingleton<IValidateOptions<UAuthMultiTenantOptions>, UAuthMultiTenantOptionsValidator>();
9697

@@ -101,32 +102,6 @@ private static IServiceCollection AddUltimateAuthInternal(this IServiceCollectio
101102
services.TryAddSingleton<IUAuthProductInfoProvider, UAuthProductInfoProvider>();
102103
services.TryAddSingleton<SeedRunner>();
103104

104-
//services.PostConfigure<UAuthOptions>(options =>
105-
//{
106-
// var hasRuntimeMarker = services.Any(sd => sd.ServiceType == typeof(IUAuthRuntimeMarker));
107-
108-
// if (hasRuntimeMarker && options.AllowDirectCoreConfiguration)
109-
// {
110-
// throw new InvalidOperationException(
111-
// "Direct core configuration is not allowed in server-hosted applications. " +
112-
// "Configure authentication policies via AddUltimateAuthServer instead.");
113-
// }
114-
//});
115-
116-
//services.PostConfigure<UAuthOptions, DirectCoreConfigurationMarker>(
117-
//(options, marker) =>
118-
//{
119-
// if (!options.AllowDirectCoreConfiguration && marker.IsConfigured)
120-
// {
121-
// throw new InvalidOperationException(
122-
// "Direct core configuration is not allowed. " +
123-
// "Set AllowDirectCoreConfiguration = true only for advanced, non-server scenarios, " +
124-
// "or configure authentication policies via AddUltimateAuthServer.");
125-
// }
126-
//});
127-
128-
129-
130105
return services;
131106
}
132107
}

src/CodeBeam.UltimateAuth.Core/Options/UAuthPkceOptions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,4 @@ public sealed class UAuthPkceOptions
2121
AuthorizationCodeLifetimeSeconds = AuthorizationCodeLifetimeSeconds,
2222
MaxVerificationAttempts = MaxVerificationAttempts,
2323
};
24-
2524
}

src/CodeBeam.UltimateAuth.Core/Options/UAuthSessionOptions.cs

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@ namespace CodeBeam.UltimateAuth.Core.Options;
44

55
// TODO: Add rotate on refresh (especially for Hybrid). Default behavior should be single session in chain for Hybrid, but can be configured.
66
// And add RotateAsync method.
7+
// Implement all options.
78

89
/// <summary>
9-
/// Defines configuration settings that control the lifecycle,
10-
/// security behavior, and device constraints of UltimateAuth
10+
/// Defines configuration settings that control the lifecycle, security behavior, and device constraints of UltimateAuth
1111
/// session management.
1212
///
13-
/// These values influence how sessions are created, refreshed,
14-
/// expired, revoked, and grouped into device chains.
13+
/// These values influence how sessions are created, refreshed, expired, revoked, and grouped into device chains.
1514
/// </summary>
1615
public sealed class UAuthSessionOptions
1716
{
@@ -50,45 +49,78 @@ public sealed class UAuthSessionOptions
5049
/// <summary>
5150
/// Maximum number of device session chains a single user may have.
5251
/// Set to zero to indicate no user-level chain limit.
52+
///
53+
/// NOTE:
54+
/// Enforcement is not active in v0.0.1.
55+
/// This option is reserved for future security policies.
5356
/// </summary>
5457
public int MaxChainsPerUser { get; set; } = 0;
5558

5659
/// <summary>
5760
/// Maximum number of session rotations within a single chain.
5861
/// Used for cleanup, replay protection, and analytics.
62+
///
63+
/// NOTE:
64+
/// Enforcement is not active in v0.0.1.
65+
/// This option is reserved for future security policies.
5966
/// </summary>
6067
public int MaxSessionsPerChain { get; set; } = 100;
6168

6269
/// <summary>
6370
/// Optional limit on the number of session chains allowed per platform
6471
/// (e.g. "web" = 1, "mobile" = 1).
72+
///
73+
/// NOTE:
74+
/// Enforcement is not active in v0.0.1.
75+
/// This option is reserved for future security policies.
6576
/// </summary>
6677
public Dictionary<string, int>? MaxChainsPerPlatform { get; set; }
6778

6879
/// <summary>
6980
/// Defines platform categories that map multiple platforms
7081
/// into a single abstract group (e.g. mobile: [ "ios", "android", "tablet" ]).
82+
///
83+
/// NOTE:
84+
/// Enforcement is not active in v0.0.1.
85+
/// This option is reserved for future security policies.
7186
/// </summary>
7287
public Dictionary<string, string[]>? PlatformCategories { get; set; }
7388

7489
/// <summary>
7590
/// Limits how many session chains can exist per platform category
7691
/// (e.g. mobile = 1, desktop = 2).
92+
///
93+
/// NOTE:
94+
/// Enforcement is not active in v0.0.1.
95+
/// This option is reserved for future security policies.
7796
/// </summary>
7897
public Dictionary<string, int>? MaxChainsPerCategory { get; set; }
7998

8099
/// <summary>
81100
/// Enables binding sessions to the user's IP address.
82101
/// When enabled, IP mismatches can invalidate a session.
102+
///
103+
/// NOTE:
104+
/// Enforcement is not active in v0.0.1.
105+
/// This option is reserved for future security policies.
83106
/// </summary>
84107
public bool EnableIpBinding { get; set; } = false;
85108

86109
/// <summary>
87110
/// Enables binding sessions to the user's User-Agent header.
88111
/// When enabled, UA mismatches can invalidate a session.
112+
///
113+
/// NOTE:
114+
/// Enforcement is not active in v0.0.1.
115+
/// This option is reserved for future security policies.
89116
/// </summary>
90117
public bool EnableUserAgentBinding { get; set; } = false;
91118

119+
/// <summary>
120+
/// NOTE:
121+
/// Enforcement is not active in v0.0.1.
122+
/// This option is reserved for future security policies.
123+
/// </summary>
92124
public DeviceMismatchBehavior DeviceMismatchBehavior { get; set; } = DeviceMismatchBehavior.Reject;
93125

94126
internal UAuthSessionOptions Clone() => new()

src/CodeBeam.UltimateAuth.Core/Options/UAuthSessionOptionsValidator.cs

Lines changed: 0 additions & 99 deletions
This file was deleted.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Microsoft.Extensions.Options;
2+
3+
namespace CodeBeam.UltimateAuth.Core.Options;
4+
5+
internal sealed class UAuthLoginOptionsValidator : IValidateOptions<UAuthLoginOptions>
6+
{
7+
public ValidateOptionsResult Validate(string? name, UAuthLoginOptions options)
8+
{
9+
var errors = new List<string>();
10+
11+
if (options.MaxFailedAttempts < 0)
12+
errors.Add("Login.MaxFailedAttempts cannot be negative.");
13+
14+
if (options.MaxFailedAttempts > 100)
15+
errors.Add("Login.MaxFailedAttempts cannot exceed 100. Use 0 to disable lockout.");
16+
17+
if (options.MaxFailedAttempts > 0 && options.LockoutMinutes <= 0)
18+
errors.Add("Login.LockoutMinutes must be greater than zero when lockout is enabled.");
19+
20+
return errors.Count == 0
21+
? ValidateOptionsResult.Success
22+
: ValidateOptionsResult.Fail(errors);
23+
}
24+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using Microsoft.Extensions.Options;
2+
3+
namespace CodeBeam.UltimateAuth.Core.Options;
4+
5+
internal sealed class UAuthSessionOptionsValidator : IValidateOptions<UAuthSessionOptions>
6+
{
7+
public ValidateOptionsResult Validate(string? name, UAuthSessionOptions options)
8+
{
9+
var errors = new List<string>();
10+
11+
if (options.Lifetime <= TimeSpan.Zero)
12+
errors.Add("Session.Lifetime must be greater than zero.");
13+
14+
if (options.MaxLifetime < options.Lifetime)
15+
errors.Add("Session.MaxLifetime must be greater than or equal to Session.Lifetime.");
16+
17+
if (options.IdleTimeout.HasValue && options.IdleTimeout < TimeSpan.Zero)
18+
errors.Add("Session.IdleTimeout cannot be negative.");
19+
20+
if (options.IdleTimeout.HasValue && options.IdleTimeout > TimeSpan.Zero && options.IdleTimeout > options.MaxLifetime)
21+
{
22+
errors.Add("Session.IdleTimeout cannot exceed Session.MaxLifetime.");
23+
}
24+
25+
//if (options.MaxChainsPerUser <= 0)
26+
// errors.Add("Session.MaxChainsPerUser must be at least 1.");
27+
28+
//if (options.MaxSessionsPerChain <= 0)
29+
// errors.Add("Session.MaxSessionsPerChain must be at least 1.");
30+
31+
//if (options.MaxChainsPerPlatform != null)
32+
//{
33+
// foreach (var kv in options.MaxChainsPerPlatform)
34+
// {
35+
// if (string.IsNullOrWhiteSpace(kv.Key))
36+
// errors.Add("Session.MaxChainsPerPlatform contains an empty platform key.");
37+
38+
// if (kv.Value <= 0)
39+
// errors.Add($"Session.MaxChainsPerPlatform['{kv.Key}'] must be >= 1.");
40+
// }
41+
//}
42+
43+
//if (options.PlatformCategories != null)
44+
//{
45+
// foreach (var cat in options.PlatformCategories)
46+
// {
47+
// var categoryName = cat.Key;
48+
// var platforms = cat.Value;
49+
50+
// if (string.IsNullOrWhiteSpace(categoryName))
51+
// errors.Add("Session.PlatformCategories contains an empty category name.");
52+
53+
// if (platforms == null || platforms.Length == 0)
54+
// errors.Add($"Session.PlatformCategories['{categoryName}'] must contain at least one platform.");
55+
56+
// var duplicates = platforms?
57+
// .GroupBy(p => p)
58+
// .Where(g => g.Count() > 1)
59+
// .Select(g => g.Key);
60+
// if (duplicates?.Any() == true)
61+
// {
62+
// errors.Add($"Session.PlatformCategories['{categoryName}'] contains duplicate platforms: {string.Join(", ", duplicates)}");
63+
// }
64+
// }
65+
//}
66+
67+
//if (options.MaxChainsPerCategory != null)
68+
//{
69+
// foreach (var kv in options.MaxChainsPerCategory)
70+
// {
71+
// if (string.IsNullOrWhiteSpace(kv.Key))
72+
// errors.Add("Session.MaxChainsPerCategory contains an empty category key.");
73+
74+
// if (kv.Value <= 0)
75+
// errors.Add($"Session.MaxChainsPerCategory['{kv.Key}'] must be >= 1.");
76+
// }
77+
//}
78+
79+
//if (options.PlatformCategories != null && options.MaxChainsPerCategory != null)
80+
//{
81+
// foreach (var category in options.PlatformCategories.Keys)
82+
// {
83+
// if (!options.MaxChainsPerCategory.ContainsKey(category))
84+
// {
85+
// errors.Add(
86+
// $"Session.MaxChainsPerCategory must define a limit for category '{category}' " +
87+
// "because it exists in Session.PlatformCategories.");
88+
// }
89+
// }
90+
//}
91+
92+
if (errors.Count == 0)
93+
return ValidateOptionsResult.Success;
94+
95+
return ValidateOptionsResult.Fail(errors);
96+
}
97+
}

0 commit comments

Comments
 (0)