using Foxchat.Core; using Foxchat.Identity.Database; using Foxchat.Identity.Database.Models; using Foxchat.Identity.Utils; 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 appScopes = app.Scopes.ExpandScopes(); var scopes = req.Scope.Split(' '); if (scopes.Except(appScopes).Any()) { throw new ApiError.Forbidden("Invalid or unauthorized scopes", scopes.Except(appScopes)); } switch (req.GrantType) { case OauthUtils.ClientCredentials: return await HandleClientCredentialsAsync(app, scopes); case OauthUtils.AuthorizationCode: // TODO 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, tokenObj) = Token.Create(null, app, scopes, expiry); db.Add(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 ); }