using Couchbase.Lite; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using WorkTracker.Configuration; using WorkTracker.Services.Storage; namespace WorkTracker.Services.Auth; public sealed class CouchbaseLiteAuthService : IAuthService { private readonly Collection users; private readonly PasswordHasher passwordHasher = new(); private readonly IOptions options; private readonly ILogger logger; public CouchbaseLiteAuthService( CouchbaseLiteDatabaseProvider databaseProvider, IOptions options, ILogger logger) { users = databaseProvider.Users; this.options = options; this.logger = logger; } public Task EnsureSeedUserAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var email = options.Value.Email.Trim(); var normalizedEmail = NormalizeEmail(email); if (users.GetDocument(normalizedEmail) is not null) { return Task.CompletedTask; } var user = new AuthUser { Id = normalizedEmail, Email = email, EmailNormalized = normalizedEmail, PasswordHash = string.Empty }; var passwordHash = passwordHasher.HashPassword(user, options.Value.Password); var userToCreate = new AuthUser { Id = normalizedEmail, Email = email, EmailNormalized = normalizedEmail, PasswordHash = passwordHash }; SaveUser(userToCreate); logger.LogInformation("Seeded single user account {Email} in Couchbase Lite", email); return Task.CompletedTask; } public Task ValidateCredentialsAsync(string email, string password, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var user = GetUser(NormalizeEmail(email)); if (user is null) { return Task.FromResult(null); } var result = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password); return Task.FromResult(result == PasswordVerificationResult.Failed ? null : user); } private AuthUser? GetUser(string normalizedEmail) { var document = users.GetDocument(normalizedEmail); if (document is null) { return null; } return new AuthUser { Id = document.Id, Email = document.GetString("email") ?? string.Empty, EmailNormalized = document.GetString("emailNormalized") ?? string.Empty, PasswordHash = document.GetString("passwordHash") ?? string.Empty }; } private void SaveUser(AuthUser user) { var document = new MutableDocument(user.Id); document.SetString("email", user.Email); document.SetString("emailNormalized", user.EmailNormalized); document.SetString("passwordHash", user.PasswordHash); users.Save(document); } private static string NormalizeEmail(string email) => email.Trim().ToUpperInvariant(); }