using LanguageExt.Common; using MediatR; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using W542.GandalfReborn.Commands; using W542.GandalfReborn.Data.Database.Repositories; using W542.GandalfReborn.Data.Dto; using W542.GandalfReborn.Data.Entities.Subject.SignIn; using W542.GandalfReborn.Extensions; using W542.GandalfReborn.Handlers.Commands; namespace W542.GandalfReborn.Handlers.Security; public class AuthCodeRequestCommandHandler(IPasswordHasher passwordHasher, ISubjectRepository subjectRepository, IMediator mediator) : IGrCommandHandler> { private static readonly object MicrosoftIsAMeme = new(); public async Task> Handle(AuthCodeRequestCommand command, CancellationToken cancellationToken) { var subjectResult = await subjectRepository.GetSingle(query => query .Where(subject => subject.Name == command.UsernameOrEmail || subject.SignInMethods.Any(signIn => signIn.Method == SignInMethod.Simple && signIn.Email == command.UsernameOrEmail)) .Include(x => x.SignInMethods) ); if (subjectResult.IsFaulted) { return $"User {command.UsernameOrEmail} does not exist.".AsErrorResult(); } var subject = subjectResult.GetValue(); var simpleSignIn = subject.SignInMethods.SingleOrDefault(x => x.Method == SignInMethod.Simple); if (simpleSignIn?.PasswordHash is null) { return "User does not support simple password.".AsErrorResult(); } var verification = passwordHasher.VerifyHashedPassword(MicrosoftIsAMeme, simpleSignIn.PasswordHash, command.Password); switch (verification) { case PasswordVerificationResult.Failed: return "Wrong Password.".AsErrorResult(); case PasswordVerificationResult.Success: return await mediator.Send(new CreateAuthCodeCommand { SubjectId = subject.Id!.Value, CodeChallenge = command.CodeChallenge, Algorithm = command.Algorithm }, cancellationToken); case PasswordVerificationResult.SuccessRehashNeeded: var newHashResult = await mediator.Send(new HashPasswordCommand { RawPassword = command.Password }, cancellationToken); if (newHashResult.IsFaulted) { return newHashResult.AsErrorResult(); } var newHash = newHashResult.GetValue(); simpleSignIn.PasswordHash = newHash; await subjectRepository.SaveChanges(); return await mediator.Send(new CreateAuthCodeCommand { SubjectId = subject.Id!.Value, CodeChallenge = command.CodeChallenge, Algorithm = command.Algorithm }, cancellationToken); default: return $"Password verification type not supported: {verification}".AsErrorResult(); } } }