feat(auth.apikeys): IApiKeyStoreMigrator abstraction — SQLite implements it, DI registers it via TryAdd so a provider registered first wins; ApiKeyAdminCommands accepts the interface (old ctor kept)

Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
This commit is contained in:
Joseph Doherty
2026-09-02 14:06:14 -04:00
parent 300094e792
commit 3f11704d10
6 changed files with 85 additions and 10 deletions
@@ -33,10 +33,15 @@ public sealed class ApiKeyAdminCommands
private readonly IApiKeyAdminStore _adminStore;
private readonly IApiKeyAuditStore _auditStore;
private readonly IApiKeyPepperProvider _pepperProvider;
private readonly SqliteAuthStoreMigrator _migrator;
private readonly IApiKeyStoreMigrator _migrator;
private readonly TimeProvider _clock;
/// <summary>Creates the command set over the supplied stores and options.</summary>
/// <summary>
/// Creates the command set over the supplied stores and options.
/// Retained for existing consumers (MxAccessGateway, HistorianGateway, ScadaBridge) that
/// construct this type against the concrete SQLite migrator; forwards to the
/// <see cref="IApiKeyStoreMigrator"/> overload.
/// </summary>
/// <param name="options">API-key options (token prefix, store path, ...).</param>
/// <param name="adminStore">Mutating store (create / revoke / rotate / delete / list).</param>
/// <param name="auditStore">Append-only audit store wired into every mutating verb.</param>
@@ -50,6 +55,25 @@ public sealed class ApiKeyAdminCommands
IApiKeyPepperProvider pepperProvider,
SqliteAuthStoreMigrator migrator,
TimeProvider? clock = null)
: this(options, adminStore, auditStore, pepperProvider, (IApiKeyStoreMigrator)migrator, clock)
{
}
/// <summary>Creates the command set over the supplied stores and options.</summary>
/// <param name="options">API-key options (token prefix, store path, ...).</param>
/// <param name="adminStore">Mutating store (create / revoke / rotate / delete / list).</param>
/// <param name="auditStore">Append-only audit store wired into every mutating verb.</param>
/// <param name="pepperProvider">Resolves the pepper used to hash secrets.</param>
/// <param name="migrator">Schema migrator used by <see cref="InitDbAsync"/> — whichever store
/// provider's implementation (SQLite, SQL Server, ...) DI has registered.</param>
/// <param name="clock">Optional clock; defaults to <see cref="TimeProvider.System"/>.</param>
public ApiKeyAdminCommands(
ApiKeyOptions options,
IApiKeyAdminStore adminStore,
IApiKeyAuditStore auditStore,
IApiKeyPepperProvider pepperProvider,
IApiKeyStoreMigrator migrator,
TimeProvider? clock = null)
{
ArgumentNullException.ThrowIfNull(options);
ArgumentNullException.ThrowIfNull(adminStore);