add basic suppport for client_credentials oauth grant
This commit is contained in:
parent
049f4a56de
commit
8995213d26
20 changed files with 627 additions and 58 deletions
|
@ -1,5 +1,8 @@
|
|||
using System.Security.Cryptography;
|
||||
using Foxchat.Core;
|
||||
using Foxchat.Identity.Utils;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Foxchat.Identity.Database.Models;
|
||||
|
||||
|
@ -9,38 +12,47 @@ public class Application : BaseModel
|
|||
public required string ClientSecret { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public required string[] Scopes { get; init; }
|
||||
public required string[] RedirectUris { get; set; }
|
||||
|
||||
public static Application Create(string name, string[] scopes)
|
||||
public static Application Create(string name, string[] scopes, string[] redirectUrls)
|
||||
{
|
||||
var clientId = RandomNumberGenerator.GetHexString(16, true);
|
||||
var clientId = RandomNumberGenerator.GetHexString(32, true);
|
||||
var clientSecretBytes = RandomNumberGenerator.GetBytes(48);
|
||||
var clientSecret = WebEncoders.Base64UrlEncode(clientSecretBytes);
|
||||
|
||||
if (!scopes.All(s => Scope.ValidScopes.Contains(s)))
|
||||
if (scopes.Any(s => !OauthUtils.Scopes.Contains(s)))
|
||||
{
|
||||
throw new ArgumentException("Invalid scopes passed to Application.Create", nameof(scopes));
|
||||
}
|
||||
|
||||
if (redirectUrls.Any(s => !OauthUtils.ValidateRedirectUri(s)))
|
||||
{
|
||||
throw new ArgumentException("Invalid redirect URLs passed to Application.Create", nameof(redirectUrls));
|
||||
}
|
||||
|
||||
return new Application
|
||||
{
|
||||
ClientId = clientId,
|
||||
ClientSecret = clientSecret,
|
||||
Name = name,
|
||||
Scopes = scopes,
|
||||
RedirectUris = redirectUrls
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class Scope
|
||||
public static class ContextApplicationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth scope for identifying a user and nothing else.
|
||||
/// </summary>
|
||||
public const string Identity = "identity";
|
||||
/// <summary>
|
||||
/// OAuth scope for a full chat client. This grants *full access* to an account.
|
||||
/// </summary>
|
||||
public const string ChatClient = "chat_client";
|
||||
public static async Task<Application> GetApplicationAsync(this IdentityContext db, string clientId)
|
||||
{
|
||||
return await db.Applications.FirstOrDefaultAsync(a => a.ClientId == clientId)
|
||||
?? throw new ApiError.Unauthorized("Invalid client ID or client secret");
|
||||
}
|
||||
|
||||
public static readonly string[] ValidScopes = [Identity, ChatClient];
|
||||
public static async Task<Application> GetApplicationAsync(this IdentityContext db, string clientId, string clientSecret)
|
||||
{
|
||||
var app = await db.GetApplicationAsync(clientId);
|
||||
if (app.ClientSecret != clientSecret) throw new ApiError.Unauthorized("Invalid client ID or client secret");
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,8 +10,9 @@ public class Token : BaseModel
|
|||
public string[] Scopes { get; set; } = [];
|
||||
public Instant Expires { get; set; }
|
||||
|
||||
public Ulid AccountId { get; set; }
|
||||
public Account Account { get; set; } = null!;
|
||||
// Tokens can be granted directly to applications with `client_credentials`
|
||||
public Ulid? AccountId { get; set; }
|
||||
public Account? Account { get; set; }
|
||||
|
||||
public Ulid ApplicationId { get; set; }
|
||||
public Application Application { get; set; } = null!;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue