using System.Security.Cryptography; using Foxchat.Core; using Foxchat.Identity.Utils; using Microsoft.AspNetCore.WebUtilities; using Microsoft.EntityFrameworkCore; namespace Foxchat.Identity.Database.Models; public class Application : BaseModel { public required string ClientId { get; init; } 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, string[] redirectUrls) { var clientId = RandomNumberGenerator.GetHexString(32, true); var clientSecretBytes = RandomNumberGenerator.GetBytes(48); var clientSecret = WebEncoders.Base64UrlEncode(clientSecretBytes); 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 ContextApplicationExtensions { public static async Task 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 async Task 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; } }