using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using MongoDB.Driver; using WorkTracker.Configuration; namespace WorkTracker.Services.Auth; public sealed class MongoAuthService : IMongoAuthService { private const string UsersCollectionName = "users"; private readonly IMongoCollection users; private readonly PasswordHasher passwordHasher = new(); private readonly IOptions options; private readonly ILogger logger; public MongoAuthService( IMongoDatabase database, IOptions options, ILogger logger) { users = database.GetCollection(UsersCollectionName); this.options = options; this.logger = logger; } public async Task EnsureSeedUserAsync(CancellationToken cancellationToken) { var email = options.Value.Email.Trim(); var normalizedEmail = NormalizeEmail(email); var existingUser = await users .Find(x => x.EmailNormalized == normalizedEmail) .FirstOrDefaultAsync(cancellationToken); if (existingUser is not null) { return; } var user = new MongoAuthUser { Email = email, EmailNormalized = normalizedEmail, PasswordHash = string.Empty }; var passwordHash = passwordHasher.HashPassword(user, options.Value.Password); var userToCreate = new MongoAuthUser { Email = email, EmailNormalized = normalizedEmail, PasswordHash = passwordHash }; await users.InsertOneAsync(userToCreate, cancellationToken: cancellationToken); logger.LogInformation("Seeded single user account {Email} in MongoDB", email); } public async Task ValidateCredentialsAsync(string email, string password, CancellationToken cancellationToken) { var normalizedEmail = NormalizeEmail(email); var user = await users .Find(x => x.EmailNormalized == normalizedEmail) .FirstOrDefaultAsync(cancellationToken); if (user is null) { return null; } var result = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password); return result == PasswordVerificationResult.Failed ? null : user; } private static string NormalizeEmail(string email) => email.Trim().ToUpperInvariant(); }