using Foxchat.Identity.Middleware; using Foxchat.Identity.Database; using Foxchat.Identity.Utils; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Identity; using Foxchat.Identity.Database.Models; using Foxchat.Core; using System.Diagnostics; using NodaTime; namespace Foxchat.Identity.Controllers.Oauth; [ApiController] [Authenticate] [Route("/_fox/ident/oauth/password")] public class PasswordAuthController(ILogger logger, IdentityContext db, IClock clock) : ControllerBase { private readonly PasswordHasher _passwordHasher = new(); [HttpPost("register")] public async Task Register([FromBody] RegisterRequest req) { var app = HttpContext.GetApplicationOrThrow(); var appToken = HttpContext.GetToken() ?? throw new UnreachableException(); // GetApplicationOrThrow already gets the token and throws if it's null if (req.Scopes.Except(appToken.Scopes).Any()) throw new ApiError.Forbidden("Cannot request token scopes that are not allowed for this token", req.Scopes.Except(appToken.Scopes)); var acct = new Account { Username = req.Username, Email = req.Email, Role = Account.AccountRole.User }; await db.AddAsync(acct); var hashedPassword = await Task.Run(() => _passwordHasher.HashPassword(acct, req.Password)); acct.Password = hashedPassword; var (tokenStr, token) = Token.Create(acct, app, req.Scopes, clock.GetCurrentInstant() + Duration.FromDays(365)); await db.AddAsync(token); await db.SaveChangesAsync(); return Ok(new RegisterResponse(acct.Id, acct.Username, acct.Email, tokenStr)); } public record RegisterRequest(string Username, string Password, string Email, string[] Scopes); public record RegisterResponse(Ulid Id, string Username, string Email, string Token); }