using Foxchat.Core; using Foxchat.Identity.Database; using Foxchat.Identity.Database.Models; using Microsoft.AspNetCore.Mvc; using NodaTime; namespace Foxchat.Identity.Controllers.Oauth; [ApiController] [Route("/_fox/ident/oauth/token")] public class TokenController(ILogger logger, IdentityContext db, IClock clock) : ControllerBase { [HttpPost] public async Task PostToken([FromBody] PostTokenRequest req) { var app = await db.GetApplicationAsync(req.ClientId, req.ClientSecret); var scopes = req.Scope.Split(' '); if (scopes.Except(app.Scopes).Any()) { throw new ApiError.BadRequest("Invalid or unauthorized scopes"); } switch (req.GrantType) { case "client_credentials": return await HandleClientCredentialsAsync(app, scopes); case "authorization_code": break; default: throw new ApiError.BadRequest("Unknown grant_type"); } throw new NotImplementedException(); } private async Task HandleClientCredentialsAsync(Application app, string[] scopes) { // TODO: make this configurable var expiry = clock.GetCurrentInstant() + Duration.FromDays(365); var (token, hash) = Token.Generate(); var tokenObj = new Token { Hash = hash, Scopes = scopes, Expires = expiry, ApplicationId = app.Id }; await db.AddAsync(tokenObj); await db.SaveChangesAsync(); logger.Debug("Created token with scopes {Scopes} for application {ApplicationId}", scopes, app.Id); return Ok(new PostTokenResponse(token, scopes, expiry)); } public record PostTokenRequest( string GrantType, string ClientId, string ClientSecret, string Scope, // Optional parameters string? Code, string? RedirectUri ); public record PostTokenResponse( string Token, string[] Scopes, Instant Expires ); }