using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; using ZB.MOM.WW.Audit; using ZB.MOM.WW.Auth.Abstractions.ApiKeys; using ZB.MOM.WW.Auth.ApiKeys; using ZB.MOM.WW.Auth.ApiKeys.Admin; using ZB.MOM.WW.Auth.ApiKeys.DependencyInjection; using ZB.MOM.WW.Auth.ApiKeys.Sqlite; using ZB.MOM.WW.MxGateway.Server.Security.Audit; namespace ZB.MOM.WW.MxGateway.Server.Security.Authentication; /// /// Extension methods for configuring the SQLite authentication store. /// /// /// The peppered-HMAC API-key pipeline (token format, hashing, constant-time compare, SQLite /// schema, stores, verifier and migration) is provided by the shared /// ZB.MOM.WW.Auth.ApiKeys library, of which this gateway is the donor. This wiring binds /// the library's from the gateway's MxGateway:Authentication /// section and layers the gateway-specific constraint enforcement, gRPC interceptor, CLI and /// dashboard on top. /// public static class AuthStoreServiceCollectionExtensions { /// The configuration section the gateway binds API-key options from. public const string AuthenticationSectionPath = "MxGateway:Authentication"; /// The gateway API-key token prefix (token format mxgw_<id>_<secret>). public const string TokenPrefix = "mxgw"; /// The configuration key the API-key pepper is resolved from. public const string PepperSecretName = "MxGateway:ApiKeyPepper"; /// /// Adds the SQLite authentication store and related services to the dependency container. /// /// Service collection to configure. /// Application configuration carrying the API-key options. /// The service collection for chaining. public static IServiceCollection AddSqliteAuthStore( this IServiceCollection services, IConfiguration configuration) { ArgumentNullException.ThrowIfNull(services); ArgumentNullException.ThrowIfNull(configuration); // Pin the gateway's API-key contract (token prefix "mxgw"; pepper resolved from // MxGateway:ApiKeyPepper) by layering fallback defaults UNDER the supplied configuration: // an in-memory source provides TokenPrefix/PepperSecretName only when the bound // MxGateway:Authentication section omits them (the section has no TokenPrefix, and the pepper // is intentionally not in appsettings — it is supplied at runtime). Explicit config wins // because it is added last. ApiKeyOptions is an init-only record, so the values must be // present at bind time rather than mutated post-configure. IConfiguration effectiveConfig = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { [$"{AuthenticationSectionPath}:TokenPrefix"] = TokenPrefix, [$"{AuthenticationSectionPath}:PepperSecretName"] = PepperSecretName, }) .AddConfiguration(configuration) .Build(); // Register the shared API-key provider: binds ApiKeyOptions from MxGateway:Authentication, // wires up the SQLite stores, the configuration-backed pepper provider, the verifier, the // migrator and the migration hosted service. services.AddZbApiKeyAuth(effectiveConfig, AuthenticationSectionPath); // Canonical audit (Task 2.3 — DEEP adopt ZB.MOM.WW.Audit). All MxGateway audit flows as a // canonical AuditEvent through the library IAuditWriter, persisted in a NEW gateway-owned // audit_event table that lives in the SAME SQLite DB file as the api-key stores (it reuses // the library's AuthSqliteConnectionFactory, registered by AddZbApiKeyAuth above). services.AddSingleton(sp => new SqliteCanonicalAuditStore(sp.GetRequiredService())); // Resolve the logger defensively: the production host always registers ILogger, but the // DI-only auth/CLI/dashboard unit tests build a bare ServiceCollection without AddLogging(). // Fall back to NullLogger there so the audit writer (and the IApiKeyAuditStore override that // depends on it) still resolve. The write path is best-effort regardless. services.AddSingleton(sp => new CanonicalAuditWriter( sp.GetRequiredService(), sp.GetService>() ?? Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance)); // OVERRIDE the library's IApiKeyAuditStore (AddZbApiKeyAuth registered the library's // SqliteApiKeyAuditStore via TryAddSingleton) with an adapter that canonicalizes every // library-emitted ApiKeyAuditEntry onto AuditEvent and forwards it through IAuditWriter. // This is the only way to canonicalize the library-internal ApiKeyAdminCommands verbs // (create/revoke/rotate/init-db, etc.), which we cannot edit. The adapter is registered // after AddZbApiKeyAuth so it (last registration) is what ApiKeyAdminCommands resolves and // what the dashboard "recent audit" view reads via IApiKeyAuditStore.ListRecentAsync. // The library's api_key_audit table is left in place but is now UNUSED — nothing writes to // it once this adapter replaces the library's audit store. services.AddSingleton(); // The shared admin command set (create/revoke/rotate/list/init-db with audit) is not // auto-registered by AddZbApiKeyAuth; the gateway CLI and dashboard drive it, so register // it here over the already-wired stores, pepper provider and migrator. services.AddSingleton(sp => new ApiKeyAdminCommands( sp.GetRequiredService>().Value, sp.GetRequiredService(), sp.GetRequiredService(), sp.GetRequiredService(), sp.GetRequiredService())); services.AddSingleton(); return services; } }