using System.Security.Claims; using JobTrackerApi.Data; using Microsoft.EntityFrameworkCore; namespace JobTrackerApi.Services; // The actual revocation check behind Program.cs's "local" JwtBearer OnTokenValidated. Pulled out // of Program.cs so it's unit-testable without standing up a full TestServer/HTTP pipeline. public static class LocalSessionValidator { public static async Task IsValidAsync(JobTrackerContext db, ClaimsPrincipal? principal, DateTimeOffset now, bool requireConfirmedEmail = false, CancellationToken cancellationToken = default) { var sid = principal?.FindFirst("sid")?.Value; var userId = principal is null ? null : LocalAuthIdentity.GetRequiredUserId(principal); // Fail closed: see the comment on the OnTokenValidated wiring in Program.cs for why a // missing sid is rejected rather than grandfathered in. if (string.IsNullOrWhiteSpace(sid) || string.IsNullOrWhiteSpace(userId)) return false; var session = await db.UserSessions.IgnoreQueryFilters() .FirstOrDefaultAsync(x => x.Id == sid && x.UserId == userId, cancellationToken); if (session is null || session.RevokedAtUtc is not null || session.ExpiresAtUtc <= now) return false; if (requireConfirmedEmail && !await db.Users.IgnoreQueryFilters().AnyAsync(x => x.Id == userId && x.EmailConfirmed, cancellationToken)) return false; if (now - session.LastSeenAtUtc > TimeSpan.FromMinutes(5)) { session.LastSeenAtUtc = now; await db.SaveChangesAsync(cancellationToken); } return true; } }