Restructure solution layout by module
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Docs CI / lint-and-preview (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Docs CI / lint-and-preview (push) Has been cancelled
				
			This commit is contained in:
		| @@ -1,212 +1,212 @@ | ||||
| # Authority Plug-in Developer Guide | ||||
|  | ||||
| > **Status:** Updated 2025-10-11 (AUTHPLUG-DOCS-01-001) with lifecycle + limiter diagrams and refreshed rate-limit guidance aligned to PLG6 acceptance criteria. | ||||
|  | ||||
| ## 1. Overview | ||||
| Authority plug-ins extend the **StellaOps Authority** service with custom identity providers, credential stores, and client-management logic. Unlike Concelier plug-ins (which ingest or export advisories), Authority plug-ins participate directly in authentication flows: | ||||
|  | ||||
| - **Use cases:** integrate corporate directories (LDAP/AD)[^ldap-rfc], delegate to external IDPs, enforce bespoke password/lockout policies, or add client provisioning automation. | ||||
| - **Constraints:** plug-ins load only during service start (no hot-reload), must function without outbound internet access, and must emit deterministic results for identical configuration input. | ||||
| - **Ship targets:** build against the host’s .NET 10 preview SDK, honour offline-first requirements, and surface actionable diagnostics so operators can triage issues from `/ready`. | ||||
|  | ||||
| ## 2. Architecture Snapshot | ||||
| Authority hosts follow a deterministic plug-in lifecycle. The exported diagram (`docs/assets/authority/authority-plugin-lifecycle.svg`) mirrors the steps below; regenerate it from the Mermaid source if you update the flow. | ||||
|  | ||||
| 1. **Configuration load** – `AuthorityPluginConfigurationLoader` resolves YAML manifests under `etc/authority.plugins/`. | ||||
| 2. **Assembly discovery** – the shared `PluginHost` scans `StellaOps.Authority.PluginBinaries` for `StellaOps.Authority.Plugin.*.dll` assemblies. | ||||
| 3. **Registrar execution** – each assembly is searched for `IAuthorityPluginRegistrar` implementations. Registrars bind options, register services, and optionally queue bootstrap tasks. | ||||
| 4. **Runtime** – the host resolves `IIdentityProviderPlugin` instances, uses capability metadata to decide which OAuth grants to expose, and invokes health checks for readiness endpoints. | ||||
|  | ||||
|  | ||||
|  | ||||
| _Source:_ `docs/assets/authority/authority-plugin-lifecycle.mmd` | ||||
|  | ||||
| **Data persistence primer:** the standard Mongo-backed plugin stores users in collections named `authority_users_<pluginName>` and lockout metadata in embedded documents. Additional plugins must document their storage layout and provide deterministic collection naming to honour the Offline Kit replication process. | ||||
|  | ||||
| ## 3. Capability Metadata | ||||
| Capability flags let the host reason about what your plug-in supports: | ||||
|  | ||||
| - Declare capabilities in your descriptor using the string constants from `AuthorityPluginCapabilities` (`password`, `mfa`, `clientProvisioning`, `bootstrap`). The configuration loader now validates these tokens and rejects unknown values at startup. | ||||
| - `AuthorityIdentityProviderCapabilities.FromCapabilities` projects those strings into strongly typed booleans (`SupportsPassword`, etc.). Authority Core will use these flags when wiring flows such as the password grant. Built-in plugins (e.g., Standard) will fail fast or force-enable required capabilities if the descriptor is misconfigured, so keep manifests accurate. | ||||
| - Typical configuration (`etc/authority.plugins/standard.yaml`): | ||||
|   ```yaml | ||||
|   plugins: | ||||
|     descriptors: | ||||
|       standard: | ||||
|         assemblyName: "StellaOps.Authority.Plugin.Standard" | ||||
|         capabilities: | ||||
|           - password | ||||
|           - bootstrap | ||||
|   ``` | ||||
| - Only declare a capability if the plug-in genuinely implements it. For example, if `SupportsClientProvisioning` is `true`, the plug-in must supply a working `IClientProvisioningStore`. | ||||
|  | ||||
| **Operational reminder:** the Authority host surfaces capability summaries during startup (see `AuthorityIdentityProviderRegistry` log lines). Use those logs during smoke tests to ensure manifests align with expectations. | ||||
|  | ||||
| **Configuration path normalisation:** Manifest-relative paths (e.g., `tokenSigning.keyDirectory: "../keys"`) are resolved against the YAML file location and environment variables are expanded before validation. Plug-ins should expect to receive an absolute, canonical path when options are injected. | ||||
|  | ||||
| **Password policy guardrails:** The Standard registrar logs a warning when a plug-in weakens the default password policy (minimum length or required character classes). Keep overrides at least as strong as the compiled defaults—operators treat the warning as an actionable security deviation. | ||||
|  | ||||
| ## 4. Project Scaffold | ||||
| - Target **.NET 10 preview**, enable nullable, treat warnings as errors, and mark Authority plug-ins with `<IsAuthorityPlugin>true</IsAuthorityPlugin>`. | ||||
| - Minimum references: | ||||
|   - `StellaOps.Authority.Plugins.Abstractions` (contracts & capability helpers) | ||||
|   - `StellaOps.Plugin` (hosting/DI helpers) | ||||
|   - `StellaOps.Auth.*` libraries as needed for shared token utilities (optional today). | ||||
| - Example `.csproj` (trimmed from `StellaOps.Authority.Plugin.Standard`): | ||||
|   ```xml | ||||
|   <Project Sdk="Microsoft.NET.Sdk"> | ||||
|     <PropertyGroup> | ||||
|       <TargetFramework>net10.0</TargetFramework> | ||||
|       <Nullable>enable</Nullable> | ||||
|       <TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||||
|       <IsAuthorityPlugin>true</IsAuthorityPlugin> | ||||
|     </PropertyGroup> | ||||
|     <ItemGroup> | ||||
|       <ProjectReference Include="..\StellaOps.Authority.Plugins.Abstractions\StellaOps.Authority.Plugins.Abstractions.csproj" /> | ||||
|       <ProjectReference Include="..\..\StellaOps.Plugin\StellaOps.Plugin.csproj" /> | ||||
|     </ItemGroup> | ||||
|   </Project> | ||||
|   ``` | ||||
|   (Add other references—e.g., MongoDB driver, shared auth libraries—according to your implementation.) | ||||
|  | ||||
| ## 5. Implementing `IAuthorityPluginRegistrar` | ||||
| - Create a parameterless registrar class that returns your plug-in type name via `PluginType`. | ||||
| - Use `AuthorityPluginRegistrationContext` to: | ||||
|   - Bind options (`AddOptions<T>(pluginName).Bind(...)`). | ||||
|   - Register singletons for stores/enrichers using manifest metadata. | ||||
|   - Register any hosted bootstrap tasks (e.g., seed admin users). | ||||
| - Always validate configuration inside `PostConfigure` and throw meaningful `InvalidOperationException` to fail fast during startup. | ||||
| - Use the provided `ILoggerFactory` from DI; avoid static loggers or console writes. | ||||
| - Example skeleton: | ||||
|   ```csharp | ||||
|   internal sealed class MyPluginRegistrar : IAuthorityPluginRegistrar | ||||
|   { | ||||
|       public string PluginType => "my-custom"; | ||||
|  | ||||
|       public void Register(AuthorityPluginRegistrationContext context) | ||||
|       { | ||||
|           var name = context.Plugin.Manifest.Name; | ||||
|  | ||||
|           context.Services.AddOptions<MyPluginOptions>(name) | ||||
|               .Bind(context.Plugin.Configuration) | ||||
|               .PostConfigure(opts => opts.Validate(name)); | ||||
|  | ||||
|           context.Services.AddSingleton<IIdentityProviderPlugin>(sp => | ||||
|               new MyIdentityProvider(context.Plugin, sp.GetRequiredService<MyCredentialStore>(), | ||||
|                                      sp.GetRequiredService<MyClaimsEnricher>(), | ||||
|                                      sp.GetRequiredService<ILogger<MyIdentityProvider>>())); | ||||
|       } | ||||
|   } | ||||
|   ``` | ||||
|  | ||||
| ## 6. Identity Provider Surface | ||||
| - Implement `IIdentityProviderPlugin` to expose: | ||||
|   - `IUserCredentialStore` for password validation and user CRUD. | ||||
|   - `IClaimsEnricher` to append roles/attributes onto issued principals. | ||||
|   - Optional `IClientProvisioningStore` for machine-to-machine clients. | ||||
|   - `AuthorityIdentityProviderCapabilities` to advertise supported flows. | ||||
| - Password guidance: | ||||
|   - Standard plug-in hashes via `ICryptoProvider` using Argon2id by default and emits PHC-compliant strings. Successful PBKDF2 logins trigger automatic rehashes so migrations complete gradually. See `docs/security/password-hashing.md` for tuning advice. | ||||
|   - Enforce password policies before hashing to avoid storing weak credentials. | ||||
| - Health checks should probe backing stores (e.g., Mongo `ping`) and return `AuthorityPluginHealthResult` so `/ready` can surface issues. | ||||
| - When supporting additional factors (e.g., TOTP), implement `SupportsMfa` and document the enrolment flow for resource servers. | ||||
|  | ||||
| ## 7. Configuration & Secrets | ||||
| - Authority looks for manifests under `etc/authority.plugins/`. Each YAML file maps directly to a plug-in name. | ||||
| - Support environment overrides using `STELLAOPS_AUTHORITY_PLUGINS__DESCRIPTORS__<NAME>__...`. | ||||
| - Never store raw secrets in git: allow operators to supply them via `.local.yaml`, environment variables, or injected secret files. Document which keys are mandatory. | ||||
| - Validate configuration as soon as the registrar runs; use explicit error messages to guide operators. The Standard plug-in now enforces complete bootstrap credentials (username + password) and positive lockout windows via `StandardPluginOptions.Validate`. | ||||
| - Cross-reference bootstrap workflows with `docs/ops/authority_bootstrap.md` (to be published alongside CORE6) so operators can reuse the same payload formats for manual provisioning. | ||||
| - `passwordHashing` inherits defaults from `authority.security.passwordHashing`. Override only when hardware constraints differ per plug-in: | ||||
|   ```yaml | ||||
|   passwordHashing: | ||||
|     algorithm: Argon2id | ||||
|     memorySizeInKib: 19456 | ||||
|     iterations: 2 | ||||
|     parallelism: 1 | ||||
|   ``` | ||||
|   Invalid values (≤0) fail fast during startup, and legacy PBKDF2 hashes rehash automatically once the new algorithm succeeds. | ||||
|  | ||||
| ### 7.1 Token Persistence Contract | ||||
| - The host automatically persists every issued principal (access, refresh, device, authorization code) in `authority_tokens`. Plug-in code **must not** bypass this store; use the provided `IAuthorityTokenStore` helpers when implementing custom flows. | ||||
| - When a plug-in disables a subject or client outside the standard handlers, call `IAuthorityTokenStore.UpdateStatusAsync(...)` for each affected token so revocation bundles stay consistent. | ||||
| - Supply machine-friendly `revokedReason` codes (`compromised`, `rotation`, `policy`, `lifecycle`, etc.) and optional `revokedMetadata` entries when invalidating credentials. These flow straight into `revocation-bundle.json` and should remain deterministic. | ||||
| - Token scopes should be normalised (trimmed, unique, ordinal sort) before returning from plug-in verification paths. `TokenPersistenceHandlers` will keep that ordering for downstream consumers. | ||||
|  | ||||
| ### 7.2 Claims & Enrichment Checklist | ||||
| - Authority always sets the OpenID Connect basics: `sub`, `client_id`, `preferred_username`, optional `name`, and `role` (for password flows). Plug-ins must use `IClaimsEnricher` to append additional claims in a **deterministic** order (sort arrays, normalise casing) so resource servers can rely on stable shapes. | ||||
| - Recommended enrichment keys: | ||||
|   - `stellaops.realm` – plug-in/tenant identifier so services can scope policies. | ||||
|   - `stellaops.subject.type` – values such as `human`, `service`, `bootstrap`. | ||||
|   - `groups` / `projects` – sorted arrays describing operator entitlements. | ||||
| - Claims visible in tokens should mirror what `/token` and `/userinfo` emit. Avoid injecting sensitive PII directly; mark values with `ClassifiedString.Personal` inside the plug-in so audit sinks can tag them appropriately. | ||||
| - For client-credential flows, remember to enrich both the client principal and the validation path (`TokenValidationHandlers`) so refresh flows keep the same metadata. | ||||
|  | ||||
| ### 7.3 Revocation Bundles & Reasons | ||||
| - Use `IAuthorityRevocationStore` to record subject/client/token revocations when credentials are deleted or rotated. Stick to the standard categories (`token`, `subject`, `client`, `key`). | ||||
| - Include a deterministic `reason` string and optional `reasonDescription` so operators understand *why* a subject was revoked when inspecting bundles offline. | ||||
| - Plug-ins should populate `metadata` with stable keys (e.g., `revokedBy`, `sourcePlugin`, `ticketId`) to simplify SOC correlation. The keys must be lowercase, ASCII, and free of secrets—bundles are mirrored to air-gapped agents. | ||||
|  | ||||
| ## 8. Rate Limiting & Lockout Interplay | ||||
| Rate limiting and account lockouts are complementary controls. Plug-ins must surface both deterministically so operators can correlate limiter hits with credential rejections. | ||||
|  | ||||
| **Baseline quotas** (from `docs/dev/authority-rate-limit-tuning-outline.md`): | ||||
|  | ||||
| | Endpoint | Default policy | Notes | | ||||
| |----------|----------------|-------| | ||||
| | `/token` | 30 requests / 60s, queue 0 | Drop to 10/60s for untrusted ranges; raise only with WAF + monitoring. | | ||||
| | `/authorize` | 60 requests / 60s, queue 10 | Reduce carefully; interactive UX depends on headroom. | | ||||
| | `/internal/*` | Disabled by default; recommended 5/60s when enabled | Keep queue 0 for bootstrap APIs. | | ||||
|  | ||||
| **Retry metadata:** The middleware stamps `Retry-After` plus tags `authority.client_id`, `authority.remote_ip`, and `authority.endpoint`. Plug-ins should keep these tags intact when crafting responses or telemetry so dashboards remain consistent. | ||||
|  | ||||
| **Lockout counters:** Treat lockouts as **subject-scoped** decisions. When multiple instances update counters, reuse the deterministic tie-breakers documented in `src/DEDUP_CONFLICTS_RESOLUTION_ALGO.md` (freshness overrides, precedence, and stable hashes) to avoid divergent lockout states across replicas. | ||||
|  | ||||
| **Alerting hooks:** Emit structured logs/metrics when either the limiter or credential store rejects access. Suggested gauges include `aspnetcore_rate_limiting_rejections_total{limiter="authority-token"}` and any custom `auth.plugins.<pluginName>.lockouts_total` counter. | ||||
|  | ||||
|  | ||||
|  | ||||
| _Source:_ `docs/assets/authority/authority-rate-limit-flow.mmd` | ||||
|  | ||||
| ## 9. Logging, Metrics, and Diagnostics | ||||
| - Always log via the injected `ILogger<T>`; include `pluginName` and correlation IDs where available. | ||||
| - Activity/metric names should align with `AuthorityTelemetry` constants (`service.name=stellaops-authority`). | ||||
| - Expose additional diagnostics via structured logging rather than writing custom HTTP endpoints; the host will integrate these into `/health` and `/ready`. | ||||
| - Emit metrics with stable names (`auth.plugins.<pluginName>.*`) when introducing custom instrumentation; coordinate with the Observability guild to reserve prefixes. | ||||
|  | ||||
| ## 10. Testing & Tooling | ||||
| - Unit tests: use Mongo2Go (or similar) to exercise credential stores without hitting production infrastructure (`StandardUserCredentialStoreTests` is a template). | ||||
| - Determinism: fix timestamps to UTC and sort outputs consistently; avoid random GUIDs unless stable. | ||||
| - Smoke tests: launch `dotnet run --project src/StellaOps.Authority/StellaOps.Authority` with your plug-in under `StellaOps.Authority.PluginBinaries` and verify `/ready`. | ||||
| - Example verification snippet: | ||||
|   ```csharp | ||||
|   [Fact] | ||||
|   public async Task VerifyPasswordAsync_ReturnsSuccess() | ||||
|   { | ||||
|       var store = CreateCredentialStore(); | ||||
|       await store.UpsertUserAsync(new AuthorityUserRegistration("alice", "Pa55!", null, null, false, | ||||
|           Array.Empty<string>(), new Dictionary<string, string?>()), CancellationToken.None); | ||||
|  | ||||
|       var result = await store.VerifyPasswordAsync("alice", "Pa55!", CancellationToken.None); | ||||
|       Assert.True(result.Succeeded); | ||||
|       Assert.True(result.User?.Roles.Count == 0); | ||||
|   } | ||||
|   ``` | ||||
|  | ||||
| ## 11. Packaging & Delivery | ||||
| - Output assembly should follow `StellaOps.Authority.Plugin.<Name>.dll` so the host’s search pattern picks it up. | ||||
| - Place the compiled DLL plus dependencies under `StellaOps.Authority.PluginBinaries` for offline deployments; include hashes/signatures in release notes (Security Guild guidance forthcoming). | ||||
| - Document any external prerequisites (e.g., CA cert bundle) in your plug-in README. | ||||
| - Update `etc/authority.plugins/<plugin>.yaml` samples and include deterministic SHA256 hashes for optional bootstrap payloads when distributing Offline Kit artefacts. | ||||
|  | ||||
| [^ldap-rfc]: Lightweight Directory Access Protocol (LDAPv3) specification — [RFC 4511](https://datatracker.ietf.org/doc/html/rfc4511). | ||||
|  | ||||
| ## 12. Checklist & Handoff | ||||
| - ✅ Capabilities declared and validated in automated tests. | ||||
| - ✅ Bootstrap workflows documented (if `bootstrap` capability used) and repeatable. | ||||
| - ✅ Local smoke test + unit/integration suites green (`dotnet test`). | ||||
| - ✅ Operational docs updated: configuration keys, secrets guidance, troubleshooting. | ||||
| - Submit the developer guide update referencing PLG6/DOC4 and tag DevEx + Docs reviewers for sign-off. | ||||
|  | ||||
| --- | ||||
| Mermaid sources for the embedded diagrams live under `docs/assets/authority/`. Regenerate the SVG assets with your preferred renderer before committing future updates so the visuals stay in sync with the `.mmd` definitions. | ||||
| # Authority Plug-in Developer Guide | ||||
|  | ||||
| > **Status:** Updated 2025-10-11 (AUTHPLUG-DOCS-01-001) with lifecycle + limiter diagrams and refreshed rate-limit guidance aligned to PLG6 acceptance criteria. | ||||
|  | ||||
| ## 1. Overview | ||||
| Authority plug-ins extend the **StellaOps Authority** service with custom identity providers, credential stores, and client-management logic. Unlike Concelier plug-ins (which ingest or export advisories), Authority plug-ins participate directly in authentication flows: | ||||
|  | ||||
| - **Use cases:** integrate corporate directories (LDAP/AD)[^ldap-rfc], delegate to external IDPs, enforce bespoke password/lockout policies, or add client provisioning automation. | ||||
| - **Constraints:** plug-ins load only during service start (no hot-reload), must function without outbound internet access, and must emit deterministic results for identical configuration input. | ||||
| - **Ship targets:** build against the host’s .NET 10 preview SDK, honour offline-first requirements, and surface actionable diagnostics so operators can triage issues from `/ready`. | ||||
|  | ||||
| ## 2. Architecture Snapshot | ||||
| Authority hosts follow a deterministic plug-in lifecycle. The exported diagram (`docs/assets/authority/authority-plugin-lifecycle.svg`) mirrors the steps below; regenerate it from the Mermaid source if you update the flow. | ||||
|  | ||||
| 1. **Configuration load** – `AuthorityPluginConfigurationLoader` resolves YAML manifests under `etc/authority.plugins/`. | ||||
| 2. **Assembly discovery** – the shared `PluginHost` scans `StellaOps.Authority.PluginBinaries` for `StellaOps.Authority.Plugin.*.dll` assemblies. | ||||
| 3. **Registrar execution** – each assembly is searched for `IAuthorityPluginRegistrar` implementations. Registrars bind options, register services, and optionally queue bootstrap tasks. | ||||
| 4. **Runtime** – the host resolves `IIdentityProviderPlugin` instances, uses capability metadata to decide which OAuth grants to expose, and invokes health checks for readiness endpoints. | ||||
|  | ||||
|  | ||||
|  | ||||
| _Source:_ `docs/assets/authority/authority-plugin-lifecycle.mmd` | ||||
|  | ||||
| **Data persistence primer:** the standard Mongo-backed plugin stores users in collections named `authority_users_<pluginName>` and lockout metadata in embedded documents. Additional plugins must document their storage layout and provide deterministic collection naming to honour the Offline Kit replication process. | ||||
|  | ||||
| ## 3. Capability Metadata | ||||
| Capability flags let the host reason about what your plug-in supports: | ||||
|  | ||||
| - Declare capabilities in your descriptor using the string constants from `AuthorityPluginCapabilities` (`password`, `mfa`, `clientProvisioning`, `bootstrap`). The configuration loader now validates these tokens and rejects unknown values at startup. | ||||
| - `AuthorityIdentityProviderCapabilities.FromCapabilities` projects those strings into strongly typed booleans (`SupportsPassword`, etc.). Authority Core will use these flags when wiring flows such as the password grant. Built-in plugins (e.g., Standard) will fail fast or force-enable required capabilities if the descriptor is misconfigured, so keep manifests accurate. | ||||
| - Typical configuration (`etc/authority.plugins/standard.yaml`): | ||||
|   ```yaml | ||||
|   plugins: | ||||
|     descriptors: | ||||
|       standard: | ||||
|         assemblyName: "StellaOps.Authority.Plugin.Standard" | ||||
|         capabilities: | ||||
|           - password | ||||
|           - bootstrap | ||||
|   ``` | ||||
| - Only declare a capability if the plug-in genuinely implements it. For example, if `SupportsClientProvisioning` is `true`, the plug-in must supply a working `IClientProvisioningStore`. | ||||
|  | ||||
| **Operational reminder:** the Authority host surfaces capability summaries during startup (see `AuthorityIdentityProviderRegistry` log lines). Use those logs during smoke tests to ensure manifests align with expectations. | ||||
|  | ||||
| **Configuration path normalisation:** Manifest-relative paths (e.g., `tokenSigning.keyDirectory: "../keys"`) are resolved against the YAML file location and environment variables are expanded before validation. Plug-ins should expect to receive an absolute, canonical path when options are injected. | ||||
|  | ||||
| **Password policy guardrails:** The Standard registrar logs a warning when a plug-in weakens the default password policy (minimum length or required character classes). Keep overrides at least as strong as the compiled defaults—operators treat the warning as an actionable security deviation. | ||||
|  | ||||
| ## 4. Project Scaffold | ||||
| - Target **.NET 10 preview**, enable nullable, treat warnings as errors, and mark Authority plug-ins with `<IsAuthorityPlugin>true</IsAuthorityPlugin>`. | ||||
| - Minimum references: | ||||
|   - `StellaOps.Authority.Plugins.Abstractions` (contracts & capability helpers) | ||||
|   - `StellaOps.Plugin` (hosting/DI helpers) | ||||
|   - `StellaOps.Auth.*` libraries as needed for shared token utilities (optional today). | ||||
| - Example `.csproj` (trimmed from `StellaOps.Authority.Plugin.Standard`): | ||||
|   ```xml | ||||
|   <Project Sdk="Microsoft.NET.Sdk"> | ||||
|     <PropertyGroup> | ||||
|       <TargetFramework>net10.0</TargetFramework> | ||||
|       <Nullable>enable</Nullable> | ||||
|       <TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||||
|       <IsAuthorityPlugin>true</IsAuthorityPlugin> | ||||
|     </PropertyGroup> | ||||
|     <ItemGroup> | ||||
|       <ProjectReference Include="..\StellaOps.Authority.Plugins.Abstractions\StellaOps.Authority.Plugins.Abstractions.csproj" /> | ||||
|       <ProjectReference Include="..\..\StellaOps.Plugin\StellaOps.Plugin.csproj" /> | ||||
|     </ItemGroup> | ||||
|   </Project> | ||||
|   ``` | ||||
|   (Add other references—e.g., MongoDB driver, shared auth libraries—according to your implementation.) | ||||
|  | ||||
| ## 5. Implementing `IAuthorityPluginRegistrar` | ||||
| - Create a parameterless registrar class that returns your plug-in type name via `PluginType`. | ||||
| - Use `AuthorityPluginRegistrationContext` to: | ||||
|   - Bind options (`AddOptions<T>(pluginName).Bind(...)`). | ||||
|   - Register singletons for stores/enrichers using manifest metadata. | ||||
|   - Register any hosted bootstrap tasks (e.g., seed admin users). | ||||
| - Always validate configuration inside `PostConfigure` and throw meaningful `InvalidOperationException` to fail fast during startup. | ||||
| - Use the provided `ILoggerFactory` from DI; avoid static loggers or console writes. | ||||
| - Example skeleton: | ||||
|   ```csharp | ||||
|   internal sealed class MyPluginRegistrar : IAuthorityPluginRegistrar | ||||
|   { | ||||
|       public string PluginType => "my-custom"; | ||||
|  | ||||
|       public void Register(AuthorityPluginRegistrationContext context) | ||||
|       { | ||||
|           var name = context.Plugin.Manifest.Name; | ||||
|  | ||||
|           context.Services.AddOptions<MyPluginOptions>(name) | ||||
|               .Bind(context.Plugin.Configuration) | ||||
|               .PostConfigure(opts => opts.Validate(name)); | ||||
|  | ||||
|           context.Services.AddSingleton<IIdentityProviderPlugin>(sp => | ||||
|               new MyIdentityProvider(context.Plugin, sp.GetRequiredService<MyCredentialStore>(), | ||||
|                                      sp.GetRequiredService<MyClaimsEnricher>(), | ||||
|                                      sp.GetRequiredService<ILogger<MyIdentityProvider>>())); | ||||
|       } | ||||
|   } | ||||
|   ``` | ||||
|  | ||||
| ## 6. Identity Provider Surface | ||||
| - Implement `IIdentityProviderPlugin` to expose: | ||||
|   - `IUserCredentialStore` for password validation and user CRUD. | ||||
|   - `IClaimsEnricher` to append roles/attributes onto issued principals. | ||||
|   - Optional `IClientProvisioningStore` for machine-to-machine clients. | ||||
|   - `AuthorityIdentityProviderCapabilities` to advertise supported flows. | ||||
| - Password guidance: | ||||
|   - Standard plug-in hashes via `ICryptoProvider` using Argon2id by default and emits PHC-compliant strings. Successful PBKDF2 logins trigger automatic rehashes so migrations complete gradually. See `docs/security/password-hashing.md` for tuning advice. | ||||
|   - Enforce password policies before hashing to avoid storing weak credentials. | ||||
| - Health checks should probe backing stores (e.g., Mongo `ping`) and return `AuthorityPluginHealthResult` so `/ready` can surface issues. | ||||
| - When supporting additional factors (e.g., TOTP), implement `SupportsMfa` and document the enrolment flow for resource servers. | ||||
|  | ||||
| ## 7. Configuration & Secrets | ||||
| - Authority looks for manifests under `etc/authority.plugins/`. Each YAML file maps directly to a plug-in name. | ||||
| - Support environment overrides using `STELLAOPS_AUTHORITY_PLUGINS__DESCRIPTORS__<NAME>__...`. | ||||
| - Never store raw secrets in git: allow operators to supply them via `.local.yaml`, environment variables, or injected secret files. Document which keys are mandatory. | ||||
| - Validate configuration as soon as the registrar runs; use explicit error messages to guide operators. The Standard plug-in now enforces complete bootstrap credentials (username + password) and positive lockout windows via `StandardPluginOptions.Validate`. | ||||
| - Cross-reference bootstrap workflows with `docs/ops/authority_bootstrap.md` (to be published alongside CORE6) so operators can reuse the same payload formats for manual provisioning. | ||||
| - `passwordHashing` inherits defaults from `authority.security.passwordHashing`. Override only when hardware constraints differ per plug-in: | ||||
|   ```yaml | ||||
|   passwordHashing: | ||||
|     algorithm: Argon2id | ||||
|     memorySizeInKib: 19456 | ||||
|     iterations: 2 | ||||
|     parallelism: 1 | ||||
|   ``` | ||||
|   Invalid values (≤0) fail fast during startup, and legacy PBKDF2 hashes rehash automatically once the new algorithm succeeds. | ||||
|  | ||||
| ### 7.1 Token Persistence Contract | ||||
| - The host automatically persists every issued principal (access, refresh, device, authorization code) in `authority_tokens`. Plug-in code **must not** bypass this store; use the provided `IAuthorityTokenStore` helpers when implementing custom flows. | ||||
| - When a plug-in disables a subject or client outside the standard handlers, call `IAuthorityTokenStore.UpdateStatusAsync(...)` for each affected token so revocation bundles stay consistent. | ||||
| - Supply machine-friendly `revokedReason` codes (`compromised`, `rotation`, `policy`, `lifecycle`, etc.) and optional `revokedMetadata` entries when invalidating credentials. These flow straight into `revocation-bundle.json` and should remain deterministic. | ||||
| - Token scopes should be normalised (trimmed, unique, ordinal sort) before returning from plug-in verification paths. `TokenPersistenceHandlers` will keep that ordering for downstream consumers. | ||||
|  | ||||
| ### 7.2 Claims & Enrichment Checklist | ||||
| - Authority always sets the OpenID Connect basics: `sub`, `client_id`, `preferred_username`, optional `name`, and `role` (for password flows). Plug-ins must use `IClaimsEnricher` to append additional claims in a **deterministic** order (sort arrays, normalise casing) so resource servers can rely on stable shapes. | ||||
| - Recommended enrichment keys: | ||||
|   - `stellaops.realm` – plug-in/tenant identifier so services can scope policies. | ||||
|   - `stellaops.subject.type` – values such as `human`, `service`, `bootstrap`. | ||||
|   - `groups` / `projects` – sorted arrays describing operator entitlements. | ||||
| - Claims visible in tokens should mirror what `/token` and `/userinfo` emit. Avoid injecting sensitive PII directly; mark values with `ClassifiedString.Personal` inside the plug-in so audit sinks can tag them appropriately. | ||||
| - For client-credential flows, remember to enrich both the client principal and the validation path (`TokenValidationHandlers`) so refresh flows keep the same metadata. | ||||
|  | ||||
| ### 7.3 Revocation Bundles & Reasons | ||||
| - Use `IAuthorityRevocationStore` to record subject/client/token revocations when credentials are deleted or rotated. Stick to the standard categories (`token`, `subject`, `client`, `key`). | ||||
| - Include a deterministic `reason` string and optional `reasonDescription` so operators understand *why* a subject was revoked when inspecting bundles offline. | ||||
| - Plug-ins should populate `metadata` with stable keys (e.g., `revokedBy`, `sourcePlugin`, `ticketId`) to simplify SOC correlation. The keys must be lowercase, ASCII, and free of secrets—bundles are mirrored to air-gapped agents. | ||||
|  | ||||
| ## 8. Rate Limiting & Lockout Interplay | ||||
| Rate limiting and account lockouts are complementary controls. Plug-ins must surface both deterministically so operators can correlate limiter hits with credential rejections. | ||||
|  | ||||
| **Baseline quotas** (from `docs/dev/authority-rate-limit-tuning-outline.md`): | ||||
|  | ||||
| | Endpoint | Default policy | Notes | | ||||
| |----------|----------------|-------| | ||||
| | `/token` | 30 requests / 60s, queue 0 | Drop to 10/60s for untrusted ranges; raise only with WAF + monitoring. | | ||||
| | `/authorize` | 60 requests / 60s, queue 10 | Reduce carefully; interactive UX depends on headroom. | | ||||
| | `/internal/*` | Disabled by default; recommended 5/60s when enabled | Keep queue 0 for bootstrap APIs. | | ||||
|  | ||||
| **Retry metadata:** The middleware stamps `Retry-After` plus tags `authority.client_id`, `authority.remote_ip`, and `authority.endpoint`. Plug-ins should keep these tags intact when crafting responses or telemetry so dashboards remain consistent. | ||||
|  | ||||
| **Lockout counters:** Treat lockouts as **subject-scoped** decisions. When multiple instances update counters, reuse the deterministic tie-breakers documented in `src/DEDUP_CONFLICTS_RESOLUTION_ALGO.md` (freshness overrides, precedence, and stable hashes) to avoid divergent lockout states across replicas. | ||||
|  | ||||
| **Alerting hooks:** Emit structured logs/metrics when either the limiter or credential store rejects access. Suggested gauges include `aspnetcore_rate_limiting_rejections_total{limiter="authority-token"}` and any custom `auth.plugins.<pluginName>.lockouts_total` counter. | ||||
|  | ||||
|  | ||||
|  | ||||
| _Source:_ `docs/assets/authority/authority-rate-limit-flow.mmd` | ||||
|  | ||||
| ## 9. Logging, Metrics, and Diagnostics | ||||
| - Always log via the injected `ILogger<T>`; include `pluginName` and correlation IDs where available. | ||||
| - Activity/metric names should align with `AuthorityTelemetry` constants (`service.name=stellaops-authority`). | ||||
| - Expose additional diagnostics via structured logging rather than writing custom HTTP endpoints; the host will integrate these into `/health` and `/ready`. | ||||
| - Emit metrics with stable names (`auth.plugins.<pluginName>.*`) when introducing custom instrumentation; coordinate with the Observability guild to reserve prefixes. | ||||
|  | ||||
| ## 10. Testing & Tooling | ||||
| - Unit tests: use Mongo2Go (or similar) to exercise credential stores without hitting production infrastructure (`StandardUserCredentialStoreTests` is a template). | ||||
| - Determinism: fix timestamps to UTC and sort outputs consistently; avoid random GUIDs unless stable. | ||||
| - Smoke tests: launch `dotnet run --project src/Authority/StellaOps.Authority/StellaOps.Authority` with your plug-in under `StellaOps.Authority.PluginBinaries` and verify `/ready`. | ||||
| - Example verification snippet: | ||||
|   ```csharp | ||||
|   [Fact] | ||||
|   public async Task VerifyPasswordAsync_ReturnsSuccess() | ||||
|   { | ||||
|       var store = CreateCredentialStore(); | ||||
|       await store.UpsertUserAsync(new AuthorityUserRegistration("alice", "Pa55!", null, null, false, | ||||
|           Array.Empty<string>(), new Dictionary<string, string?>()), CancellationToken.None); | ||||
|  | ||||
|       var result = await store.VerifyPasswordAsync("alice", "Pa55!", CancellationToken.None); | ||||
|       Assert.True(result.Succeeded); | ||||
|       Assert.True(result.User?.Roles.Count == 0); | ||||
|   } | ||||
|   ``` | ||||
|  | ||||
| ## 11. Packaging & Delivery | ||||
| - Output assembly should follow `StellaOps.Authority.Plugin.<Name>.dll` so the host’s search pattern picks it up. | ||||
| - Place the compiled DLL plus dependencies under `StellaOps.Authority.PluginBinaries` for offline deployments; include hashes/signatures in release notes (Security Guild guidance forthcoming). | ||||
| - Document any external prerequisites (e.g., CA cert bundle) in your plug-in README. | ||||
| - Update `etc/authority.plugins/<plugin>.yaml` samples and include deterministic SHA256 hashes for optional bootstrap payloads when distributing Offline Kit artefacts. | ||||
|  | ||||
| [^ldap-rfc]: Lightweight Directory Access Protocol (LDAPv3) specification — [RFC 4511](https://datatracker.ietf.org/doc/html/rfc4511). | ||||
|  | ||||
| ## 12. Checklist & Handoff | ||||
| - ✅ Capabilities declared and validated in automated tests. | ||||
| - ✅ Bootstrap workflows documented (if `bootstrap` capability used) and repeatable. | ||||
| - ✅ Local smoke test + unit/integration suites green (`dotnet test`). | ||||
| - ✅ Operational docs updated: configuration keys, secrets guidance, troubleshooting. | ||||
| - Submit the developer guide update referencing PLG6/DOC4 and tag DevEx + Docs reviewers for sign-off. | ||||
|  | ||||
| --- | ||||
| Mermaid sources for the embedded diagrams live under `docs/assets/authority/`. Regenerate the SVG assets with your preferred renderer before committing future updates so the visuals stay in sync with the `.mmd` definitions. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user