using System; using System.Security.Claims; using System.IdentityModel.Tokens.Jwt; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using SNote.Models; using SNote.Server.Data; using SNote.Server.Security; namespace SNote.Server.Endpoints; public static class AuthEndpoints { public static void MapAuthEndpoints(this IEndpointRouteBuilder routes) { var group = routes.MapGroup("/api/auth"); group.MapPost("/register", async (RegisterRequest req, HttpContext context, ServerDbContext db, PeerCache peerCache, RsaKeyManager rsaKeyManager, IConfiguration configuration, HttpClient httpClient, IServiceProvider serviceProvider) => { if (string.IsNullOrWhiteSpace(req.Username) || string.IsNullOrWhiteSpace(req.Password) || string.IsNullOrWhiteSpace(req.EncryptedKey)) { return Results.BadRequest("Username, password, and encrypted key are required."); } var existing = await db.Users.AnyAsync(u => u.Username.ToLower() == req.Username.ToLower()); if (existing) { return Results.Conflict("Username already exists."); } var user = new User { Username = req.Username, PasswordHash = PasswordHasher.HashPassword(req.Password), EncryptedKey = req.EncryptedKey }; db.Users.Add(user); await db.SaveChangesAsync(); _ = Task.Run(async () => { try { using var scope = serviceProvider.CreateScope(); var scopeDb = scope.ServiceProvider.GetRequiredService(); var scopeRsa = scope.ServiceProvider.GetRequiredService(); var scopeConfig = scope.ServiceProvider.GetRequiredService(); await SyncEndpoints.BroadcastUserChangeAsync(user.Username, scopeDb, peerCache, scopeRsa, scopeConfig, httpClient); } catch (Exception ex) { Console.WriteLine($"Error broadcasting user registration: {ex.Message}"); } }); return Results.Ok(new { message = "Registration successful." }); }); group.MapPost("/change-password", async (ChangePasswordRequest req, HttpContext context, ServerDbContext db, CertificateManager certManager, PeerCache peerCache, RsaKeyManager rsaKeyManager, IConfiguration configuration, HttpClient httpClient, IServiceProvider serviceProvider) => { var username = AuthHelper.GetAuthenticatedUser(context, certManager); if (string.IsNullOrEmpty(username)) return Results.Unauthorized(); if (string.IsNullOrWhiteSpace(req.OldPassword) || string.IsNullOrWhiteSpace(req.NewPassword) || string.IsNullOrWhiteSpace(req.NewEncryptedKey)) { return Results.BadRequest("Old password, new password, and new encrypted key are required."); } var user = await db.Users.FirstOrDefaultAsync(u => u.Username.ToLower() == username.ToLower()); if (user == null) return Results.NotFound("User not found."); // Verify old password if (!PasswordHasher.VerifyPassword(user.PasswordHash, req.OldPassword)) { return Results.BadRequest("Incorrect old password."); } // Update user properties user.PasswordHash = PasswordHasher.HashPassword(req.NewPassword); user.EncryptedKey = req.NewEncryptedKey; await db.SaveChangesAsync(); // Broadcast the user password change! _ = Task.Run(async () => { try { using var scope = serviceProvider.CreateScope(); var scopeDb = scope.ServiceProvider.GetRequiredService(); var scopeRsa = scope.ServiceProvider.GetRequiredService(); var scopeConfig = scope.ServiceProvider.GetRequiredService(); await SyncEndpoints.BroadcastUserChangeAsync(user.Username, scopeDb, peerCache, scopeRsa, scopeConfig, httpClient); } catch (Exception ex) { Console.WriteLine($"Error broadcasting password change: {ex.Message}"); } }); return Results.Ok(new { message = "Password changed successfully." }); }); group.MapPost("/login", async (LoginRequest req, ServerDbContext db, CertificateManager certManager) => { if (string.IsNullOrWhiteSpace(req.Username) || string.IsNullOrWhiteSpace(req.Password)) { return Results.BadRequest("Username and password are required."); } var user = await db.Users.FirstOrDefaultAsync(u => u.Username.ToLower() == req.Username.ToLower()); if (user == null || !PasswordHasher.VerifyPassword(user.PasswordHash, req.Password)) { return Results.Unauthorized(); } // Generate JWT Token signed by shared certificate var cert = certManager.GetCertificate(); var privateKey = new X509SecurityKey(cert); var claims = new[] { new Claim(ClaimTypes.Name, user.Username), new Claim(ClaimTypes.Role, "User") }; var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(claims), Expires = DateTime.UtcNow.AddDays(7), SigningCredentials = new SigningCredentials(privateKey, SecurityAlgorithms.RsaSha256) }; var tokenHandler = new JwtSecurityTokenHandler(); var token = tokenHandler.CreateToken(tokenDescriptor); var tokenString = tokenHandler.WriteToken(token); return Results.Ok(new LoginResponse(tokenString, user.EncryptedKey)); }); group.MapGet("/encrypted-key/{username}", async (string username, ServerDbContext db) => { var user = await db.Users.FirstOrDefaultAsync(u => u.Username.ToLower() == username.ToLower()); if (user == null) { return Results.NotFound("User not found."); } return Results.Ok(new { encryptedKey = user.EncryptedKey }); }); } } public record RegisterRequest(string Username, string Password, string EncryptedKey); public record LoginRequest(string Username, string Password); public record LoginResponse(string Token, string EncryptedKey); public record ChangePasswordRequest(string OldPassword, string NewPassword, string NewEncryptedKey);