namespace ZB.MOM.WW.Auth.Abstractions.ApiKeys; public sealed record ApiKeyOptions { public string TokenPrefix { get; init; } = "mxgw"; public string PepperSecretName { get; init; } = ""; public string SqlitePath { get; init; } = ""; public bool RunMigrationsOnStartup { get; init; } = true; } public enum ApiKeyFailure { MissingOrMalformed, KeyNotFound, KeyRevoked, PepperUnavailable, SecretMismatch, KeyExpired } public sealed record ApiKeyIdentity(string KeyId, string DisplayName, IReadOnlySet Scopes, object? Constraints); public sealed record ApiKeyVerification(bool Succeeded, ApiKeyIdentity? Identity, ApiKeyFailure? Failure); public interface IApiKeyVerifier { Task VerifyAsync(string authorizationHeader, CancellationToken ct); } /// /// As a positional record, (byte[]) participates in equality /// BY REFERENCE. Two records whose SecretHash arrays contain identical bytes are NOT /// considered equal by . Callers must not rely on value /// equality for ; use /// or similar for content comparison. /// public sealed record ApiKeyRecord( string KeyId, string KeyPrefix, byte[] SecretHash, string DisplayName, IReadOnlySet Scopes, string? ConstraintsJson, DateTimeOffset CreatedUtc, DateTimeOffset? LastUsedUtc, DateTimeOffset? RevokedUtc, // Optional absolute expiry. NULL = never expires (the default for every key minted before this // field existed, so upgrading a store leaves existing keys valid). Enforced in ApiKeyVerifier. DateTimeOffset? ExpiresUtc = null); public interface IApiKeyStore { Task FindByKeyIdAsync(string keyId, CancellationToken ct); Task FindActiveByKeyIdAsync(string keyId, CancellationToken ct); Task MarkUsedAsync(string keyId, DateTimeOffset whenUtc, CancellationToken ct); } public sealed record ApiKeyAuditEntry(string? KeyId, string EventType, string? RemoteAddress, DateTimeOffset CreatedUtc, string? Details); /// /// Hash-free projection of an API-key record, safe to enumerate and surface to admins. /// Deliberately omits SecretHash so that listing keys can never leak secret material. /// public sealed record ApiKeyListItem( string KeyId, string KeyPrefix, string DisplayName, IReadOnlySet Scopes, string? ConstraintsJson, DateTimeOffset CreatedUtc, DateTimeOffset? LastUsedUtc, DateTimeOffset? RevokedUtc, // Optional absolute expiry (NULL = never expires); surfaced so admins can see/alert on expiry. DateTimeOffset? ExpiresUtc = null); public interface IApiKeyAdminStore { Task CreateAsync(ApiKeyRecord record, CancellationToken ct); Task RevokeAsync(string keyId, DateTimeOffset whenUtc, CancellationToken ct); Task RotateAsync(string keyId, byte[] newSecretHash, CancellationToken ct); Task DeleteAsync(string keyId, CancellationToken ct); /// Replaces the scope set on an existing key. Does not touch the secret. Returns false if the key does not exist. Task SetScopesAsync(string keyId, IReadOnlySet scopes, CancellationToken ct); /// Enables (clears revoked_utc) or disables (sets revoked_utc) a key WITHOUT changing its secret. Returns false if the key does not exist. Task SetEnabledAsync(string keyId, bool enabled, DateTimeOffset whenUtc, CancellationToken ct); /// /// Enumerates all API keys as hash-free projections, newest first. /// The secret hash is never selected, so callers cannot use this to recover secret material. /// Task> ListAsync(CancellationToken ct); } public interface IApiKeyAuditStore { Task AppendAsync(ApiKeyAuditEntry entry, CancellationToken ct); Task> ListRecentAsync(int limit, CancellationToken ct); }