using System.Security.Claims; using Microsoft.Extensions.Options; using MxGateway.Server.Configuration; using MxGateway.Server.Security.Authentication; using MxGateway.Server.Security.Authorization; namespace MxGateway.Server.Dashboard; public sealed class DashboardAuthenticator( IApiKeyVerifier apiKeyVerifier, IOptions options) : IDashboardAuthenticator { private const string GenericFailureMessage = "The API key is invalid or is not authorized for dashboard access."; public async Task AuthenticateAsync( string? apiKey, CancellationToken cancellationToken) { if (options.Value.Authentication.Mode == AuthenticationMode.Disabled) { return DashboardAuthenticationResult.Success(CreatePrincipal(new ApiKeyIdentity( KeyId: "authentication-disabled", KeyPrefix: "authentication-disabled", DisplayName: "Authentication Disabled", Scopes: new HashSet([GatewayScopes.Admin], StringComparer.Ordinal)))); } if (string.IsNullOrWhiteSpace(apiKey)) { return DashboardAuthenticationResult.Fail(GenericFailureMessage); } ApiKeyVerificationResult verificationResult = await apiKeyVerifier .VerifyAsync(FormatAuthorizationHeader(apiKey), cancellationToken) .ConfigureAwait(false); if (!verificationResult.Succeeded || verificationResult.Identity is null) { return DashboardAuthenticationResult.Fail(GenericFailureMessage); } if (options.Value.Dashboard.RequireAdminScope && !verificationResult.Identity.Scopes.Contains(GatewayScopes.Admin)) { return DashboardAuthenticationResult.Fail(GenericFailureMessage); } return DashboardAuthenticationResult.Success(CreatePrincipal(verificationResult.Identity)); } private static string FormatAuthorizationHeader(string apiKey) { string trimmedApiKey = apiKey.Trim(); return trimmedApiKey.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase) ? trimmedApiKey : $"Bearer {trimmedApiKey}"; } private static ClaimsPrincipal CreatePrincipal(ApiKeyIdentity identity) { List claims = [ new Claim(ClaimTypes.NameIdentifier, identity.KeyId), new Claim(ClaimTypes.Name, identity.DisplayName), new Claim(DashboardAuthenticationDefaults.KeyPrefixClaimType, identity.KeyPrefix) ]; claims.AddRange(identity.Scopes.Select(scope => new Claim( DashboardAuthenticationDefaults.ScopeClaimType, scope))); ClaimsIdentity claimsIdentity = new( claims, DashboardAuthenticationDefaults.AuthenticationScheme, ClaimTypes.Name, DashboardAuthenticationDefaults.ScopeClaimType); return new ClaimsPrincipal(claimsIdentity); } }