♻️ Refactored to make a simplifier auth session system
This commit is contained in:
@@ -597,7 +597,6 @@ public class AccountCurrentController(
|
|||||||
|
|
||||||
var query = db.AuthSessions
|
var query = db.AuthSessions
|
||||||
.Include(session => session.Account)
|
.Include(session => session.Account)
|
||||||
.Include(session => session.Challenge)
|
|
||||||
.Where(session => session.Account.Id == currentUser.Id);
|
.Where(session => session.Account.Id == currentUser.Id);
|
||||||
|
|
||||||
var total = await query.CountAsync();
|
var total = await query.CountAsync();
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public class DysonTokenAuthHandler(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Add scopes as claims
|
// Add scopes as claims
|
||||||
session.Challenge?.Scopes.ForEach(scope => claims.Add(new Claim("scope", scope)));
|
session.Scopes.ForEach(scope => claims.Add(new Claim("scope", scope)));
|
||||||
|
|
||||||
// Add superuser claim if applicable
|
// Add superuser claim if applicable
|
||||||
if (session.Account.IsSuperuser)
|
if (session.Account.IsSuperuser)
|
||||||
@@ -117,16 +117,17 @@ public class DysonTokenAuthHandler(
|
|||||||
{
|
{
|
||||||
if (authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
if (authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var token = authHeader["Bearer ".Length..].Trim();
|
var tokenText = authHeader["Bearer ".Length..].Trim();
|
||||||
var parts = token.Split('.');
|
var parts = tokenText.Split('.');
|
||||||
|
|
||||||
return new TokenInfo
|
return new TokenInfo
|
||||||
{
|
{
|
||||||
Token = token,
|
Token = tokenText,
|
||||||
Type = parts.Length == 3 ? TokenType.OidcKey : TokenType.AuthKey
|
Type = parts.Length == 3 ? TokenType.OidcKey : TokenType.AuthKey
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (authHeader.StartsWith("AtField ", StringComparison.OrdinalIgnoreCase))
|
|
||||||
|
if (authHeader.StartsWith("AtField ", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return new TokenInfo
|
return new TokenInfo
|
||||||
{
|
{
|
||||||
@@ -134,7 +135,8 @@ public class DysonTokenAuthHandler(
|
|||||||
Type = TokenType.AuthKey
|
Type = TokenType.AuthKey
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (authHeader.StartsWith("AkField ", StringComparison.OrdinalIgnoreCase))
|
|
||||||
|
if (authHeader.StartsWith("AkField ", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return new TokenInfo
|
return new TokenInfo
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ public class AuthController(
|
|||||||
[Required] [MaxLength(256)] public string Account { get; set; } = null!;
|
[Required] [MaxLength(256)] public string Account { get; set; } = null!;
|
||||||
[Required] [MaxLength(512)] public string DeviceId { get; set; } = null!;
|
[Required] [MaxLength(512)] public string DeviceId { get; set; } = null!;
|
||||||
[MaxLength(1024)] public string? DeviceName { get; set; }
|
[MaxLength(1024)] public string? DeviceName { get; set; }
|
||||||
public List<string> Audiences { get; set; } = new();
|
public List<string> Audiences { get; set; } = [];
|
||||||
public List<string> Scopes { get; set; } = new();
|
public List<string> Scopes { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("challenge")]
|
[HttpPost("challenge")]
|
||||||
@@ -68,15 +68,9 @@ public class AuthController(
|
|||||||
.Where(e => e.UserAgent == userAgent)
|
.Where(e => e.UserAgent == userAgent)
|
||||||
.Where(e => e.StepRemain > 0)
|
.Where(e => e.StepRemain > 0)
|
||||||
.Where(e => e.ExpiredAt != null && now < e.ExpiredAt)
|
.Where(e => e.ExpiredAt != null && now < e.ExpiredAt)
|
||||||
.Where(e => e.Type == Shared.Models.ChallengeType.Login)
|
|
||||||
.Where(e => e.DeviceId == request.DeviceId)
|
.Where(e => e.DeviceId == request.DeviceId)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
if (existingChallenge is not null)
|
if (existingChallenge is not null) return existingChallenge;
|
||||||
{
|
|
||||||
var existingSession = await db.AuthSessions.Where(e => e.ChallengeId == existingChallenge.Id)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
if (existingSession is null) return existingChallenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
var challenge = new SnAuthChallenge
|
var challenge = new SnAuthChallenge
|
||||||
{
|
{
|
||||||
@@ -111,14 +105,11 @@ public class AuthController(
|
|||||||
.ThenInclude(e => e.Profile)
|
.ThenInclude(e => e.Profile)
|
||||||
.FirstOrDefaultAsync(e => e.Id == id);
|
.FirstOrDefaultAsync(e => e.Id == id);
|
||||||
|
|
||||||
if (challenge is null)
|
if (challenge is not null) return challenge;
|
||||||
{
|
logger.LogWarning("GetChallenge: challenge not found (challengeId={ChallengeId}, ip={IpAddress})",
|
||||||
logger.LogWarning("GetChallenge: challenge not found (challengeId={ChallengeId}, ip={IpAddress})",
|
id, HttpContext.Connection.RemoteIpAddress?.ToString());
|
||||||
id, HttpContext.Connection.RemoteIpAddress?.ToString());
|
return NotFound("Auth challenge was not found.");
|
||||||
return NotFound("Auth challenge was not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return challenge;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("challenge/{id:guid}/factors")]
|
[HttpGet("challenge/{id:guid}/factors")]
|
||||||
@@ -216,7 +207,7 @@ public class AuthController(
|
|||||||
throw new ArgumentException("Invalid password.");
|
throw new ArgumentException("Invalid password.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
challenge.FailedAttempts++;
|
challenge.FailedAttempts++;
|
||||||
db.Update(challenge);
|
db.Update(challenge);
|
||||||
@@ -229,8 +220,11 @@ public class AuthController(
|
|||||||
);
|
);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
logger.LogWarning("DoChallenge: authentication failure (challengeId={ChallengeId}, factorId={FactorId}, accountId={AccountId}, failedAttempts={FailedAttempts}, factorType={FactorType}, ip={IpAddress}, uaLength={UaLength})",
|
logger.LogWarning(
|
||||||
challenge.Id, factor.Id, challenge.AccountId, challenge.FailedAttempts, factor.Type, HttpContext.Connection.RemoteIpAddress?.ToString(), (HttpContext.Request.Headers.UserAgent.ToString() ?? "").Length);
|
"DoChallenge: authentication failure (challengeId={ChallengeId}, factorId={FactorId}, accountId={AccountId}, failedAttempts={FailedAttempts}, factorType={FactorType}, ip={IpAddress}, uaLength={UaLength})",
|
||||||
|
challenge.Id, factor.Id, challenge.AccountId, challenge.FailedAttempts, factor.Type,
|
||||||
|
HttpContext.Connection.RemoteIpAddress?.ToString(),
|
||||||
|
HttpContext.Request.Headers.UserAgent.ToString().Length);
|
||||||
|
|
||||||
return BadRequest("Invalid password.");
|
return BadRequest("Invalid password.");
|
||||||
}
|
}
|
||||||
@@ -240,7 +234,7 @@ public class AuthController(
|
|||||||
AccountService.SetCultureInfo(challenge.Account);
|
AccountService.SetCultureInfo(challenge.Account);
|
||||||
await pusher.SendPushNotificationToUserAsync(new SendPushNotificationToUserRequest
|
await pusher.SendPushNotificationToUserAsync(new SendPushNotificationToUserRequest
|
||||||
{
|
{
|
||||||
Notification = new PushNotification()
|
Notification = new PushNotification
|
||||||
{
|
{
|
||||||
Topic = "auth.login",
|
Topic = "auth.login",
|
||||||
Title = localizer["NewLoginTitle"],
|
Title = localizer["NewLoginTitle"],
|
||||||
@@ -279,7 +273,7 @@ public class AuthController(
|
|||||||
{
|
{
|
||||||
[Required] [MaxLength(512)] public string DeviceId { get; set; } = null!;
|
[Required] [MaxLength(512)] public string DeviceId { get; set; } = null!;
|
||||||
[MaxLength(1024)] public string? DeviceName { get; set; }
|
[MaxLength(1024)] public string? DeviceName { get; set; }
|
||||||
[Required] public DysonNetwork.Shared.Models.ClientPlatform Platform { get; set; }
|
[Required] public Shared.Models.ClientPlatform Platform { get; set; }
|
||||||
public Instant? ExpiredAt { get; set; }
|
public Instant? ExpiredAt { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,8 +332,9 @@ public class AuthController(
|
|||||||
[Microsoft.AspNetCore.Authorization.Authorize] // Use full namespace to avoid ambiguity with DysonNetwork.Pass.Permission.Authorize
|
[Microsoft.AspNetCore.Authorization.Authorize] // Use full namespace to avoid ambiguity with DysonNetwork.Pass.Permission.Authorize
|
||||||
public async Task<ActionResult<TokenExchangeResponse>> LoginFromSession([FromBody] NewSessionRequest request)
|
public async Task<ActionResult<TokenExchangeResponse>> LoginFromSession([FromBody] NewSessionRequest request)
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser ||
|
if (HttpContext.Items["CurrentUser"] is not SnAccount ||
|
||||||
HttpContext.Items["CurrentSession"] is not Shared.Models.SnAuthSession currentSession) return Unauthorized();
|
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
var newSession = await auth.CreateSessionFromParentAsync(
|
var newSession = await auth.CreateSessionFromParentAsync(
|
||||||
currentSession,
|
currentSession,
|
||||||
@@ -352,13 +347,12 @@ public class AuthController(
|
|||||||
var tk = auth.CreateToken(newSession);
|
var tk = auth.CreateToken(newSession);
|
||||||
|
|
||||||
// Set cookie using HttpContext, similar to CreateSessionAndIssueToken
|
// Set cookie using HttpContext, similar to CreateSessionAndIssueToken
|
||||||
var cookieDomain = _cookieDomain;
|
|
||||||
HttpContext.Response.Cookies.Append(AuthConstants.CookieTokenName, tk, new CookieOptions
|
HttpContext.Response.Cookies.Append(AuthConstants.CookieTokenName, tk, new CookieOptions
|
||||||
{
|
{
|
||||||
HttpOnly = true,
|
HttpOnly = true,
|
||||||
Secure = true,
|
Secure = true,
|
||||||
SameSite = SameSiteMode.Lax,
|
SameSite = SameSiteMode.Lax,
|
||||||
Domain = cookieDomain,
|
Domain = _cookieDomain,
|
||||||
Expires = request.ExpiredAt?.ToDateTimeOffset() ?? DateTime.UtcNow.AddYears(20)
|
Expires = request.ExpiredAt?.ToDateTimeOffset() ?? DateTime.UtcNow.AddYears(20)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public class AuthService(
|
|||||||
{
|
{
|
||||||
// 1) Find out how many authentication factors the account has enabled.
|
// 1) Find out how many authentication factors the account has enabled.
|
||||||
var enabledFactors = await db.AccountAuthFactors
|
var enabledFactors = await db.AccountAuthFactors
|
||||||
.Where(f => f.AccountId == account.Id)
|
.Where(f => f.AccountId == account.Id && f.Type != AccountAuthFactorType.PinCode)
|
||||||
.Where(f => f.EnabledAt != null)
|
.Where(f => f.EnabledAt != null)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
var maxSteps = enabledFactors.Count;
|
var maxSteps = enabledFactors.Count;
|
||||||
@@ -42,13 +42,15 @@ public class AuthService(
|
|||||||
|
|
||||||
// 2) Get login context from recent sessions
|
// 2) Get login context from recent sessions
|
||||||
var recentSessions = await db.AuthSessions
|
var recentSessions = await db.AuthSessions
|
||||||
.Include(s => s.Challenge)
|
|
||||||
.Where(s => s.AccountId == account.Id)
|
.Where(s => s.AccountId == account.Id)
|
||||||
.Where(s => s.LastGrantedAt != null)
|
.Where(s => s.LastGrantedAt != null)
|
||||||
.OrderByDescending(s => s.LastGrantedAt)
|
.OrderByDescending(s => s.LastGrantedAt)
|
||||||
.Take(10)
|
.Take(10)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
var recentChallengeIds = recentSessions.Where(s => s.ChallengeId != null).Select(s => s.ChallengeId.Value).ToList();
|
||||||
|
var recentChallenges = await db.AuthChallenges.Where(c => recentChallengeIds.Contains(c.Id)).ToListAsync();
|
||||||
|
|
||||||
var ipAddress = request.HttpContext.Connection.RemoteIpAddress?.ToString();
|
var ipAddress = request.HttpContext.Connection.RemoteIpAddress?.ToString();
|
||||||
var userAgent = request.Headers.UserAgent.ToString();
|
var userAgent = request.Headers.UserAgent.ToString();
|
||||||
|
|
||||||
@@ -60,14 +62,14 @@ public class AuthService(
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Check if IP has been used before
|
// Check if IP has been used before
|
||||||
var ipPreviouslyUsed = recentSessions.Any(s => s.Challenge?.IpAddress == ipAddress);
|
var ipPreviouslyUsed = recentChallenges.Any(c => c.IpAddress == ipAddress);
|
||||||
if (!ipPreviouslyUsed)
|
if (!ipPreviouslyUsed)
|
||||||
{
|
{
|
||||||
riskScore += 8;
|
riskScore += 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check geographical distance for last known location
|
// Check geographical distance for last known location
|
||||||
var lastKnownIp = recentSessions.FirstOrDefault(s => !string.IsNullOrWhiteSpace(s.Challenge?.IpAddress))?.Challenge?.IpAddress;
|
var lastKnownIp = recentChallenges.FirstOrDefault(c => !string.IsNullOrWhiteSpace(c.IpAddress))?.IpAddress;
|
||||||
if (!string.IsNullOrWhiteSpace(lastKnownIp) && lastKnownIp != ipAddress)
|
if (!string.IsNullOrWhiteSpace(lastKnownIp) && lastKnownIp != ipAddress)
|
||||||
{
|
{
|
||||||
riskScore += 6;
|
riskScore += 6;
|
||||||
@@ -81,9 +83,9 @@ public class AuthService(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var uaPreviouslyUsed = recentSessions.Any(s =>
|
var uaPreviouslyUsed = recentChallenges.Any(c =>
|
||||||
!string.IsNullOrWhiteSpace(s.Challenge?.UserAgent) &&
|
!string.IsNullOrWhiteSpace(c.UserAgent) &&
|
||||||
string.Equals(s.Challenge.UserAgent, userAgent, StringComparison.OrdinalIgnoreCase));
|
string.Equals(c.UserAgent, userAgent, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
if (!uaPreviouslyUsed)
|
if (!uaPreviouslyUsed)
|
||||||
{
|
{
|
||||||
@@ -184,30 +186,18 @@ public class AuthService(
|
|||||||
public async Task<SnAuthSession> CreateSessionForOidcAsync(SnAccount account, Instant time,
|
public async Task<SnAuthSession> CreateSessionForOidcAsync(SnAccount account, Instant time,
|
||||||
Guid? customAppId = null, SnAuthSession? parentSession = null)
|
Guid? customAppId = null, SnAuthSession? parentSession = null)
|
||||||
{
|
{
|
||||||
var challenge = new SnAuthChallenge
|
|
||||||
{
|
|
||||||
AccountId = account.Id,
|
|
||||||
IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString(),
|
|
||||||
UserAgent = HttpContext.Request.Headers.UserAgent,
|
|
||||||
StepRemain = 1,
|
|
||||||
StepTotal = 1,
|
|
||||||
Type = customAppId is not null ? ChallengeType.OAuth : ChallengeType.Oidc,
|
|
||||||
DeviceId = Guid.NewGuid().ToString(),
|
|
||||||
DeviceName = "OIDC/OAuth",
|
|
||||||
Platform = ClientPlatform.Web,
|
|
||||||
};
|
|
||||||
|
|
||||||
var session = new SnAuthSession
|
var session = new SnAuthSession
|
||||||
{
|
{
|
||||||
AccountId = account.Id,
|
AccountId = account.Id,
|
||||||
CreatedAt = time,
|
CreatedAt = time,
|
||||||
LastGrantedAt = time,
|
LastGrantedAt = time,
|
||||||
Challenge = challenge,
|
IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString(),
|
||||||
|
UserAgent = HttpContext.Request.Headers.UserAgent,
|
||||||
AppId = customAppId,
|
AppId = customAppId,
|
||||||
ParentSessionId = parentSession?.Id
|
ParentSessionId = parentSession?.Id,
|
||||||
|
Type = customAppId is not null ? SessionType.OAuth : SessionType.Oidc,
|
||||||
};
|
};
|
||||||
|
|
||||||
db.AuthChallenges.Add(challenge);
|
|
||||||
db.AuthSessions.Add(session);
|
db.AuthSessions.Add(session);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
@@ -419,11 +409,6 @@ public class AuthService(
|
|||||||
if (challenge.StepRemain != 0)
|
if (challenge.StepRemain != 0)
|
||||||
throw new ArgumentException("Challenge not yet completed.");
|
throw new ArgumentException("Challenge not yet completed.");
|
||||||
|
|
||||||
var hasSession = await db.AuthSessions
|
|
||||||
.AnyAsync(e => e.ChallengeId == challenge.Id);
|
|
||||||
if (hasSession)
|
|
||||||
throw new ArgumentException("Session already exists for this challenge.");
|
|
||||||
|
|
||||||
var device = await GetOrCreateDeviceAsync(challenge.AccountId, challenge.DeviceId, challenge.DeviceName,
|
var device = await GetOrCreateDeviceAsync(challenge.AccountId, challenge.DeviceId, challenge.DeviceName,
|
||||||
challenge.Platform);
|
challenge.Platform);
|
||||||
|
|
||||||
@@ -433,6 +418,10 @@ public class AuthService(
|
|||||||
LastGrantedAt = now,
|
LastGrantedAt = now,
|
||||||
ExpiredAt = now.Plus(Duration.FromDays(7)),
|
ExpiredAt = now.Plus(Duration.FromDays(7)),
|
||||||
AccountId = challenge.AccountId,
|
AccountId = challenge.AccountId,
|
||||||
|
IpAddress = challenge.IpAddress,
|
||||||
|
UserAgent = challenge.UserAgent,
|
||||||
|
Scopes = challenge.Scopes,
|
||||||
|
Audiences = challenge.Audiences,
|
||||||
ChallengeId = challenge.Id,
|
ChallengeId = challenge.Id,
|
||||||
ClientId = device.Id,
|
ClientId = device.Id,
|
||||||
};
|
};
|
||||||
@@ -457,7 +446,7 @@ public class AuthService(
|
|||||||
return tk;
|
return tk;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string CreateCompactToken(Guid sessionId, RSA rsa)
|
private static string CreateCompactToken(Guid sessionId, RSA rsa)
|
||||||
{
|
{
|
||||||
// Create the payload: just the session ID
|
// Create the payload: just the session ID
|
||||||
var payloadBytes = sessionId.ToByteArray();
|
var payloadBytes = sessionId.ToByteArray();
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ public class OidcProviderController(
|
|||||||
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized();
|
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized();
|
||||||
|
|
||||||
// Get requested scopes from the token
|
// Get requested scopes from the token
|
||||||
var scopes = currentSession.Challenge?.Scopes ?? [];
|
var scopes = currentSession.Scopes;
|
||||||
|
|
||||||
var userInfo = new Dictionary<string, object>
|
var userInfo = new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ public class OidcProviderService(
|
|||||||
var now = SystemClock.Instance.GetCurrentInstant();
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
|
||||||
var queryable = db.AuthSessions
|
var queryable = db.AuthSessions
|
||||||
.Include(s => s.Challenge)
|
|
||||||
.AsQueryable();
|
.AsQueryable();
|
||||||
if (withAccount)
|
if (withAccount)
|
||||||
queryable = queryable
|
queryable = queryable
|
||||||
@@ -85,8 +84,7 @@ public class OidcProviderService(
|
|||||||
.Where(s => s.AccountId == accountId &&
|
.Where(s => s.AccountId == accountId &&
|
||||||
s.AppId == clientId &&
|
s.AppId == clientId &&
|
||||||
(s.ExpiredAt == null || s.ExpiredAt > now) &&
|
(s.ExpiredAt == null || s.ExpiredAt > now) &&
|
||||||
s.Challenge != null &&
|
s.Type == Shared.Models.SessionType.OAuth)
|
||||||
s.Challenge.Type == Shared.Models.ChallengeType.OAuth)
|
|
||||||
.OrderByDescending(s => s.CreatedAt)
|
.OrderByDescending(s => s.CreatedAt)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
}
|
}
|
||||||
@@ -511,7 +509,6 @@ public class OidcProviderService(
|
|||||||
{
|
{
|
||||||
return await db.AuthSessions
|
return await db.AuthSessions
|
||||||
.Include(s => s.Account)
|
.Include(s => s.Account)
|
||||||
.Include(s => s.Challenge)
|
|
||||||
.FirstOrDefaultAsync(s => s.Id == sessionId);
|
.FirstOrDefaultAsync(s => s.Id == sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public class TokenAuthService(
|
|||||||
"AuthenticateTokenAsync: success via cache (sessionId={SessionId}, accountId={AccountId}, scopes={ScopeCount}, expiresAt={ExpiresAt})",
|
"AuthenticateTokenAsync: success via cache (sessionId={SessionId}, accountId={AccountId}, scopes={ScopeCount}, expiresAt={ExpiresAt})",
|
||||||
sessionId,
|
sessionId,
|
||||||
session.AccountId,
|
session.AccountId,
|
||||||
session.Challenge?.Scopes.Count,
|
session.Scopes.Count,
|
||||||
session.ExpiredAt
|
session.ExpiredAt
|
||||||
);
|
);
|
||||||
return (true, session, null);
|
return (true, session, null);
|
||||||
@@ -87,7 +87,6 @@ public class TokenAuthService(
|
|||||||
|
|
||||||
session = await db.AuthSessions
|
session = await db.AuthSessions
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(e => e.Challenge)
|
|
||||||
.Include(e => e.Client)
|
.Include(e => e.Client)
|
||||||
.Include(e => e.Account)
|
.Include(e => e.Account)
|
||||||
.ThenInclude(e => e.Profile)
|
.ThenInclude(e => e.Profile)
|
||||||
@@ -112,9 +111,9 @@ public class TokenAuthService(
|
|||||||
session.AccountId,
|
session.AccountId,
|
||||||
session.ClientId,
|
session.ClientId,
|
||||||
session.AppId,
|
session.AppId,
|
||||||
session.Challenge?.Scopes.Count,
|
session.Scopes.Count,
|
||||||
session.Challenge?.IpAddress,
|
session.IpAddress,
|
||||||
(session.Challenge?.UserAgent ?? string.Empty).Length
|
(session.UserAgent ?? string.Empty).Length
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.LogDebug("AuthenticateTokenAsync: enriching account with subscription (accountId={AccountId})", session.AccountId);
|
logger.LogDebug("AuthenticateTokenAsync: enriching account with subscription (accountId={AccountId})", session.AccountId);
|
||||||
|
|||||||
2882
DysonNetwork.Pass/Migrations/20251202160759_SimplifiedAuthSession.Designer.cs
generated
Normal file
2882
DysonNetwork.Pass/Migrations/20251202160759_SimplifiedAuthSession.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,105 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Pass.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class SimplifiedAuthSession : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "fk_auth_sessions_auth_challenges_challenge_id",
|
||||||
|
table: "auth_sessions");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "ix_auth_sessions_challenge_id",
|
||||||
|
table: "auth_sessions");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "type",
|
||||||
|
table: "auth_challenges");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<List<string>>(
|
||||||
|
name: "audiences",
|
||||||
|
table: "auth_sessions",
|
||||||
|
type: "jsonb",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new List<string>());
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "ip_address",
|
||||||
|
table: "auth_sessions",
|
||||||
|
type: "character varying(128)",
|
||||||
|
maxLength: 128,
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<List<string>>(
|
||||||
|
name: "scopes",
|
||||||
|
table: "auth_sessions",
|
||||||
|
type: "jsonb",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new List<string>());
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "type",
|
||||||
|
table: "auth_sessions",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "user_agent",
|
||||||
|
table: "auth_sessions",
|
||||||
|
type: "character varying(512)",
|
||||||
|
maxLength: 512,
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "audiences",
|
||||||
|
table: "auth_sessions");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ip_address",
|
||||||
|
table: "auth_sessions");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "scopes",
|
||||||
|
table: "auth_sessions");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "type",
|
||||||
|
table: "auth_sessions");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "user_agent",
|
||||||
|
table: "auth_sessions");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "type",
|
||||||
|
table: "auth_challenges",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_auth_sessions_challenge_id",
|
||||||
|
table: "auth_sessions",
|
||||||
|
column: "challenge_id");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "fk_auth_sessions_auth_challenges_challenge_id",
|
||||||
|
table: "auth_sessions",
|
||||||
|
column: "challenge_id",
|
||||||
|
principalTable: "auth_challenges",
|
||||||
|
principalColumn: "id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -933,10 +933,6 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("integer")
|
.HasColumnType("integer")
|
||||||
.HasColumnName("step_total");
|
.HasColumnName("step_total");
|
||||||
|
|
||||||
b.Property<int>("Type")
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasColumnName("type");
|
|
||||||
|
|
||||||
b.Property<Instant>("UpdatedAt")
|
b.Property<Instant>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("updated_at");
|
.HasColumnName("updated_at");
|
||||||
@@ -1023,6 +1019,11 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("app_id");
|
.HasColumnName("app_id");
|
||||||
|
|
||||||
|
b.Property<List<string>>("Audiences")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("audiences");
|
||||||
|
|
||||||
b.Property<Guid?>("ChallengeId")
|
b.Property<Guid?>("ChallengeId")
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("challenge_id");
|
.HasColumnName("challenge_id");
|
||||||
@@ -1043,6 +1044,11 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("expired_at");
|
.HasColumnName("expired_at");
|
||||||
|
|
||||||
|
b.Property<string>("IpAddress")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
|
.HasColumnName("ip_address");
|
||||||
|
|
||||||
b.Property<Instant?>("LastGrantedAt")
|
b.Property<Instant?>("LastGrantedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_granted_at");
|
.HasColumnName("last_granted_at");
|
||||||
@@ -1051,19 +1057,30 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("parent_session_id");
|
.HasColumnName("parent_session_id");
|
||||||
|
|
||||||
|
b.Property<List<string>>("Scopes")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("scopes");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("type");
|
||||||
|
|
||||||
b.Property<Instant>("UpdatedAt")
|
b.Property<Instant>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("updated_at");
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.Property<string>("UserAgent")
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)")
|
||||||
|
.HasColumnName("user_agent");
|
||||||
|
|
||||||
b.HasKey("Id")
|
b.HasKey("Id")
|
||||||
.HasName("pk_auth_sessions");
|
.HasName("pk_auth_sessions");
|
||||||
|
|
||||||
b.HasIndex("AccountId")
|
b.HasIndex("AccountId")
|
||||||
.HasDatabaseName("ix_auth_sessions_account_id");
|
.HasDatabaseName("ix_auth_sessions_account_id");
|
||||||
|
|
||||||
b.HasIndex("ChallengeId")
|
|
||||||
.HasDatabaseName("ix_auth_sessions_challenge_id");
|
|
||||||
|
|
||||||
b.HasIndex("ClientId")
|
b.HasIndex("ClientId")
|
||||||
.HasDatabaseName("ix_auth_sessions_client_id");
|
.HasDatabaseName("ix_auth_sessions_client_id");
|
||||||
|
|
||||||
@@ -2537,11 +2554,6 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasConstraintName("fk_auth_sessions_accounts_account_id");
|
.HasConstraintName("fk_auth_sessions_accounts_account_id");
|
||||||
|
|
||||||
b.HasOne("DysonNetwork.Shared.Models.SnAuthChallenge", "Challenge")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("ChallengeId")
|
|
||||||
.HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id");
|
|
||||||
|
|
||||||
b.HasOne("DysonNetwork.Shared.Models.SnAuthClient", "Client")
|
b.HasOne("DysonNetwork.Shared.Models.SnAuthClient", "Client")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("ClientId")
|
.HasForeignKey("ClientId")
|
||||||
@@ -2554,8 +2566,6 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
|
|
||||||
b.Navigation("Account");
|
b.Navigation("Account");
|
||||||
|
|
||||||
b.Navigation("Challenge");
|
|
||||||
|
|
||||||
b.Navigation("Client");
|
b.Navigation("Client");
|
||||||
|
|
||||||
b.Navigation("ParentSession");
|
b.Navigation("ParentSession");
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using DysonNetwork.Shared.Stream;
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using NATS.Client.Core;
|
using NATS.Client.Core;
|
||||||
using NATS.Net;
|
|
||||||
using Swashbuckle.AspNetCore.Annotations;
|
using Swashbuckle.AspNetCore.Annotations;
|
||||||
using WebSocketPacket = DysonNetwork.Shared.Models.WebSocketPacket;
|
using WebSocketPacket = DysonNetwork.Shared.Models.WebSocketPacket;
|
||||||
|
|
||||||
@@ -40,10 +39,10 @@ public class WebSocketController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var accountId = Guid.Parse(currentUser.Id!);
|
var accountId = Guid.Parse(currentUser.Id!);
|
||||||
var deviceId = currentSession.Challenge?.DeviceId ?? Guid.NewGuid().ToString();
|
var deviceId = currentSession.ClientId;
|
||||||
|
|
||||||
// TODO temporary fix due to the server update
|
if (string.IsNullOrEmpty(deviceId))
|
||||||
if (string.IsNullOrEmpty(deviceId)) deviceId = Guid.NewGuid().ToString().Replace("-", "");
|
return BadRequest("Unable to determine device id");
|
||||||
if (deviceAlt is not null)
|
if (deviceAlt is not null)
|
||||||
deviceId = $"{deviceId}+{deviceAlt}";
|
deviceId = $"{deviceId}+{deviceAlt}";
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ public class NotificationController(
|
|||||||
|
|
||||||
var result =
|
var result =
|
||||||
await nty.SubscribeDevice(
|
await nty.SubscribeDevice(
|
||||||
currentSession.Challenge.DeviceId,
|
currentSession.ClientId,
|
||||||
request.DeviceToken,
|
request.DeviceToken,
|
||||||
request.Provider,
|
request.Provider,
|
||||||
currentUser
|
currentUser
|
||||||
@@ -117,7 +117,7 @@ public class NotificationController(
|
|||||||
var affectedRows = await db.PushSubscriptions
|
var affectedRows = await db.PushSubscriptions
|
||||||
.Where(s =>
|
.Where(s =>
|
||||||
s.AccountId == accountId &&
|
s.AccountId == accountId &&
|
||||||
s.DeviceId == currentSession.Challenge.DeviceId
|
s.DeviceId == currentSession.ClientId
|
||||||
).ExecuteDeleteAsync();
|
).ExecuteDeleteAsync();
|
||||||
return Ok(affectedRows);
|
return Ok(affectedRows);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ public class DysonTokenAuthHandler(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Add scopes as claims
|
// Add scopes as claims
|
||||||
session.Challenge?.Scopes.ToList().ForEach(scope => claims.Add(new Claim("scope", scope)));
|
session.Scopes.ToList().ForEach(scope => claims.Add(new Claim("scope", scope)));
|
||||||
|
|
||||||
// Add superuser claim if applicable
|
// Add superuser claim if applicable
|
||||||
if (session.Account.IsSuperuser)
|
if (session.Account.IsSuperuser)
|
||||||
|
|||||||
@@ -22,7 +22,8 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.3.0" />
|
<PackageReference Include="MaxMind.GeoIP2" Version="5.3.0" />
|
||||||
<PackageReference Include="MessagePack" Version="2.5.192" />
|
<PackageReference Include="MessagePack" Version="3.1.4" />
|
||||||
|
<PackageReference Include="MessagePack.NodaTime" Version="3.5.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.3.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.3.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.0" />
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public static class Extensions
|
|||||||
return RedLockFactory.Create(new List<RedLockMultiplexer> { new(mux) });
|
return RedLockFactory.Create(new List<RedLockMultiplexer> { new(mux) });
|
||||||
});
|
});
|
||||||
builder.Services.AddSingleton<ICacheService, CacheServiceRedis>();
|
builder.Services.AddSingleton<ICacheService, CacheServiceRedis>();
|
||||||
builder.Services.AddSingleton<ICacheSerializer, JsonCacheSerializer>();
|
builder.Services.AddSingleton<ICacheSerializer, MessagePackCacheSerializer>();
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,24 +2,34 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using DysonNetwork.Shared.GeoIp;
|
using DysonNetwork.Shared.GeoIp;
|
||||||
|
using DysonNetwork.Shared.Proto;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using NodaTime.Serialization.Protobuf;
|
using NodaTime.Serialization.Protobuf;
|
||||||
|
|
||||||
namespace DysonNetwork.Shared.Models;
|
namespace DysonNetwork.Shared.Models;
|
||||||
|
|
||||||
|
public enum SessionType
|
||||||
|
{
|
||||||
|
Login,
|
||||||
|
OAuth, // Trying to authorize other platforms
|
||||||
|
Oidc // Trying to connect other platforms
|
||||||
|
}
|
||||||
|
|
||||||
public class SnAuthSession : ModelBase
|
public class SnAuthSession : ModelBase
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; } = Guid.NewGuid();
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
public SessionType Type { get; set; } = SessionType.Login;
|
||||||
public Instant? LastGrantedAt { get; set; }
|
public Instant? LastGrantedAt { get; set; }
|
||||||
public Instant? ExpiredAt { get; set; }
|
public Instant? ExpiredAt { get; set; }
|
||||||
|
|
||||||
|
[Column(TypeName = "jsonb")] public List<string> Audiences { get; set; } = [];
|
||||||
|
[Column(TypeName = "jsonb")] public List<string> Scopes { get; set; } = [];
|
||||||
|
[MaxLength(128)] public string? IpAddress { get; set; }
|
||||||
|
[MaxLength(512)] public string? UserAgent { get; set; }
|
||||||
|
|
||||||
public Guid AccountId { get; set; }
|
public Guid AccountId { get; set; }
|
||||||
[JsonIgnore] public SnAccount Account { get; set; } = null!;
|
[JsonIgnore] public SnAccount Account { get; set; } = null!;
|
||||||
|
|
||||||
// The challenge that created this session
|
|
||||||
public Guid? ChallengeId { get; set; }
|
|
||||||
public SnAuthChallenge? Challenge { get; set; } = null!;
|
|
||||||
|
|
||||||
// The client device for this session
|
// The client device for this session
|
||||||
public Guid? ClientId { get; set; }
|
public Guid? ClientId { get; set; }
|
||||||
public SnAuthClient? Client { get; set; } = null!;
|
public SnAuthClient? Client { get; set; } = null!;
|
||||||
@@ -28,30 +38,41 @@ public class SnAuthSession : ModelBase
|
|||||||
public Guid? ParentSessionId { get; set; }
|
public Guid? ParentSessionId { get; set; }
|
||||||
public SnAuthSession? ParentSession { get; set; }
|
public SnAuthSession? ParentSession { get; set; }
|
||||||
|
|
||||||
|
// The origin challenge for this session
|
||||||
|
public Guid? ChallengeId { get; set; }
|
||||||
|
|
||||||
// Indicates the session is for an OIDC connection
|
// Indicates the session is for an OIDC connection
|
||||||
public Guid? AppId { get; set; }
|
public Guid? AppId { get; set; }
|
||||||
|
|
||||||
public Proto.AuthSession ToProtoValue() => new()
|
public AuthSession ToProtoValue()
|
||||||
{
|
{
|
||||||
Id = Id.ToString(),
|
var proto = new AuthSession
|
||||||
LastGrantedAt = LastGrantedAt?.ToTimestamp(),
|
{
|
||||||
ExpiredAt = ExpiredAt?.ToTimestamp(),
|
Id = Id.ToString(),
|
||||||
AccountId = AccountId.ToString(),
|
LastGrantedAt = LastGrantedAt?.ToTimestamp(),
|
||||||
Account = Account.ToProtoValue(),
|
Type = Type switch
|
||||||
ChallengeId = ChallengeId.ToString(),
|
{
|
||||||
Challenge = Challenge?.ToProtoValue(),
|
SessionType.Login => Proto.SessionType.Login,
|
||||||
ClientId = ClientId.ToString(),
|
SessionType.OAuth => Proto.SessionType.Oauth,
|
||||||
Client = Client?.ToProtoValue(),
|
SessionType.Oidc => Proto.SessionType.Oidc,
|
||||||
ParentSessionId = ParentSessionId.ToString(),
|
_ => Proto.SessionType.ChallengeTypeUnspecified
|
||||||
AppId = AppId?.ToString()
|
},
|
||||||
};
|
IpAddress = IpAddress,
|
||||||
}
|
UserAgent = UserAgent,
|
||||||
|
ExpiredAt = ExpiredAt?.ToTimestamp(),
|
||||||
|
AccountId = AccountId.ToString(),
|
||||||
|
Account = Account.ToProtoValue(),
|
||||||
|
ClientId = ClientId.ToString(),
|
||||||
|
Client = Client?.ToProtoValue(),
|
||||||
|
ParentSessionId = ParentSessionId.ToString(),
|
||||||
|
AppId = AppId?.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
public enum ChallengeType
|
proto.Audiences.AddRange(Audiences);
|
||||||
{
|
proto.Scopes.AddRange(Scopes);
|
||||||
Login,
|
|
||||||
OAuth, // Trying to authorize other platforms
|
return proto;
|
||||||
Oidc // Trying to connect other platforms
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ClientPlatform
|
public enum ClientPlatform
|
||||||
@@ -72,10 +93,9 @@ public class SnAuthChallenge : ModelBase
|
|||||||
public int StepRemain { get; set; }
|
public int StepRemain { get; set; }
|
||||||
public int StepTotal { get; set; }
|
public int StepTotal { get; set; }
|
||||||
public int FailedAttempts { get; set; }
|
public int FailedAttempts { get; set; }
|
||||||
public ChallengeType Type { get; set; } = ChallengeType.Login;
|
[Column(TypeName = "jsonb")] public List<Guid> BlacklistFactors { get; set; } = [];
|
||||||
[Column(TypeName = "jsonb")] public List<Guid> BlacklistFactors { get; set; } = new();
|
[Column(TypeName = "jsonb")] public List<string> Audiences { get; set; } = [];
|
||||||
[Column(TypeName = "jsonb")] public List<string> Audiences { get; set; } = new();
|
[Column(TypeName = "jsonb")] public List<string> Scopes { get; set; } = [];
|
||||||
[Column(TypeName = "jsonb")] public List<string> Scopes { get; set; } = new();
|
|
||||||
[MaxLength(128)] public string? IpAddress { get; set; }
|
[MaxLength(128)] public string? IpAddress { get; set; }
|
||||||
[MaxLength(512)] public string? UserAgent { get; set; }
|
[MaxLength(512)] public string? UserAgent { get; set; }
|
||||||
[MaxLength(512)] public string DeviceId { get; set; } = null!;
|
[MaxLength(512)] public string DeviceId { get; set; } = null!;
|
||||||
@@ -93,14 +113,13 @@ public class SnAuthChallenge : ModelBase
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Proto.AuthChallenge ToProtoValue() => new()
|
public AuthChallenge ToProtoValue() => new()
|
||||||
{
|
{
|
||||||
Id = Id.ToString(),
|
Id = Id.ToString(),
|
||||||
ExpiredAt = ExpiredAt?.ToTimestamp(),
|
ExpiredAt = ExpiredAt?.ToTimestamp(),
|
||||||
StepRemain = StepRemain,
|
StepRemain = StepRemain,
|
||||||
StepTotal = StepTotal,
|
StepTotal = StepTotal,
|
||||||
FailedAttempts = FailedAttempts,
|
FailedAttempts = FailedAttempts,
|
||||||
Type = (Proto.ChallengeType)Type,
|
|
||||||
BlacklistFactors = { BlacklistFactors.Select(x => x.ToString()) },
|
BlacklistFactors = { BlacklistFactors.Select(x => x.ToString()) },
|
||||||
Audiences = { Audiences },
|
Audiences = { Audiences },
|
||||||
Scopes = { Scopes },
|
Scopes = { Scopes },
|
||||||
|
|||||||
@@ -17,12 +17,15 @@ message AuthSession {
|
|||||||
optional google.protobuf.Timestamp expired_at = 4;
|
optional google.protobuf.Timestamp expired_at = 4;
|
||||||
string account_id = 5;
|
string account_id = 5;
|
||||||
Account account = 6;
|
Account account = 6;
|
||||||
string challenge_id = 7;
|
|
||||||
AuthChallenge challenge = 8;
|
|
||||||
google.protobuf.StringValue app_id = 9;
|
google.protobuf.StringValue app_id = 9;
|
||||||
optional string client_id = 10;
|
optional string client_id = 10;
|
||||||
optional string parent_session_id = 11;
|
optional string parent_session_id = 11;
|
||||||
AuthClient client = 12;
|
AuthClient client = 12;
|
||||||
|
repeated string audiences = 13;
|
||||||
|
repeated string scopes = 14;
|
||||||
|
google.protobuf.StringValue ip_address = 15;
|
||||||
|
google.protobuf.StringValue user_agent = 16;
|
||||||
|
SessionType type = 17;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Represents an authentication challenge
|
// Represents an authentication challenge
|
||||||
@@ -32,7 +35,6 @@ message AuthChallenge {
|
|||||||
int32 step_remain = 3;
|
int32 step_remain = 3;
|
||||||
int32 step_total = 4;
|
int32 step_total = 4;
|
||||||
int32 failed_attempts = 5;
|
int32 failed_attempts = 5;
|
||||||
ChallengeType type = 7;
|
|
||||||
repeated string blacklist_factors = 8;
|
repeated string blacklist_factors = 8;
|
||||||
repeated string audiences = 9;
|
repeated string audiences = 9;
|
||||||
repeated string scopes = 10;
|
repeated string scopes = 10;
|
||||||
@@ -56,7 +58,7 @@ message AuthClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enum for challenge types
|
// Enum for challenge types
|
||||||
enum ChallengeType {
|
enum SessionType {
|
||||||
CHALLENGE_TYPE_UNSPECIFIED = 0;
|
CHALLENGE_TYPE_UNSPECIFIED = 0;
|
||||||
LOGIN = 1;
|
LOGIN = 1;
|
||||||
OAUTH = 2;
|
OAUTH = 2;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConcurrentDictionary_00602_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1f443143201742669eeb211a435e32ae4c600_003F24_003F59c4e69f_003FConcurrentDictionary_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConcurrentDictionary_00602_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1f443143201742669eeb211a435e32ae4c600_003F24_003F59c4e69f_003FConcurrentDictionary_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConnectionMultiplexer_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F2ed0e2f073b1d77b98dadb822da09ee8a9dfb91bf29bf2bbaecb8750d7e74cc9_003FConnectionMultiplexer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConnectionMultiplexer_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F2ed0e2f073b1d77b98dadb822da09ee8a9dfb91bf29bf2bbaecb8750d7e74cc9_003FConnectionMultiplexer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AContainerResourceBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F19256b6d2a8a458692f07fe8d98d79e9161628_003Fd7_003F266d041b_003FContainerResourceBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AContainerResourceBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F19256b6d2a8a458692f07fe8d98d79e9161628_003Fd7_003F266d041b_003FContainerResourceBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AContractlessStandardResolver_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0fb7a5f343ed4578a15c716986a3f17950c00_003F78_003F264ef090_003FContractlessStandardResolver_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AControllerBase_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003Ff6_003Fdf150bb3_003FControllerBase_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AControllerBase_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003Ff6_003Fdf150bb3_003FControllerBase_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AController_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb320290c1b964c3e88434ff5505d9086c9a00_003Fdf_003F95b535f9_003FController_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AController_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb320290c1b964c3e88434ff5505d9086c9a00_003Fdf_003F95b535f9_003FController_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACookieOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F663f33943e4c4e889dc7050c1e97e703e000_003F89_003Fb06980d7_003FCookieOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACookieOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F663f33943e4c4e889dc7050c1e97e703e000_003F89_003Fb06980d7_003FCookieOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
@@ -107,6 +108,7 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMarkdownExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F941558ce147e451baff49677c95dde8d75000_003F67_003F8c0cc1a5_003FMarkdownExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMarkdownExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F941558ce147e451baff49677c95dde8d75000_003F67_003F8c0cc1a5_003FMarkdownExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMarkdownExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F5cf0a036d23b7f3f04eba9a3fe5c9b5dfb37a2b8c23602bc5176317374cb1c_003FMarkdownExtensions_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMarkdownExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F5cf0a036d23b7f3f04eba9a3fe5c9b5dfb37a2b8c23602bc5176317374cb1c_003FMarkdownExtensions_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMediaAnalysis_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffef366b36a224d469ff150d30f9a866d23c00_003Fd7_003F5c138865_003FMediaAnalysis_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMediaAnalysis_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffef366b36a224d469ff150d30f9a866d23c00_003Fd7_003F5c138865_003FMediaAnalysis_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMessagePackSerializerOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0fb7a5f343ed4578a15c716986a3f17950c00_003Ffc_003F34de33e4_003FMessagePackSerializerOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMicrosoftDependencyInjectionJobFactory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1edbd6e24d7b430fabce72177269baa19200_003Fa8_003F91b091de_003FMicrosoftDependencyInjectionJobFactory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMicrosoftDependencyInjectionJobFactory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1edbd6e24d7b430fabce72177269baa19200_003Fa8_003F91b091de_003FMicrosoftDependencyInjectionJobFactory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANodaExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F472c5156daf14d04a5b30e00cd80dbf85ac0_003Ff4_003F9e841463_003FNodaExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANodaExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F472c5156daf14d04a5b30e00cd80dbf85ac0_003Ff4_003F9e841463_003FNodaExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANotFoundResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F28_003F290250f5_003FNotFoundResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANotFoundResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F28_003F290250f5_003FNotFoundResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
|||||||
Reference in New Issue
Block a user