feat(auth)!: 0.3.0 — ZB.MOM.WW.Auth.ApiKeys.SqlServer package (AddZbApiKeyAuthSqlServer, importer), README/changelog
The `!` marks the new package + version, not an API break — SQLite stays default and existing consumers need no code change. Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
This commit is contained in:
+96
@@ -0,0 +1,96 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ZB.MOM.WW.Auth.Abstractions.ApiKeys;
|
||||
using ZB.MOM.WW.Auth.ApiKeys.DependencyInjection;
|
||||
|
||||
namespace ZB.MOM.WW.Auth.ApiKeys.SqlServer.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// Dependency-injection entry point that swaps the API-key store backend from the SQLite default
|
||||
/// to a shared SQL Server database — one row set read and written by every node of a cluster (e.g.
|
||||
/// a ScadaBridge central pair), instead of the per-node file <see cref="AddZbApiKeyAuth"/> alone
|
||||
/// would give each node.
|
||||
/// </summary>
|
||||
public static class SqlServerApiKeyServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers the SQL Server-backed <see cref="IApiKeyStore"/>, <see cref="IApiKeyAdminStore"/>,
|
||||
/// <see cref="IApiKeyAuditStore"/> and <see cref="IApiKeyStoreMigrator"/>, then calls
|
||||
/// <see cref="ApiKeyServiceCollectionExtensions.AddZbApiKeyAuth"/> to complete the registration
|
||||
/// (verifier, pepper provider, migration hosted service).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Call this INSTEAD of <see cref="ApiKeyServiceCollectionExtensions.AddZbApiKeyAuth"/></b>,
|
||||
/// never both — this method calls it internally. The SQL Server stores and migrator are
|
||||
/// registered here, BEFORE that inner call, precisely because
|
||||
/// <see cref="ApiKeyServiceCollectionExtensions.AddZbApiKeyAuth"/> registers everything with
|
||||
/// <c>TryAdd*</c>: whichever implementation of <see cref="IApiKeyStore"/> (etc.) is registered
|
||||
/// first wins, and a later <c>TryAdd</c> for the same service is a no-op. That ordering is the
|
||||
/// entire mechanism by which this method displaces the SQLite default.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="ApiKeyServiceCollectionExtensions.AddZbApiKeyAuth"/> still registers a SQLite
|
||||
/// connection factory from <c>ApiKeyOptions.SqlitePath</c> even when SQL Server wins every
|
||||
/// store slot. That is harmless — the SQLite factory is lazy and the connection it would open
|
||||
/// is never opened when nothing resolves it — and it is actually useful: it keeps the one path
|
||||
/// a node's pre-existing per-node SQLite file lives at in a single option, which is exactly the
|
||||
/// source path <see cref="SqliteApiKeyStoreImporter"/> needs for the one-shot copy into the
|
||||
/// shared store.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="SqliteApiKeyStoreImporter"/> is registered here so a host can resolve it and run
|
||||
/// it once at boot (e.g. from a startup task or its own hosted service) to copy an existing
|
||||
/// node's per-node keys and audit rows into the now-shared store; it performs no work by being
|
||||
/// merely registered.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>The connection string must be a literal value or an environment-variable override — never
|
||||
/// a <c>${secret:}</c> reference</b> on a host whose secret store is itself SQL-backed: resolving
|
||||
/// that reference would require a working secret store, which on such a host requires this very
|
||||
/// database to already be reachable, an unbreakable circular dependency at boot.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="services">The service collection to add to.</param>
|
||||
/// <param name="config">The application configuration.</param>
|
||||
/// <param name="apiKeySectionPath">Path of the configuration section holding the core API-key options.</param>
|
||||
/// <param name="sqlServerSectionPath">Path of the configuration section holding <see cref="SqlServerApiKeyOptions"/>.</param>
|
||||
/// <returns>The same <paramref name="services"/> instance, for chaining.</returns>
|
||||
public static IServiceCollection AddZbApiKeyAuthSqlServer(
|
||||
this IServiceCollection services,
|
||||
IConfiguration config,
|
||||
string apiKeySectionPath,
|
||||
string sqlServerSectionPath)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(config);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(apiKeySectionPath);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(sqlServerSectionPath);
|
||||
|
||||
services.Configure<SqlServerApiKeyOptions>(config.GetSection(sqlServerSectionPath));
|
||||
|
||||
services.TryAddSingleton(sp =>
|
||||
new AuthSqlServerConnectionFactory(sp.GetRequiredService<IOptions<SqlServerApiKeyOptions>>().Value));
|
||||
|
||||
// Registered BEFORE AddZbApiKeyAuth so its TryAdd calls find these already present and
|
||||
// leave them alone — that is the whole mechanism by which the SQL Server store displaces
|
||||
// SQLite (see the class remarks).
|
||||
services.TryAddSingleton<IApiKeyStore>(sp =>
|
||||
new SqlServerApiKeyStore(sp.GetRequiredService<AuthSqlServerConnectionFactory>()));
|
||||
services.TryAddSingleton<IApiKeyAdminStore>(sp =>
|
||||
new SqlServerApiKeyAdminStore(sp.GetRequiredService<AuthSqlServerConnectionFactory>()));
|
||||
services.TryAddSingleton<IApiKeyAuditStore>(sp =>
|
||||
new SqlServerApiKeyAuditStore(sp.GetRequiredService<AuthSqlServerConnectionFactory>()));
|
||||
|
||||
services.TryAddSingleton<SqlServerApiKeyStoreMigrator>();
|
||||
services.TryAddSingleton<IApiKeyStoreMigrator>(sp => sp.GetRequiredService<SqlServerApiKeyStoreMigrator>());
|
||||
|
||||
// Registered so a host can resolve it and run the one-shot per-node SQLite -> shared store
|
||||
// copy at boot; registering it here does no work on its own.
|
||||
services.TryAddSingleton<SqliteApiKeyStoreImporter>();
|
||||
|
||||
return services.AddZbApiKeyAuth(config, apiKeySectionPath);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user