feat(host): inbound API key store provider — SqlServer shares one zbauth row set across the central pair; one-shot SQLite import at boot (file renamed .imported, one retry on a concurrent-peer PK race, failure never blocks boot)
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
This commit is contained in:
@@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ZB.MOM.WW.Auth.ApiKeys.DependencyInjection;
|
||||
using ZB.MOM.WW.Auth.ApiKeys.SqlServer.DependencyInjection;
|
||||
using ZB.MOM.WW.Auth.AspNetCore;
|
||||
using ZB.MOM.WW.Health;
|
||||
using ZB.MOM.WW.Health.Akka;
|
||||
@@ -434,6 +435,14 @@ try
|
||||
// value already supplied via appsettings/env wins (AddInMemoryCollection
|
||||
// is registered last, but only fills keys the operator did not set
|
||||
// because we read the existing value first).
|
||||
//
|
||||
// Which BACKEND holds those keys is chosen by
|
||||
// ScadaBridge:InboundApi:ApiKeyStore:Provider (InboundApiKeyStoreOptions):
|
||||
// Sqlite (default) is the per-node file described above; SqlServer shares one
|
||||
// row set across the central pair, so a key minted on either node survives a
|
||||
// failover (design 2026-09-02, deferred-work register row 108). SqlitePath is
|
||||
// still bound under SqlServer — it names the legacy per-node file that
|
||||
// InboundApiKeyStoreImportService copies in once.
|
||||
const string apiKeyStoreSection = "ScadaBridge:InboundApi:ApiKeyStore";
|
||||
var configuredSqlitePath = builder.Configuration[$"{apiKeyStoreSection}:SqlitePath"];
|
||||
var apiKeyStoreDefaults = new Dictionary<string, string?>
|
||||
@@ -447,7 +456,36 @@ try
|
||||
};
|
||||
builder.Configuration.AddInMemoryCollection(apiKeyStoreDefaults);
|
||||
|
||||
builder.Services.AddZbApiKeyAuth(builder.Configuration, apiKeyStoreSection);
|
||||
var apiKeyStoreProvider = InboundApiKeyStoreOptions.ReadProvider(builder.Configuration);
|
||||
builder.Services.Configure<InboundApiKeyStoreOptions>(
|
||||
builder.Configuration.GetSection(InboundApiKeyStoreOptions.SectionPath));
|
||||
if (apiKeyStoreProvider == InboundApiKeyStoreProvider.SqlServer)
|
||||
{
|
||||
// Shared store for a central pair (design 2026-09-02, register row 108). The connection
|
||||
// string defaults to the configuration database — the pair already shares it — under
|
||||
// the library's own `zbauth` schema, so no new database or secret is needed. It must be
|
||||
// a literal or an env override, NEVER a ${secret:} reference: the secrets store is
|
||||
// itself SQL-backed and resolves before this point, so a secret-backed key-store string
|
||||
// would be circular.
|
||||
const string sqlSection = apiKeyStoreSection + ":SqlServer";
|
||||
if (string.IsNullOrWhiteSpace(builder.Configuration[$"{sqlSection}:ConnectionString"]))
|
||||
{
|
||||
builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
[$"{sqlSection}:ConnectionString"] = builder.Configuration["ScadaBridge:Database:ConfigurationDb"],
|
||||
});
|
||||
}
|
||||
|
||||
builder.Services.AddZbApiKeyAuthSqlServer(builder.Configuration, apiKeyStoreSection, sqlSection);
|
||||
builder.Services.AddSingleton<IInboundApiKeyStoreImporter, LibraryInboundApiKeyStoreImporter>();
|
||||
// Registered AFTER AddZbApiKeyAuthSqlServer so it starts after the library's migration
|
||||
// hosted service (hosted services start in registration order) — the schema exists first.
|
||||
builder.Services.AddHostedService<InboundApiKeyStoreImportService>();
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Services.AddZbApiKeyAuth(builder.Configuration, apiKeyStoreSection);
|
||||
}
|
||||
|
||||
// Inbound-API key re-arch, additive: expose the library admin facade
|
||||
// (ApiKeyAdminCommands) and the app-side management seam (IInboundApiKeyAdmin)
|
||||
@@ -463,7 +501,7 @@ try
|
||||
sp.GetRequiredService<ZB.MOM.WW.Auth.Abstractions.ApiKeys.IApiKeyAdminStore>(),
|
||||
sp.GetRequiredService<ZB.MOM.WW.Auth.Abstractions.ApiKeys.IApiKeyAuditStore>(),
|
||||
sp.GetRequiredService<ZB.MOM.WW.Auth.ApiKeys.IApiKeyPepperProvider>(),
|
||||
sp.GetRequiredService<ZB.MOM.WW.Auth.ApiKeys.Sqlite.SqliteAuthStoreMigrator>(),
|
||||
sp.GetRequiredService<ZB.MOM.WW.Auth.Abstractions.ApiKeys.IApiKeyStoreMigrator>(),
|
||||
TimeProvider.System));
|
||||
builder.Services.AddSingleton<
|
||||
ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Security.IInboundApiKeyAdmin,
|
||||
|
||||
Reference in New Issue
Block a user