using Femto.Common.Domain; using Femto.Common.Infrastructure.DbConnection; using Femto.Modules.Auth.Application.Dto; using Femto.Modules.Auth.Data; using Femto.Modules.Auth.Errors; using Femto.Modules.Auth.Models; using Microsoft.EntityFrameworkCore; namespace Femto.Modules.Auth.Application.Interface.ValidateSession; internal class ValidateSessionCommandHandler(AuthContext context) : ICommandHandler { public async Task Handle( ValidateSessionCommand request, CancellationToken cancellationToken ) { try { return new ValidateSessionResult(await DoSessionValidation(request, cancellationToken)); } finally { await context.SaveChangesAsync(cancellationToken); } } private async Task DoSessionValidation( ValidateSessionCommand request, CancellationToken cancellationToken ) { var now = DateTimeOffset.UtcNow; var session = await context.Sessions.SingleOrDefaultAsync( s => s.Id == request.SessionId, cancellationToken ); var rememberMe = request.RememberMe; if (session is null) { (session, rememberMe) = await this.TryAuthenticateWithRememberMeToken( request.User, request.RememberMe, cancellationToken ); } if (session.UserId != request.User.Id) { context.Remove(session); throw new InvalidSessionError(); } if (session.Expires < now) { context.Remove(session); throw new InvalidSessionError(); } if (session.ShouldRefresh) { context.Remove(session); session = Session.Weak(session.UserId); await context.AddAsync(session, cancellationToken); } return new SessionDto(session, rememberMe); } private async Task<(Session, string)> TryAuthenticateWithRememberMeToken( UserInfo user, string? rememberMeToken, CancellationToken cancellationToken ) { if (rememberMeToken is null) throw new InvalidSessionError(); var parts = rememberMeToken.Split('.'); if (parts.Length != 2) throw new InvalidSessionError(); var selector = parts[0]; var verifier = parts[1]; var longTermSession = await context.LongTermSessions.SingleOrDefaultAsync( s => s.Selector == selector, cancellationToken ); if (longTermSession is null) throw new InvalidSessionError(); context.Remove(longTermSession); if (!longTermSession.Validate(verifier)) throw new InvalidSessionError(); var session = Session.Weak(user.Id); await context.AddAsync(session, cancellationToken); (longTermSession, rememberMeToken) = LongTermSession.Create(user.Id); await context.AddAsync(longTermSession, cancellationToken); return (session, rememberMeToken); } }