add basic suppport for client_credentials oauth grant

This commit is contained in:
sam 2024-05-20 17:00:21 +02:00
parent 049f4a56de
commit 8995213d26
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
20 changed files with 627 additions and 58 deletions

View file

@ -0,0 +1,45 @@
using Foxchat.Core;
using Foxchat.Core.Models.Http;
using Foxchat.Identity.Authorization;
using Foxchat.Identity.Database;
using Foxchat.Identity.Database.Models;
using Microsoft.AspNetCore.Mvc;
namespace Foxchat.Identity.Controllers.Oauth;
[ApiController]
[Authenticate]
[Route("/_fox/ident/oauth/apps")]
public class AppsController(ILogger logger, IdentityContext db) : ControllerBase
{
[HttpPost]
public async Task<IActionResult> CreateApplication([FromBody] Apps.CreateRequest req)
{
var app = Application.Create(req.Name, req.Scopes, req.RedirectUris);
await db.AddAsync(app);
await db.SaveChangesAsync();
logger.Information("Created new application {Name} with ID {Id} and client ID {ClientId}", app.Name, app.Id, app.ClientId);
return Ok(new Apps.CreateResponse(
app.Id, app.ClientId, app.ClientSecret, app.Name, app.Scopes, app.RedirectUris
));
}
[HttpGet]
public IActionResult GetSelfApp([FromQuery(Name = "with_secret")] bool withSecret)
{
var token = HttpContext.GetToken();
if (token is not { Account: null }) throw new ApiError.Forbidden("This endpoint requires a client token.");
var app = token.Application;
return Ok(new Apps.GetSelfResponse(
app.Id,
app.ClientId,
withSecret ? app.ClientSecret : null,
app.Name,
app.Scopes,
app.RedirectUris
));
}
}

View file

@ -0,0 +1,73 @@
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<IActionResult> PostToken([FromBody] PostTokenRequest req)
{
var app = await db.GetApplicationAsync(req.ClientId, req.ClientSecret);
var scopes = req.Scope.Split(' ');
if (app.Scopes.Except(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<IActionResult> 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
);
}