feat(auth): ScadaBridge inbound API — adopt ZB.MOM.WW.Auth.ApiKeys verifier + Bearer + scope=method (re-arch A+B); additive, old path retired later

This commit is contained in:
Joseph Doherty
2026-06-02 02:40:18 -04:00
parent 4db8c373af
commit a94558c289
7 changed files with 451 additions and 157 deletions
+38
View File
@@ -1,3 +1,4 @@
using ZB.MOM.WW.Auth.ApiKeys.DependencyInjection;
using ZB.MOM.WW.Health;
using ZB.MOM.WW.Health.Akka;
using ZB.MOM.WW.Health.EntityFrameworkCore;
@@ -106,6 +107,43 @@ try
builder.Services.AddSecurity(builder.Configuration);
builder.Services.AddCentralUI();
builder.Services.AddInboundAPI();
// Inbound-API auth re-arch (A+B), additive: stand up the shared
// ZB.MOM.WW.Auth.ApiKeys verifier + SQLite store + startup migration
// ALONGSIDE the legacy peppered-HMAC X-API-Key path. The POST
// /api/{methodName} endpoint now authenticates Bearer tokens
// (sbk_<keyId>_<secret>) and authorizes by scope == method name through
// this verifier. The legacy ApiKeyValidator/IApiKeyHasher remain
// registered (unused by the endpoint) until a later sub-task retires the
// SQL Server ApiKey entity.
//
// ApiKeyOptions is an init-only record, so the contract-mandated values
// are injected as in-memory configuration UNDER the bound section path
// (ScadaBridge:InboundApi:ApiKeyStore) rather than mutated post-bind:
// - TokenPrefix = "sbk" (the inbound token prefix)
// - PepperSecretName points at the EXISTING inbound-API pepper config key
// (reused so one secret backs both the legacy and new path during the
// additive window)
// - RunMigrationsOnStartup = true (hosted service creates the schema)
// - SqlitePath defaults under the content root's data/ directory, but any
// 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).
const string apiKeyStoreSection = "ScadaBridge:InboundApi:ApiKeyStore";
var configuredSqlitePath = builder.Configuration[$"{apiKeyStoreSection}:SqlitePath"];
var apiKeyStoreDefaults = new Dictionary<string, string?>
{
[$"{apiKeyStoreSection}:TokenPrefix"] = "sbk",
[$"{apiKeyStoreSection}:PepperSecretName"] = "ScadaBridge:InboundApi:ApiKeyPepper",
[$"{apiKeyStoreSection}:RunMigrationsOnStartup"] = "true",
[$"{apiKeyStoreSection}:SqlitePath"] = string.IsNullOrWhiteSpace(configuredSqlitePath)
? Path.Combine(builder.Environment.ContentRootPath, "data", "inbound-api-keys.sqlite")
: configuredSqlitePath,
};
builder.Configuration.AddInMemoryCollection(apiKeyStoreDefaults);
builder.Services.AddZbApiKeyAuth(builder.Configuration, apiKeyStoreSection);
builder.Services.AddManagementService();
var configDbConnectionString = configuration["ScadaBridge:Database:ConfigurationDb"]