using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using JobTrackerApi.Data; using Microsoft.EntityFrameworkCore; namespace JobTrackerApi.Services; public static class SessionRevocation { public static async Task RevokeCurrentAsync(JobTrackerContext db, string userId, string sessionId, CancellationToken cancellationToken) { var session = await db.UserSessions.IgnoreQueryFilters() .FirstOrDefaultAsync(x => x.Id == sessionId && x.UserId == userId && x.RevokedAtUtc == null, cancellationToken); if (session is null) return; session.RevokedAtUtc = DateTimeOffset.UtcNow; await db.SaveChangesAsync(cancellationToken); } public static async Task RevokeAllAsync(JobTrackerContext db, string userId, string? trustedDeviceHashToKeep, CancellationToken cancellationToken) { var sessions = await db.UserSessions.IgnoreQueryFilters() .Where(x => x.UserId == userId && x.RevokedAtUtc == null) .ToListAsync(cancellationToken); var devices = await db.TrustedDevices.IgnoreQueryFilters() .Where(x => x.UserId == userId && (trustedDeviceHashToKeep == null || x.TokenHash != trustedDeviceHashToKeep)) .ToListAsync(cancellationToken); var now = DateTimeOffset.UtcNow; foreach (var session in sessions) session.RevokedAtUtc = now; db.TrustedDevices.RemoveRange(devices); if (sessions.Count > 0 || devices.Count > 0) await db.SaveChangesAsync(cancellationToken); } public static bool TryReadIdentity(ClaimsPrincipal principal, string? cookieToken, out string userId, out string sessionId) { userId = principal.FindFirstValue(ClaimTypes.NameIdentifier) ?? principal.FindFirstValue("sub") ?? ""; sessionId = principal.FindFirstValue("sid") ?? ""; if (userId.Length > 0 && sessionId.Length > 0) return true; if (string.IsNullOrWhiteSpace(cookieToken) || cookieToken.Length > 16_384) return false; var handler = new JwtSecurityTokenHandler { MapInboundClaims = false }; if (!handler.CanReadToken(cookieToken)) return false; try { var token = handler.ReadJwtToken(cookieToken); userId = token.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier || x.Type == "sub")?.Value ?? ""; sessionId = token.Claims.FirstOrDefault(x => x.Type == "sid")?.Value ?? ""; return userId.Length > 0 && sessionId.Length > 0; } catch { return false; } } }