Implement dashboard authentication
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
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<GatewayOptions> options) : IDashboardAuthenticator
|
||||
{
|
||||
private const string GenericFailureMessage = "The API key is invalid or is not authorized for dashboard access.";
|
||||
|
||||
public async Task<DashboardAuthenticationResult> 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<string>([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<Claim> 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user