diff --git a/docs/plans/2026-06-02-auth-audit-normalization-phase1.md b/docs/plans/2026-06-02-auth-audit-normalization-phase1.md index 7792130..5751ab3 100644 --- a/docs/plans/2026-06-02-auth-audit-normalization-phase1.md +++ b/docs/plans/2026-06-02-auth-audit-normalization-phase1.md @@ -189,11 +189,48 @@ keeps the build green at each step. secret (proven by test asserting `SecretHash.SequenceEqual` + unchanged `last_used_utc`). This is what lets C/D edit a key's method-scopes and toggle enabled WITHOUT re-issuing the token. **ScadaBridge must re-pin Auth packages 0.1.2 → 0.1.3.** - **C (management), D (CentralUI), E (retire SQL Server ApiKey + ApiMethod.ApprovedApiKeyIds migration + runbook/CHANGELOG) - — IN PROGRESS (C next).** Mapping for C: `CreateApiKeyCommand` → `CreateKeyAsync` (keyId = `Guid.NewGuid().ToString("N")`, + — IN PROGRESS.** Mapping: `CreateApiKeyCommand` → `CreateKeyAsync` (keyId = `Guid.NewGuid().ToString("N")`, DisplayName = name, scopes = `--methods`); `ListApiKeysCommand` → `ListKeysAsync` (enabled = `RevokedUtc is null`); `UpdateApiKeyCommand(IsEnabled)` → `SetEnabledAsync`; new set-scopes path → `SetScopesAsync`; `DeleteApiKeyCommand` → revoke-then-`DeleteKeyAsync`. All management message keys switch `int ApiKeyId` → `string KeyId`. +### Discovered architecture (CentralUI Explore, 2026-06-02) — expands C/D/E +Two facts the original A–E spec missed: +1. **CentralUI bypasses the ManagementActor.** `Components/Pages/Admin/ApiKeys.razor`, `ApiKeyForm.razor`, and + `Components/Pages/Design/ApiMethodForm.razor` call `IInboundApiRepository` (SQL Server EF) **directly** — they do NOT + send the `CreateApiKeyCommand`/etc. management messages. So there are **two** management entry points to rewire + (CLI→ManagementActor uses the messages; CentralUI→repository uses the entities). Decoupling: introduce one app-side + **`IInboundApiKeyAdmin` seam** over the library `ApiKeyAdminCommands`, and route BOTH CLI and CentralUI through it + (DRY + single audit path). The message-contract change (int→string) touches only CLI+ManagementActor; the + entity/repository change (`ApiKey.Id`, `ApiMethod.ApprovedApiKeyIds`) touches CentralUI + TransportExport. +2. **TransportExport couples API keys + methods into config export/import** (`Components/Pages/Design/TransportExport.razor` + + `.razor.cs`, `HashSet` selections, `ExportSelection`). With keys now in the library SQLite store (per-env pepper, + secret-once), a key can't be exported/re-imported usefully. **Decision (user, 2026-06-02): EXCLUDE inbound API keys from + transport — export API methods only; keys are re-created + method-scopes re-granted per environment.** + +CentralUI blast radius (string keyId + scopes replace int Id + ApprovedApiKeyIds CSV): `Admin/ApiKeys.razor`, +`Admin/ApiKeyForm.razor`, `Design/ApiMethodForm.razor` (approved-keys ↔ key-scopes), `Design/TransportExport.razor(.cs)`, +`Design/ExternalSystems.razor` (uses method `int` id — methods STAY int in SQL Server, so unaffected for keys), +`Dashboard.razor` (key count), test `Admin/ApiKeyFormAuditDrillinTests.cs`. + +### C/D/E decomposition — 5 reviewed green sub-commits (user: "coordinated multi-commit now", 2026-06-02) +- **C1** — re-pin ScadaBridge Auth 0.1.2→0.1.3; add app-side `IInboundApiKeyAdmin` seam (string-keyId model: + Create(name,methods)→(keyId,token) / List / SetEnabled / SetMethods / Delete[=revoke+delete] / GetMethodsForKey / + GetKeysForMethod) over the library facade; register `ApiKeyAdminCommands` + the seam in Host **and** CentralUI DI; seam + unit tests. **Purely additive — build green.** +- **C2** — Commons `Messages/Management/SecurityCommands.cs` contracts int→string keyId + add `Methods` + new + `SetApiKeyMethodsCommand`; rewire ManagementActor handlers + CLI `security api-key` onto the seam; update ManagementActor + tests. (CentralUI unaffected — it doesn't use these messages.) +- **C3** — CentralUI `ApiKeys.razor`/`ApiKeyForm.razor`/`ApiMethodForm.razor` (+ Dashboard count) off `IInboundApiRepository`- + for-keys onto the seam; string keyId; method-scope editing replaces `ApprovedApiKeyIds`; update bUnit test. (Methods stay + in SQL Server; just stop using the `ApprovedApiKeyIds` column — dropped in C5.) +- **C4** — TransportExport: remove API-key selection/export (methods-only); drop key `HashSet` + `ExportSelection` keys; + tests. +- **C5 (=E)** — retire SQL Server `ApiKey` entity + DbContext reg + `IInboundApiRepository` key methods + + `GetApprovedKeysForMethodAsync`; drop `ApiMethod.ApprovedApiKeyIds`; EF migration (drop ApiKeys table + column); delete + residual `ApiKeyValidator`/`ApiKeyHasher`; runbook + CHANGELOG (breaking: re-issue keys, `X-API-Key`→`Authorization: Bearer`); + full build+test sweep. + ## Resolved decisions (2026-06-02) - **Decision A — ScadaBridge inbound API keys depth → (a) FULL ADOPT.** Re-architect inbound-API auth to the diff --git a/docs/plans/2026-06-02-auth-audit-normalization.md.tasks.json b/docs/plans/2026-06-02-auth-audit-normalization.md.tasks.json index 9d99ff2..5634820 100644 --- a/docs/plans/2026-06-02-auth-audit-normalization.md.tasks.json +++ b/docs/plans/2026-06-02-auth-audit-normalization.md.tasks.json @@ -30,7 +30,14 @@ {"id": 29, "subject": "Task 2.5: ScadaBridge rename→IAuditRedactor + AuditOutcome (#3) [high-risk]", "status": "pending", "blockedBy": [25]}, {"id": 30, "subject": "Task 3.1: Introduce IAuditActorAccessor seam", "status": "pending", "blockedBy": [9]}, - {"id": 31, "subject": "Task 3.2-3.4: Wire emit sites to Auth principal (#4)", "status": "pending", "blockedBy": [30]} + {"id": 31, "subject": "Task 3.2-3.4: Wire emit sites to Auth principal (#4)", "status": "pending", "blockedBy": [30]}, + + {"id": 32, "subject": "Task 1.3-L: Extend Auth.ApiKeys admin store (SetScopes/SetEnabled) -> lib 0.1.3 (PUBLISHED)", "status": "completed", "blockedBy": []}, + {"id": 33, "subject": "Task 1.3-C1: ScadaBridge re-pin 0.1.3 + IInboundApiKeyAdmin seam (additive)", "status": "pending", "blockedBy": [32]}, + {"id": 34, "subject": "Task 1.3-C2: ManagementActor + CLI + Commons messages onto seam", "status": "pending", "blockedBy": [33]}, + {"id": 35, "subject": "Task 1.3-C3: CentralUI pages onto seam (string keyId + scopes)", "status": "pending", "blockedBy": [33]}, + {"id": 36, "subject": "Task 1.3-C4: TransportExport exclude API keys (methods-only)", "status": "pending", "blockedBy": [33, 35]}, + {"id": 37, "subject": "Task 1.3-C5 (=E): retire SQL Server ApiKey entity + EF migration + runbook", "status": "pending", "blockedBy": [34, 35, 36]} ], "lastUpdated": "2026-06-02" }