diff --git a/.agent/plans/2026-06-03-sdk-namespace-hygiene/implementation-plan.md b/.agent/plans/2026-06-03-sdk-namespace-hygiene/implementation-plan.md new file mode 100644 index 0000000..b721626 --- /dev/null +++ b/.agent/plans/2026-06-03-sdk-namespace-hygiene/implementation-plan.md @@ -0,0 +1,372 @@ +--- +title: "SDK Namespace Hygiene — Implementation Plan" +description: Phased plan to improve AAuth SDK namespace layout and public type naming +ms.date: 2026-06-03 +--- + +This plan addresses the four issues in [research.md](research.md). Phasing is +**non-breaking first**: Phase 1 ships the high-value DI ergonomics win on its +own; Phases 2–5 are the breaking renames/moves. + +> **Policy (2026-06-03):** SDK is in **alpha** — clean breaks are acceptable. +> **No** `[Obsolete]` shims or type-forwarders. Apply renames/moves directly and +> update all in-repo callers (`src/`, `samples/`, `tests/`, `docs/`) in the same +> change. Phases are sequenced for review clarity only; there is no version gate +> blocking the breaking phases. + +## Phase 1: Move DI/Builder Extensions to Conventional Namespaces + +Surface the registration API with no extra `using`. This is the only +non-breaking phase (for callers relying on implicit usings) and delivers the +biggest day-one ergonomics gain. + +### Files + +| Action | Path | Change | +|--------|------|--------| +| Edit | `src/AAuth/DependencyInjection/AAuthResourceServiceCollectionExtensions.cs` | `namespace` → `Microsoft.Extensions.DependencyInjection` | +| Edit | `src/AAuth/DependencyInjection/AAuthAgentServiceCollectionExtensions.cs` | `namespace` → `Microsoft.Extensions.DependencyInjection` | +| Edit | `src/AAuth/DependencyInjection/AAuthDiscoveryServiceCollectionExtensions.cs` | `namespace` → `Microsoft.Extensions.DependencyInjection` | +| Edit | `src/AAuth/DependencyInjection/AAuthApplicationBuilderExtensions.cs` | `namespace` → `Microsoft.AspNetCore.Builder` | +| Edit | option records (`AAuthResourceOptions`, `AAuthAgentOptions`, `AAuthDiscoveryOptions`, `AAuthResourcePipelineOptions`) | `namespace` → `AAuth` root | +| Edit | extension `.cs` files | add `using AAuth;` for the relocated options where needed | +| Edit | in-repo callers | drop now-redundant `using AAuth.DependencyInjection;`; add `using AAuth;` only where an options type is named | + +### Implementation notes + +- Keep the class names unchanged — only the `namespace` declaration moves. +- The extension methods reference their options types; once options live in + `AAuth`, add `using AAuth;` inside the extension files. +- Verify the four-party `AAuthAccessServerOptions` (in `AAuth.Server`) is **not** + in scope here — it is a host-options type, not a DI-extension options record. +- Update in-repo consumers found in research (tests under + `tests/AAuth.Tests/DependencyInjection/`, `tests/AAuth.Conformance/`). + +### Implementation Decisions + +- Options records land in the `AAuth` root (resolved 2026-06-03) — keeps the + registration call reachable with a single `using AAuth;` for the named options + type, no `AAuth.Configuration` namespace. + +### Definition of Done + +- [x] DI extension methods compile under `Microsoft.Extensions.DependencyInjection` +- [x] App-builder extensions compile under `Microsoft.AspNetCore.Builder` +- [x] Option records relocated and referenced correctly +- [x] A minimal `Program.cs` can call `services.AddAAuthResource(...)` with **no** + `using AAuth.DependencyInjection;` +- [x] In-repo callers updated; redundant usings removed +- [x] `dotnet build AAuth.slnx -v q` clean +- [x] `dotnet test tests/AAuth.Tests` and `tests/AAuth.Conformance` green +- [ ] Docs (`docs/reference/dependency-injection.md`, getting-started) updated to + drop the extra `using` _(folded into the single Phase 6 doc sweep)_ + +--- + +## Phase 2: Resolve `AAuth` Type-Name Stutter + +Drop the redundant `AAuth` prefix where it adds no disambiguation; keep it where +collision/brand identity matters (see research buckets). + +### Candidate renames (confirm final list in Implementation Decisions) + +| From | To | Namespace | +|------|----|-----------| +| `AAuthMission` | `Mission` | `AAuth.Agent` | +| `AAuthAgentId` | `AgentId` | `AAuth.Identifiers` | +| `AAuthServerId` | `ServerId` | `AAuth.Identifiers` | +| `AAuthInteraction` | `Interaction` | `AAuth.Headers` | +| `AAuthVerificationResult` | _kept (see decision)_ | `AAuth.Server` | +| `AAuthClaimsRequirement` | `ClaimsRequirement` | `AAuth.Headers` | +| `AAuthClaimsResponse` | `ClaimsResponse` | `AAuth.Headers` | + +### Keep (do not rename) + +`AAuthKey`, `IAAuthKey`, `AAuthVerifier`, `AAuthClientBuilder`, `AAuthConstants`, +`AAuthDiagnostics`, `AAuthLevel`, `AAuthAccessMode`, `AAuthUrl`. + +### Implementation notes + +- Use the language-server rename so all references update atomically. +- Clean break: do **not** add `[Obsolete]` aliases (alpha policy). +- Sweep docs and samples for the old names. + +### Implementation Decisions + +- Compat-shim policy: **clean break, no shims** (alpha) — resolved 2026-06-03. +- Final rename list: the candidate table above is adopted as-is (resolved + 2026-06-03). +- **Deviation (2026-06-03):** `AAuthVerificationResult` is **kept** (not renamed + to `VerificationResult`). A distinct public `VerificationResult` already exists + in `AAuth.Server` (the middleware's raw `HttpContext.Items` result), so the + `AAuth` prefix provides real disambiguation here. Consolidating the two result + types is a behavior change and out of scope for a names-only phase. + +### Definition of Done + +- [x] Agreed renames applied via language-server rename +- [x] No remaining references to old names in `src/`, `samples/`, `tests/` _(docs + swept in Phase 6)_ +- [x] `dotnet build AAuth.slnx -v q` clean; full test suite green +- [ ] E2E (`tests/e2e`, sample `playwright-tests`) green _(run once at the + consolidated validation before Phase 7)_ + +--- + +## Phase 3: Consolidate the Access-Server Feature Namespace + +Co-locate the four-party federated types so the feature lives in one namespace. + +### Files + +| Action | Path | Change | +|--------|------|--------| +| Edit | `src/AAuth/Tokens/AccessServerClient.cs` | `namespace` → `AAuth.Access` | +| Edit | `src/AAuth/Tokens/AccessServerRequest.cs` | `namespace` → `AAuth.Access` | +| Edit | `src/AAuth/Server/IAccessPolicy.cs` (+ `AccessDecision`) | `namespace` → `AAuth.Access` | +| Edit | `src/AAuth/Server/IAccessPendingStore.cs` (+ `AccessPendingEntry`, `InMemoryAccessPendingStore`) | `namespace` → `AAuth.Access` | +| Edit | `src/AAuth/Server/AAuthAccessServerEndpoints.cs` (+ `AAuthAccessServerOptions`) | `namespace` → `AAuth.Access` | +| Optional | move files into a new `src/AAuth/Access/` folder to match namespace | folder reorg | + +### Implementation notes + +- Confirm namespace name with research Open Question 3 (`AAuth.Access` vs + `AAuth.Federation`). +- Update `using` sites in `samples/MockAccessServer`, `samples/MockPersonServer`, + and the federation tests. +- Decide whether to physically move files into an `Access/` folder (cleaner) or + only change the `namespace` declaration (smaller diff). + +### Implementation Decisions + +- Namespace name: `AAuth.Access` (resolved 2026-06-03). +- Folder reorg: yes — move the files into `src/AAuth/Access/` so the folder + mirrors the namespace (resolved 2026-06-03). + +### Definition of Done + +- [x] All four-party types share one namespace +- [x] Sample servers + federation tests updated and building +- [ ] `make demo` and `make demo-keycloak` start cleanly _(consolidated validation)_ +- [ ] Federated e2e (`npx playwright test --grep "Federated"`) green _(consolidated validation)_ + +--- + +## Phase 4: Surface Client Builders in a First-Class Namespace + +Move the headline builders out of `AAuth.HttpSig`. + +### Files + +| Action | Path | Change | +|--------|------|--------| +| Edit | `src/AAuth/HttpSig/AAuthClientBuilder.cs` | `namespace` → chosen target | +| Edit | `src/AAuth/HttpSig/SelfIssuingBuilder.cs` | `namespace` → chosen target | +| Edit | `src/AAuth/HttpSig/EnrolledBuilder.cs` | `namespace` → chosen target | +| Edit | `src/AAuth/HttpSig/BootstrapBuilder.cs` | `namespace` → chosen target | +| Optional | move files to a matching folder | folder reorg | + +### Implementation notes + +- Choose target per research Issue 4: **Option A** (root `AAuth`) for max "one + using", or **Option B** (`AAuth.Client`) for client/transport separation. +- Leave signing internals (`AAuthSigningHandler`, `ISignatureKeyProvider`, + `SignatureKeyParser`, resolvers) in `AAuth.HttpSig`. +- Update `using AAuth.HttpSig;` consumer sites that only needed the builders. + +### Implementation Decisions + +- Option A — builders land in the root `AAuth` namespace (resolved 2026-06-03) + for the maximum "one `using`" ergonomics; signing internals stay in + `AAuth.HttpSig`. + +### Definition of Done + +- [x] Builders relocated; signing internals remain in `AAuth.HttpSig` +- [x] Consumers reach `AAuthClientBuilder` via root `AAuth` (Option A) or + `AAuth.Client` (Option B) +- [x] `dotnet build AAuth.slnx -v q` clean; full suite green (e2e consolidated) +- [ ] Quick-start docs show the simplified `using` _(Phase 6 doc sweep)_ + +--- + +## Phase 5: Split `AAuth.Server` into Concern-Based Sub-Namespaces + +After Phase 3 extracts the four-party federation types into `AAuth.Access`, the +~20 remaining `AAuth.Server` types cluster cleanly by concern. Split them into +sub-namespaces so the (currently 25-file) `Server/` folder is navigable and each +namespace declares its purpose. This is low-risk because Phase 1 routes the +common path through `services.AddAAuthResource(...)` / `app.UseAAuth...()` in the +Microsoft namespaces — most apps never import `AAuth.Server.*` directly. + +### Proposed grouping + +| Target namespace | Types | Theme | +|---|---|---| +| `AAuth.Server.Verification` | `AAuthVerificationMiddleware`, `AAuthVerificationOptions`, `AAuthVerificationResult`, `AAuthAuthenticationHandler`, `AAuthHttpContextExtensions`, `AAuthLevel`, `AAuthAccessMode` | Inbound request verification + the ASP.NET auth handler and `HttpContext` accessors | +| `AAuth.Server.Challenge` | `AAuthChallengeMiddleware`, `ChallengeOptions` | Emitting `401`/`WWW-Authenticate` challenges | +| `AAuth.Server.Authorization` | `AAuthScopeRequirement`, `AAuthScopeHandler` | ASP.NET Core scope authorization | +| `AAuth.Server.Metadata` | `WellKnownEndpoints`, `AAuthResourceMetadataOptions`, `AAuthAgentMetadataOptions`, `AAuthPersonServerMetadataOptions` | `.well-known/*` discovery documents | +| `AAuth.Server.CallChaining` | `CallChainingRouter`, `CallChainingHandler`, `CallChainingOptions`, `UpstreamAuthTokenFeature` | Multi-hop `act` delegation | +| `AAuth.Server` (unchanged) | `IJtiStore`, `InMemoryJtiStore`, `IOpaqueTokenStore`, `RevocationEndpoint` | Replay/JTI + opaque-token persistence and revocation — left at the root to avoid over-fragmenting | + +### Implementation notes + +- Sequence **after** Phase 3 so the `AAuth.Access` types are already gone from + `Server/` and do not need re-touching. +- Mirror each namespace with a folder: `src/AAuth/Server/Verification/`, + `.../Challenge/`, `.../Authorization/`, `.../Metadata/`, `.../CallChaining/`. +- Update the DI extension files (now in `Microsoft.Extensions.DependencyInjection`) + to add the new `using AAuth.Server.*;` imports they need. +- Update in-repo callers that import `AAuth.Server` directly (sample servers, + conformance/integration tests) to the specific sub-namespaces. + +### Implementation Decisions + +- Adopt the five-group split above (resolved 2026-06-03). +- Keep the stores group (`IJtiStore`, `InMemoryJtiStore`, `IOpaqueTokenStore`, + `RevocationEndpoint`) at the `AAuth.Server` root — no dedicated + `AAuth.Server.Stores` (resolved 2026-06-03). +- `AAuthResourceMetadataOptions` is declared inside `WellKnownEndpoints.cs`, so + it moves to `AAuth.Server.Metadata` with that file (no separate file). +- `AAuthAccessServerMetadataOptions` was also folded into + `AAuth.Server.Metadata` (it is a `.well-known` metadata type); the grouping + table above omitted it but it belongs with the other metadata options. + +### Definition of Done + +- [x] `Server/` types relocated to the agreed sub-namespaces and matching folders +- [x] DI extensions and in-repo callers updated with the new imports +- [x] No half-split namespace (every moved type and its references consistent) +- [x] `dotnet build AAuth.slnx -v q` clean; full unit + conformance suites green +- [ ] `make demo` / `make demo-keycloak` start; e2e green _(consolidated run before Phase 7)_ + +--- + +## Phase 6: Propagate Renames to Samples, Docs, and Embedded Snippets + +Phases 1–5 update `src/` and the directly-referenced callers, but the SDK's +*teaching surface* also embeds the public type/namespace names as **literal +text** that the compiler never checks: documentation prose, Markdown code +fences, and the C# snippets rendered inside the GuidedTour and SampleApp UIs. +These must be swept by hand so the visible API matches the shipped API. + +### Surfaces to sweep + +| Surface | Where | Why it is not caught by build | +|---|---|---| +| Sample app source | `samples/**/*.cs`, `samples/**/*.razor` | Builds, but `using` lines and type names need updating to the new namespaces (e.g. `samples/SampleApp/Program.cs` imports `AAuth.DependencyInjection`, `AAuth.Server`) | +| GuidedTour step snippets | `samples/GuidedTour/CodeSnippets.cs`, `StepRecord.CodeSnippet` | C# rendered as **strings** in `PayloadInspector.razor`; not compiled | +| GuidedTour highlighter token list | `samples/GuidedTour/Components/PayloadInspector.razor` (the `AAuthKey|AgentTokenBuilder|AAuthClientBuilder|...` regex) | Highlight keyword list references type names literally | +| GuidedTour / SampleApp prose | `Tour.razor`, `Home.razor`, other `Components/Pages/*.razor` | Inline `` spans and explanatory copy | +| Markdown docs | `docs/**/*.md`, `README.md`, `src/AAuth/README.md`, `samples/**/README.md` | Code fences + prose reference namespaces/types as text | +| Plan/research embedded examples | this folder + sibling plans, only if they show now-renamed public API in a "current usage" context | Keep historical decisions intact; update only illustrative current-API snippets | + +### Implementation notes + +- Run this phase **after** each breaking phase lands, or once after Phases 2–5, + to avoid sweeping the same files twice. Prefer a single sweep after Phase 5. +- For each renamed/moved symbol, grep the whole repo (including `*.razor`, + `*.md`, `*.cs` string literals) for the **old** name and update every hit. +- Update the GuidedTour highlighter regex token list in `PayloadInspector.razor` + so renamed types still colorize. +- Re-run the GuidedTour and SampleApp so the embedded snippets render and the + flows still execute (snippets are illustrative but must match what the running + sample actually does). +- Do **not** rewrite historical plan decisions; only fix illustrative + current-API snippets per the plan-workflow rules. + +### Definition of Done + +- [x] No occurrence of any old namespace/type name remains in `samples/`, + `docs/`, `README.md`, `src/AAuth/README.md` (grep clean for each renamed symbol) +- [x] `samples/GuidedTour/CodeSnippets.cs` and any `StepRecord.CodeSnippet` + strings show the new namespaces/types +- [x] GuidedTour highlighter regex updated for renamed types +- [x] `Tour.razor` / `Home.razor` inline `` references updated +- [x] `dotnet build AAuth.slnx -v q` clean (samples included) +- [x] `make demo` and `make demo-keycloak` start; GuidedTour (:5400) and + SampleApp (:5240) render the updated snippets +- [x] Sample `playwright-tests` and `tests/e2e` green + +--- + +## Phase 7: Internal Reviewer-Agent Verification (No-Regression + Spec Alignment) + +A final independent pass: dispatch an **internal reviewer agent** to confirm the +refactor changed *only* names/namespaces — not behavior — and that all +user-visible terminology aligns **100%** with the protocol specifications in +[aauth-spec/](../../aauth-spec/). + +### Reviewer scope + +| Check | Source of truth | Pass criterion | +|---|---|---| +| No functional regression | Phases 1–6 diffs | Every change is a namespace move, type rename, `using` update, or literal-text edit. **Zero** logic/control-flow/signature-shape changes beyond the rename | +| Public-surface consistency | `src/AAuth/**` | New names are internally consistent (no half-renamed members, no orphaned old names) | +| Spec terminology alignment | `aauth-spec/draft-hardt-oauth-aauth-protocol.md`, `draft-hardt-aauth-bootstrap.md`, `draft-hardt-aauth-r3.md`, `SPEC-VERSION.md` | Every user-visible term (type names, namespace stems, doc prose, GuidedTour/SampleApp copy) matches the spec's canonical vocabulary — e.g. "Access Server", "Person Server", "Agent Provider", "auth token", "resource token", "agent token", `sig=jwt`, `dwk`, `cnf.jwk`, federated/deferred wording | +| Docs/snippets match running behavior | `samples/`, `docs/` | Embedded snippets reflect the actual SDK calls the samples make post-rename | + +### How to run + +- Dispatch the **Implementation Validator** agent (and/or a spec-terminology + reviewer) with: the plan + research docs, the full diff of Phases 1–6, and the + `aauth-spec/` directory as the terminology source of truth. +- Ask for **severity-graded findings**: Critical (behavior changed / spec + mismatch on a normative term), Major (inconsistent or half-applied rename), + Minor (stylistic/doc nit). +- Triage findings: fix Critical/Major before closing; capture Minor as follow-ups + or fix inline. + +### Definition of Done + +- [x] Reviewer agent run completed with findings recorded +- [x] Zero Critical findings (no behavior regression; no normative-term mismatch) +- [x] Zero unresolved Major findings (renames fully and consistently applied) +- [x] Spec terminology verified 100% aligned across types, docs, and sample copy +- [x] Full build + unit + conformance + e2e suites green +- [x] Findings summary appended to this plan (or a linked review note) + +### Verification Results (2026-06-03) + +Independent verification run on the full `origin/main...HEAD` diff (121 files, ++982/-243). The Implementation Validator subagent lacked workspace tools, so the +review was performed directly with diff analysis, normalized-rename matching, and +targeted greps. + +- **No behavior change (Critical: 0).** Normalizing every `.cs` hunk by the + rename table leaves exactly three categories of change: `using` directives, + `namespace` declarations, and renamed-symbol equivalents. The only non-rename + content edit is the intentional exception-message grammar fix in + `src/AAuth/Access/AccessServerClient.cs` ("expected an AAuthClaimsResponse." → + "expected a ClaimsResponse."). Zero logic, control-flow, signature-shape, + serialization, or wire-format changes. +- **Rename completeness (Major: 0).** Grep across `src/`, `samples/`, `tests/`, + `docs/`, `README.md` finds no lingering `AAuthMission`/`AAuthAgentId`/ + `AAuthServerId`/`AAuthInteraction`/`AAuthClaimsRequirement`/`AAuthClaimsResponse`. + KEPT items confirmed present: `AAuthVerificationResult` (distinct from the + middleware's `VerificationResult`), `AAuthMissionHeader`, + `AAuthConstants.AAuthMission` (= `"AAuth-Mission"`), and the + `AAuthInteraction*Exception` family. +- **Namespace consistency.** All five `Server/` sub-folders declare their matching + `AAuth.Server.{Verification,Challenge,Authorization,Metadata,CallChaining}` + namespace; all 5 `Access/` files declare `AAuth.Access`; `Server/` root files + declare only `AAuth.Server`. No half-split. +- **Spec terminology alignment.** New names track `aauth-spec/` vocabulary — + "mission" (250 occurrences), "interaction" (160), "agent/server identifier" + (26/5), "access server" (17), "person server" (23). +- **Suites.** `dotnet build AAuth.slnx` clean (0/0); unit 371 passed; conformance + 346 passed; `make demo-federated` + federated `tests/e2e` Playwright green. + +**Verdict: PASS** — refactor is names/namespaces only, fully and consistently +applied, and spec-aligned. + +--- + +## Out of Scope + +| Item | Reason | +|------|--------| +| Renaming `IAAuthKey`/`AAuthKey` | Brand identity + BCL `Key` collision risk; intentionally kept | +| Assembly/package split (multiple NuGet packages) | Larger architectural decision; not a namespace concern | +| Renaming the root `AAuth` assembly/namespace | Out of question; it is the product brand | diff --git a/.agent/plans/2026-06-03-sdk-namespace-hygiene/research.md b/.agent/plans/2026-06-03-sdk-namespace-hygiene/research.md new file mode 100644 index 0000000..62a21a6 --- /dev/null +++ b/.agent/plans/2026-06-03-sdk-namespace-hygiene/research.md @@ -0,0 +1,202 @@ +--- +title: "SDK Namespace Hygiene — Research" +description: Research document for improving the AAuth SDK namespace layout and public type naming +ms.date: 2026-06-03 +--- + +## Problem Statement + +The AAuth SDK (`src/AAuth/`) organizes ~106 types across 11 namespaces. The +layout is conventional and structurally sound — feature/layer folders map 1:1 +to namespaces — but four recurring friction points hurt discoverability and +ergonomics for consumers: + +1. **DI extension methods are not in the conventional namespace.** Registration + helpers live in `AAuth.DependencyInjection` rather than the .NET-idiomatic + `Microsoft.Extensions.DependencyInjection` / `Microsoft.AspNetCore.Builder`, + so consumers must add an extra `using` they would not expect. +2. **The `AAuth` type-name prefix stutters** against the `AAuth.*` namespaces + (`AAuth.Crypto.AAuthKey`, `AAuth.Agent.AAuthMission`, etc.). +3. **The four-party Access-Server feature is split** across `AAuth.Tokens` and + `AAuth.Server`, so a single feature spans two namespaces. +4. **The primary client builders hide in `AAuth.HttpSig`**, which reads like an + internal transport concern rather than the headline entry point. + +This document captures the current state and the trade-offs. It contains no +task lists — see [implementation-plan.md](implementation-plan.md). + +## Current Namespace Inventory + +| Namespace | Folder | Representative types | +|---|---|---| +| `AAuth` | `src/AAuth/` | `AAuthUrl`, `AAuthConstants`, `AAuthDiagnostics`, `AAuthTokenType` | +| `AAuth.Crypto` | `Crypto/` | `IAAuthKey`, `AAuthKey`, `EcdsaAAuthKey`, `IKeyStore`, `FileKeyStore`, `InMemoryKeyStore`, `KeyFactory` | +| `AAuth.HttpSig` | `HttpSig/` | `AAuthClientBuilder`, `SelfIssuingBuilder`, `EnrolledBuilder`, `BootstrapBuilder`, `AAuthVerifier`, `AAuthSigningHandler`, `ISignatureKeyProvider`/`Resolver` family | +| `AAuth.Tokens` | `Tokens/` | `AuthTokenBuilder`, `AgentTokenBuilder`, `ResourceTokenBuilder`, `TokenVerifier`, `AccessServerClient`, `AccessServerRequest`, `ActChainBuilder`/`Reader` | +| `AAuth.Server` | `Server/` | `AAuthVerificationMiddleware`, `AAuthChallengeMiddleware`, `IAccessPolicy`, `IAccessPendingStore`, `AAuthAccessServerEndpoints`, `IJtiStore`, `WellKnownEndpoints` | +| `AAuth.Agent` | `Agent/` | `AgentProviderClient`, `TokenExchangeClient`, `ChallengeHandler`, `ITokenRefresher` family, `AAuthMission`, `IInteractionPresenter` | +| `AAuth.Discovery` | `Discovery/` | `MetadataClient`, `JwksClient`, `ServerMetadata` | +| `AAuth.Headers` | `Headers/` | `AAuthInteraction`, `AAuthRequirementHeader`, `AAuthClaimsRequirement`, `AAuthClaimsResponse` | +| `AAuth.Errors` | `Errors/` | `AAuthTokenExchangeException`, `AAuthPaymentRequiredException`, `SignatureError`, `TokenError`, `PollingError` | +| `AAuth.Identifiers` | `Identifiers/` | `AAuthAgentId`, `AAuthServerId` | +| `AAuth.DependencyInjection` | `DependencyInjection/` | `AAuthResourceServiceCollectionExtensions`, `AAuthAgentServiceCollectionExtensions`, `AAuthDiscoveryServiceCollectionExtensions`, `AAuthApplicationBuilderExtensions`, plus options records | + +## Issue 1 — DI Extensions in a Non-Conventional Namespace + +### Current state + +```csharp +namespace AAuth.DependencyInjection; + +public static class AAuthResourceServiceCollectionExtensions +{ + public static IServiceCollection AddAAuthResource(this IServiceCollection services, ...) { ... } +} +``` + +Extension targets and their idiomatic homes: + +| Extension class | Extends | Idiomatic namespace | +|---|---|---| +| `AAuthResourceServiceCollectionExtensions` | `IServiceCollection` | `Microsoft.Extensions.DependencyInjection` | +| `AAuthAgentServiceCollectionExtensions` | `IServiceCollection` | `Microsoft.Extensions.DependencyInjection` | +| `AAuthDiscoveryServiceCollectionExtensions` | `IServiceCollection` | `Microsoft.Extensions.DependencyInjection` | +| `AAuthApplicationBuilderExtensions` | `IApplicationBuilder` | `Microsoft.AspNetCore.Builder` | + +### Why it matters + +`Program.cs` in an ASP.NET Core app already has `Microsoft.Extensions.DependencyInjection` +and `Microsoft.AspNetCore.Builder` in scope via implicit usings. Placing the +extension methods there makes `services.AddAAuthResource(...)` and +`app.UseAAuth...()` appear in IntelliSense with **no extra `using`**. This is +the established convention used by EF Core, MediatR, Polly, Serilog, etc. + +### Consumer impact (in-repo) + +`using AAuth.DependencyInjection;` appears in test/sample files including +[ActivityDiagnosticsTests.cs](../../tests/AAuth.Conformance/Observability/ActivityDiagnosticsTests.cs), +[AAuthResourceDITests.cs](../../tests/AAuth.Tests/DependencyInjection/AAuthResourceDITests.cs), +[AAuthAgentDITests.cs](../../tests/AAuth.Tests/DependencyInjection/AAuthAgentDITests.cs), +[NamingJwtValidationTests.cs](../../tests/AAuth.Conformance/HttpSignatures/NamingJwtValidationTests.cs), +[ChallengeMiddlewareTests.cs](../../tests/AAuth.Conformance/HttpSignatures/ChallengeMiddlewareTests.cs), +[AAuthDiscoveryDITests.cs](../../tests/AAuth.Tests/DependencyInjection/AAuthDiscoveryDITests.cs). +The option records currently in `AAuth.DependencyInjection` +(`AAuthResourceOptions`, `AAuthAgentOptions`, `AAuthDiscoveryOptions`, +`AAuthResourcePipelineOptions`) are referenced by the `configure` lambdas, so +they need a stable home that does not force the same extra `using`. + +### Design note + +The extension *methods* should move to the Microsoft namespaces; the *options* +records they configure can either move with them or relocate to a feature +namespace (e.g. `AAuth` root or `AAuth.Server`/`AAuth.Agent`). The safest split +is: methods → Microsoft namespaces, options → `AAuth` root (single `using AAuth;`). + +## Issue 2 — `AAuth` Type-Name Prefix Stutter + +`AAuth`-prefixed public types include (non-exhaustive): `AAuthKey`, +`AAuthScopeRequirement`, `AAuthInteraction`, `AAuthAccessMode`, +`AAuthAgentMetadataOptions`, `AAuthAuthenticationHandler`, +`AAuthVerificationOptions`, `AAuthChallengeMiddleware`, `AAuthClaimsResponse`, +`AAuthHttpContextExtensions`, `AAuthLevel`, `AAuthAccessServerOptions`, +`AAuthResourceMetadataOptions`, `AAuthVerificationResult`, `AAuthConstants`, +`AAuthDiagnostics`, `AAuthClaimsRequirement`, `AAuthMission`, `AAuthAgentId`, +`AAuthServerId`, `AAuthVerifier`, `AAuthClientBuilder`. + +### Guidance + +.NET naming guidelines recommend prefixing types in the **root** namespace and +for **disambiguation** against BCL/common names, but not redundantly inside a +descriptive sub-namespace. Two buckets: + +| Keep the prefix (collision/identity risk) | Candidate to drop the prefix | +|---|---| +| `AAuthKey` (vs `System.Security...Key`) | `AAuthMission` → `Mission` | +| `AAuthVerifier` | `AAuthAgentId` → `AgentId` | +| `AAuthClientBuilder` (headline brand) | `AAuthServerId` → `ServerId` | +| `AAuthConstants`, `AAuthDiagnostics` (root) | `AAuthInteraction` → `Interaction` (in `Headers`) | +| `AAuthLevel`, `AAuthAccessMode` (terse enums) | `AAuthVerificationResult` → `VerificationResult` | + +This is a **breaking** change (public renames). It is best done as one +deliberate sweep, not piecemeal, and gated behind a major version bump. + +## Issue 3 — Access-Server Feature Split Across Namespaces + +The four-party federated feature spans: + +| Type | Current namespace | Role | +|---|---|---| +| `AccessServerClient` | `AAuth.Tokens` | Client the PS uses to federate | +| `AccessServerRequest` | `AAuth.Tokens` | Request DTO | +| `IAccessPolicy`, `AccessDecision` | `AAuth.Server` | AS-side policy contract | +| `IAccessPendingStore`, `AccessPendingEntry` | `AAuth.Server` | AS-side deferred-decision store | +| `AAuthAccessServerEndpoints`, `AAuthAccessServerOptions` | `AAuth.Server` | AS host helper | + +A consumer building federated access must import both `AAuth.Tokens` and +`AAuth.Server`. Options: introduce a cohesive `AAuth.Access` namespace, or at +minimum co-locate the client-side pair with the policy/store contracts. Note +the directional asymmetry: `AccessServerClient` is used by the *Person Server* +(client role), while the policy/store/endpoints are used by the *Access Server* +(host role) — a single namespace is still reasonable since both are "access" +domain concepts. + +## Issue 4 — Client Builders Under `AAuth.HttpSig` + +The headline consumer entry points — `AAuthClientBuilder`, `SelfIssuingBuilder`, +`EnrolledBuilder`, `BootstrapBuilder` — live in `AAuth.HttpSig`, alongside +internal signing machinery (`AAuthSigningHandler`, `ISignatureKeyProvider`, +`SignatureKeyParser`). A newcomer typing `new AAuthClientBuilder()` will not +guess "HttpSig". `using AAuth.HttpSig;` appears in many test files, confirming +it is currently load-bearing for consumers. + +### Options + +- **A:** Move the four builders to root `AAuth` (joins `AAuthUrl`, + `AAuthConstants`). Surfaces the headline API with a single `using AAuth;`. +- **B:** Introduce `AAuth.Client` for the builder family, leaving signing + internals in `HttpSig`. + +Option A maximizes "one using" ergonomics; Option B keeps a clean client/ +transport separation. Either is breaking for the `using AAuth.HttpSig;` sites. + +## Risk & Compatibility Summary + +> **Policy (2026-06-03):** SDK is in **alpha** — clean breaks are acceptable. +> No `[Obsolete]` shims or type-forwarders; renames/moves are applied directly +> and all in-repo callers are updated in the same change. + +| Issue | Breaking? | Notes | +|---|---|---| +| 1 — DI namespace | Non-breaking for callers using implicit usings; explicit `using AAuth.DependencyInjection;` sites (all in-repo) updated | Largest ergonomic win; ship first | +| 2 — Type renames | Breaking (public API) | Clean break; update all in-repo references via language-server rename | +| 3 — Access namespace | Breaking (`AccessServerClient` namespace move) | Clean break; update sample/test usings | +| 4 — Builder namespace | Breaking (`AAuth.HttpSig` move) | Clean break; update sample/test usings | + +## Open Questions + +1. **Compat shims:** Do we ship `[Obsolete]` forwarding aliases for the + breaking renames (Issues 2–4), or is a clean break acceptable given the SDK + is pre-1.0 / draft-stage? + > **Resolved (2026-06-03):** Clean break — **no** `[Obsolete]` shims or + > type-forwarders. The SDK is in **alpha**; breaking changes are acceptable. + > Old names are simply renamed/removed and all in-repo callers updated. +2. **Options placement (Issue 1):** Move option records to `AAuth` root, or + leave them in a dedicated `AAuth.Configuration` namespace? **Pending.** +3. **Issue 3 namespace name:** `AAuth.Access` vs `AAuth.Federation`? The spec + uses "federated access"; the host is the "Access Server". **Pending — lean + `AAuth.Access` to match the type-name stems (`Access*`).** +4. **Sequencing:** Should Issue 1 ship independently (non-breaking, immediate) + ahead of the breaking batch (2–4)? + > **Resolved (2026-06-03):** Phase ordering kept for review clarity, but no + > version gate is required — alpha permits landing the breaking phases as + > soon as each is ready. + +## References + +- .NET namespace/type naming guidelines (Framework Design Guidelines): + prefix types in the root namespace; avoid redundant prefixes in descriptive + sub-namespaces. +- ASP.NET Core convention: DI/builder extension methods live in + `Microsoft.Extensions.DependencyInjection` and `Microsoft.AspNetCore.Builder`. +- In-repo prior art: [2026-05-27-token-refresher-concrete-types/research.md](../2026-05-27-token-refresher-concrete-types/research.md) + discusses namespace placement for `AAuth.Agent` additions. diff --git a/README.md b/README.md index cac2211..771a9cb 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ The simplest mode is **pseudonymous (HWK)** — the agent signs every request wi ```csharp using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var key = AAuthKey.Generate(); // Ed25519 keypair @@ -127,7 +127,7 @@ On the agent side, building the client with `WithChallengeHandling` makes the en ```csharp using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var key = AAuthKey.Generate(); @@ -170,8 +170,10 @@ The resource verifies signatures, publishes metadata, and issues resource token ```csharp using AAuth.Crypto; -using AAuth.DependencyInjection; -using AAuth.Server; +using AAuth; +using AAuth.Server.Challenge; +using AAuth.Server.Verification; +using AAuth.Server.Metadata; var builder = WebApplication.CreateBuilder(args); var resourceKey = AAuthKey.Generate(); @@ -214,8 +216,8 @@ Hosted services act as their own Agent Provider — generate a key, publish meta ```csharp using AAuth.Crypto; -using AAuth.HttpSig; -using AAuth.Server; +using AAuth; +using AAuth.Server.Metadata; var builder = WebApplication.CreateBuilder(args); var key = AAuthKey.Generate(); diff --git a/docs/README.md b/docs/README.md index b7c7aff..ca3364b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -101,7 +101,7 @@ This is the documentation for the AAuth .NET SDK (`AAuth` NuGet package). It cov | `TokenExchangeClient` | Sends signed `POST /token` to the Person Server | | `DeferredPoller` | Polls the pending URL until auth_token or timeout | | `AgentProviderClient` | Enrols with an Agent Provider (CLI/desktop agents; hosted services self-issue) | -| `AAuthMission` / `AAuthMissionHeader` | Mission state + the `AAuth-Mission` header helpers | +| `Mission` / `AAuthMissionHeader` | Mission state + the `AAuth-Mission` header helpers | | `MissionForwardingHandler` | `DelegatingHandler` that forwards mission context downstream | | `AAuthCapabilitiesHeader` | Helpers for the `AAuth-Capabilities` request header | | `IInteractionPresenter` | Surface interaction URLs to the user | @@ -132,23 +132,48 @@ This is the documentation for the AAuth .NET SDK (`AAuth` NuGet package). It cov | Type | Purpose | |------|---------| | `AAuthRequirementHeader` | Format/parse the `AAuth-Requirement` challenge header | -| `AAuthInteraction` | Interaction URL + code from 202 responses | +| `Interaction` | Interaction URL + code from 202 responses | -> The `AAuthCapabilitiesHeader` and `AAuthMissionHeader` types live in the `AAuth.Agent` namespace (alongside `AAuthMission` and `MissionForwardingHandler`), not in `AAuth.Headers`. +> The `AAuthCapabilitiesHeader` and `AAuthMissionHeader` types live in the `AAuth.Agent` namespace (alongside `Mission` and `MissionForwardingHandler`), not in `AAuth.Headers`. -### `AAuth.Server` — Resource server utilities +### `AAuth.Server.Verification` — Verification middleware | Type | Purpose | |------|---------| | `AAuthVerificationMiddleware` | HTTP sig PoP + JWT issuer verification middleware | -| `AAuthChallengeMiddleware` | Auto-challenge: issues 401 with resource token | | `AAuthAuthenticationHandler` | Maps `AAuthVerificationResult` to `ClaimsPrincipal` | | `AAuthVerificationResult` | Typed verification result in `HttpContext.Features` | | `AAuthLevel` | Pseudonymous / Identified / Authorized | + +### `AAuth.Server.Challenge` — Auto-challenge middleware + +| Type | Purpose | +|------|---------| +| `AAuthChallengeMiddleware` | Auto-challenge: issues 401 with resource token | + +### `AAuth.Server.Authorization` — Scope authorization + +| Type | Purpose | +|------|---------| | `AAuthScopeRequirement` | ASP.NET Core authorization requirement for scopes | | `AAuthScopeHandler` | Evaluates scope requirements against verified scopes | -| `CallChainingHandler` | Multi-hop delegation routing for resource-as-agent | + +### `AAuth.Server.Metadata` — Well-known endpoints + +| Type | Purpose | +|------|---------| | `WellKnownEndpoints` | `MapAAuthResourceWellKnown()` for ASP.NET minimal APIs | + +### `AAuth.Server.CallChaining` — Delegation routing + +| Type | Purpose | +|------|---------| +| `CallChainingHandler` | Multi-hop delegation routing for resource-as-agent | + +### `AAuth.Server` — Resource server utilities + +| Type | Purpose | +|------|---------| | `RevocationEndpoint` | Token revocation endpoint | | `IJtiStore` / `InMemoryJtiStore` | Replay detection (JTI tracking) | | `IOpaqueTokenStore` | Opaque token storage abstraction | @@ -159,7 +184,7 @@ This is the documentation for the AAuth .NET SDK (`AAuth` NuGet package). It cov |------|---------| | `AAuthDiagnostics` | Shared `ActivitySource` + tag key constants for OTel tracing | -### `AAuth.DependencyInjection` — ASP.NET Core integration +### `Microsoft.Extensions.DependencyInjection` / `Microsoft.AspNetCore.Builder` — ASP.NET Core integration | Type | Purpose | |------|---------| @@ -168,6 +193,8 @@ This is the documentation for the AAuth .NET SDK (`AAuth` NuGet package). It cov | `AAuthDiscoveryServiceCollectionExtensions` | `services.AddAAuthDiscovery(...)` | | `AAuthApplicationBuilderExtensions` | `app.UseAAuthVerification()` | +> These extension methods live in the conventional `Microsoft.Extensions.DependencyInjection` and `Microsoft.AspNetCore.Builder` namespaces so they surface automatically in ASP.NET Core projects. The associated options records (`AAuthAgentOptions`, `AAuthResourceOptions`, `AAuthDiscoveryOptions`, etc.) live in the root `AAuth` namespace. + ### `AAuth.Errors` — Error types | Type | Purpose | @@ -181,8 +208,8 @@ This is the documentation for the AAuth .NET SDK (`AAuth` NuGet package). It cov | Type | Purpose | |------|---------| -| `AAuthAgentId` | Parse/validate `aauth:` agent identifiers | -| `AAuthServerId` | Parse/validate server identifiers | +| `AgentId` | Parse/validate `aauth:` agent identifiers | +| `ServerId` | Parse/validate server identifiers | ## Samples diff --git a/docs/advanced/interaction-chaining.md b/docs/advanced/interaction-chaining.md index fc264e5..c4126c7 100644 --- a/docs/advanced/interaction-chaining.md +++ b/docs/advanced/interaction-chaining.md @@ -88,7 +88,7 @@ IResult ReEmitChainedInteraction(HttpContext ctx, PendingStore.Entry entry) ctx.Response.Headers["Retry-After"] = "1"; ctx.Response.Headers.CacheControl = "no-store"; ctx.Response.Headers[AAuthRequirementHeader.Name] = - AAuthInteraction.Format(entry.InteractionUrl, entry.InteractionCode); + Interaction.Format(entry.InteractionUrl, entry.InteractionCode); return Results.Json(new { status = "interaction_required" }, statusCode: 202); } ``` diff --git a/docs/advanced/missions.md b/docs/advanced/missions.md index 1b89518..396b4e4 100644 --- a/docs/advanced/missions.md +++ b/docs/advanced/missions.md @@ -6,12 +6,12 @@ A mission is a structured, multi-step authorization negotiation between agent and resource. Unlike a single challenge/response, missions allow the resource to declare requirements progressively and the agent to fulfill them over multiple round-trips. -## AAuthMission +## Mission ```csharp namespace AAuth.Agent; -public sealed class AAuthMission +public sealed class Mission { public required string Id { get; init; } public required string Status { get; init; } // "pending", "approved", "denied", "completed" @@ -20,7 +20,7 @@ public sealed class AAuthMission public string? StatusUrl { get; init; } // poll for status changes public string? InteractionUrl { get; init; } // user-facing approval page - public static AAuthMission FromJson(JsonObject json); + public static Mission FromJson(JsonObject json); } ``` @@ -60,7 +60,7 @@ if (response.StatusCode == HttpStatusCode.Unauthorized) if (missionHeader is not null) { var body = await response.Content.ReadFromJsonAsync(); - var mission = AAuthMission.FromJson(body!); + var mission = Mission.FromJson(body!); Console.WriteLine($"Mission: {mission.Id}"); Console.WriteLine($"Status: {mission.Status}"); diff --git a/docs/concepts.md b/docs/concepts.md index 83a27c8..c0a65f6 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -42,7 +42,7 @@ Four modes: ### 3. Governance (Missions) Optional layer. Agent proposes missions; PS approves and scopes permissions. -SDK: `AAuthMission`, `AAuthMissionHeader` +SDK: `Mission`, `AAuthMissionHeader` See [Missions](https://explorer.aauth.dev/missions/compare). diff --git a/docs/getting-started.md b/docs/getting-started.md index 3a1845b..ceac36e 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -47,7 +47,7 @@ The simplest mode is pseudonymous (HWK) — no Agent Provider needed: ```csharp using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var key = AAuthKey.Generate(); @@ -255,8 +255,8 @@ A hosted service (web app, API, orchestrator) acts as its own Agent Provider: ```csharp using AAuth.Crypto; -using AAuth.HttpSig; -using AAuth.Server; +using AAuth; +using AAuth.Server.Metadata; var builder = WebApplication.CreateBuilder(args); var key = AAuthKey.Generate(); @@ -290,8 +290,10 @@ A resource that verifies signatures and issues resource token challenges: ```csharp using AAuth.Crypto; -using AAuth.DependencyInjection; -using AAuth.Server; +using AAuth; +using AAuth.Server.Verification; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; var builder = WebApplication.CreateBuilder(args); var resourceKey = AAuthKey.Generate(); @@ -383,7 +385,7 @@ For agents that do NOT have a stable URL (CLI tools, desktop apps, mobile apps), ```csharp using AAuth.Agent; using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; // Key is generated INSIDE the store — private material never leaves var keyStore = FileKeyStore.Default(); // ~/.aauth/keys/ (or plug in HSM/Key Vault) @@ -408,7 +410,7 @@ Load the key by handle from the store and let the SDK manage agent tokens: ```csharp using AAuth.Agent; using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var keyStore = FileKeyStore.Default(); var localKeyHandle = configuration["AAuth:LocalKeyHandle"]!; @@ -452,7 +454,6 @@ Console.WriteLine(await response.Content.ReadAsStringAsync()); using AAuth.Agent; using AAuth.Crypto; using AAuth.Discovery; -using AAuth.HttpSig; var apClient = new AgentProviderClient(new HttpClient(), new InMemoryKeyStore()); var enrol = await apClient.EnrolAsync( diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index cc37af1..f3d573d 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -92,7 +92,7 @@ Server `Retry-After` headers override `DefaultPollInterval` (clamped to `MinPoll | Property | Type | Default | Description | |----------|------|---------|-------------| -| `OnInteractionRequired` | `Func?` | `null` | Callback for 202+interaction | +| `OnInteractionRequired` | `Func?` | `null` | Callback for 202+interaction | | `PollingTimeout` | `TimeSpan` | 5 minutes | Maximum polling time | | `DefaultPollInterval` | `TimeSpan` | 5 seconds | Interval between polls | | `PreferWaitSeconds` | `int?` | `null` | `Prefer: wait=N` header value | @@ -216,7 +216,7 @@ Standard `DelegatingHandler` — no configurable options. Requires an `ISignatur | Property | Type | Default | Description | |----------|------|---------|-------------| -| `OnInteractionRequired` | `Func?` | null | Deferred consent callback | +| `OnInteractionRequired` | `Func?` | null | Deferred consent callback | | `PollingTimeout` | `TimeSpan` | 5 minutes | Max deferred polling time | | `DefaultPollInterval` | `TimeSpan` | 5 seconds | Poll interval (overridden by Retry-After) | | `PreferWaitSeconds` | `int?` | null | Sends `Prefer: wait=N` to long-poll | diff --git a/docs/server/authorization-policies.md b/docs/server/authorization-policies.md index 80b0dad..e6c8b9b 100644 --- a/docs/server/authorization-policies.md +++ b/docs/server/authorization-policies.md @@ -9,7 +9,7 @@ The AAuth SDK integrates with ASP.NET Core's authorization system via `AAuthScop ## Registration ```csharp -using AAuth.DependencyInjection; +using AAuth; builder.Services.AddAAuthAuthentication(); builder.Services.AddAAuthAuthorization(); diff --git a/docs/server/challenge-middleware.md b/docs/server/challenge-middleware.md index fd422e7..bed7d34 100644 --- a/docs/server/challenge-middleware.md +++ b/docs/server/challenge-middleware.md @@ -5,8 +5,9 @@ ## Registration ```csharp -using AAuth.DependencyInjection; -using AAuth.Server; +using AAuth; +using AAuth.Server.Challenge; +using AAuth.Server.Verification; // Must be registered AFTER UseAAuthVerification app.UseAAuthChallenge(new ChallengeOptions diff --git a/docs/server/multi-scheme-verification.md b/docs/server/multi-scheme-verification.md index 700d083..87cc0cc 100644 --- a/docs/server/multi-scheme-verification.md +++ b/docs/server/multi-scheme-verification.md @@ -30,7 +30,7 @@ public sealed class SignatureKeyResolution Handles all four schemes out of the box: ```csharp -using AAuth.DependencyInjection; +using AAuth; // DI extension (recommended) — registers resolver automatically builder.Services.AddAAuthResource(options => @@ -47,6 +47,7 @@ app.UseAAuthVerification(); ```csharp using AAuth.HttpSig; using AAuth.Discovery; +using AAuth.Server.Verification; // Register required services builder.Services.AddSingleton(new AAuthVerifier()); diff --git a/docs/server/replay-detection.md b/docs/server/replay-detection.md index 8d6ac49..c50523e 100644 --- a/docs/server/replay-detection.md +++ b/docs/server/replay-detection.md @@ -29,7 +29,7 @@ public interface IJtiStore Thread-safe, in-process implementation. Suitable for single-instance deployments and testing. ```csharp -using AAuth.DependencyInjection; +using AAuth; // DI extension (recommended) — registers IJtiStore automatically builder.Services.AddAAuthResource(options => @@ -44,7 +44,8 @@ builder.Services.AddAAuthResource(options => ```csharp using AAuth.Server; -using AAuth.DependencyInjection; +using AAuth; +using AAuth.Server.Verification; builder.Services.AddSingleton(new AAuthVerifier()); builder.Services.AddSingleton(new InMemoryJtiStore()); diff --git a/docs/server/resource-metadata.md b/docs/server/resource-metadata.md index cd930b7..11fa45c 100644 --- a/docs/server/resource-metadata.md +++ b/docs/server/resource-metadata.md @@ -9,7 +9,7 @@ Resources publish a `.well-known/aauth-resource.json` document so agents can dis ## Setup ```csharp -using AAuth.DependencyInjection; +using AAuth; builder.Services.AddAAuthResource(options => { @@ -31,7 +31,7 @@ app.MapAAuthWellKnown(); // serves /.well-known/aauth-resource.json Manual Setup ```csharp -using AAuth.Server; +using AAuth.Server.Metadata; using AAuth.Crypto; var signingKey = AAuthKey.Generate(); diff --git a/docs/server/verification-middleware.md b/docs/server/verification-middleware.md index 36b411d..cb9fa5d 100644 --- a/docs/server/verification-middleware.md +++ b/docs/server/verification-middleware.md @@ -9,10 +9,10 @@ ## Registration ```csharp -using AAuth.DependencyInjection; +using AAuth; using AAuth.Discovery; using AAuth.HttpSig; -using AAuth.Server; +using AAuth.Server.Verification; // Required services builder.Services.AddSingleton(new AAuthVerifier()); diff --git a/docs/signing-modes/agent-identity-jwks-uri.md b/docs/signing-modes/agent-identity-jwks-uri.md index 0bd7a91..43296b8 100644 --- a/docs/signing-modes/agent-identity-jwks-uri.md +++ b/docs/signing-modes/agent-identity-jwks-uri.md @@ -16,7 +16,7 @@ The agent references its JWKS endpoint and key ID. The resource fetches the publ ```csharp using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var key = AAuthKey.Generate(); @@ -34,7 +34,7 @@ var response = await client.GetAsync("https://resource.example/data"); ```csharp using AAuth.Agent; using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var key = AAuthKey.Generate(); diff --git a/docs/signing-modes/agent-token-jwt.md b/docs/signing-modes/agent-token-jwt.md index 79bf27a..f35cb36 100644 --- a/docs/signing-modes/agent-token-jwt.md +++ b/docs/signing-modes/agent-token-jwt.md @@ -18,7 +18,7 @@ The agent presents its full agent token inline. The resource (or Person Server) ```csharp using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var key = AAuthKey.Generate(); @@ -63,7 +63,7 @@ using var client = new AAuthClientBuilder(key) ```csharp using AAuth.Agent; using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var keyStore = FileKeyStore.Default(); var key = await keyStore.LoadAsync(configuration["AAuth:LocalKeyHandle"]!); diff --git a/docs/signing-modes/key-rotation-jkt-jwt.md b/docs/signing-modes/key-rotation-jkt-jwt.md index 1cf757c..d8cec20 100644 --- a/docs/signing-modes/key-rotation-jkt-jwt.md +++ b/docs/signing-modes/key-rotation-jkt-jwt.md @@ -14,7 +14,7 @@ Two-key delegation where a durable (hardware-backed) key issues a naming JWT tha ```csharp using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var durableKey = AAuthKey.Generate(); // long-lived, possibly hardware-backed var ephemeralKey = AAuthKey.Generate(); // short-lived signing key diff --git a/docs/signing-modes/overview.md b/docs/signing-modes/overview.md index d6e06fd..24d4cbd 100644 --- a/docs/signing-modes/overview.md +++ b/docs/signing-modes/overview.md @@ -24,7 +24,7 @@ All AAuth signing modes use HTTP Message Signatures (RFC 9421). The difference i ```csharp using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var key = AAuthKey.Generate(); diff --git a/docs/signing-modes/pseudonymous-hwk.md b/docs/signing-modes/pseudonymous-hwk.md index 32d6049..4d54002 100644 --- a/docs/signing-modes/pseudonymous-hwk.md +++ b/docs/signing-modes/pseudonymous-hwk.md @@ -14,7 +14,7 @@ The agent proves it holds a specific key without disclosing its identity. The fu ```csharp using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var key = AAuthKey.Generate(); diff --git a/docs/workflows/bootstrap-enrollment.md b/docs/workflows/bootstrap-enrollment.md index 8299701..fb650c5 100644 --- a/docs/workflows/bootstrap-enrollment.md +++ b/docs/workflows/bootstrap-enrollment.md @@ -56,7 +56,7 @@ Run this in a separate tool, CLI, or setup script — not in your application: ```csharp using AAuth.Agent; using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; // Key is generated INSIDE the store — private material never leaves. // FileKeyStore.Default() returns the in-process IKeyStore shipped with the SDK @@ -84,7 +84,7 @@ Console.WriteLine($"Add to appsettings: AAuth:LocalKeyHandle = {enrol.LocalKeyHa ```csharp using AAuth.Agent; using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; // Key stays in the store — loaded by reference, never extracted var keyStore = FileKeyStore.Default(); diff --git a/docs/workflows/call-chaining.md b/docs/workflows/call-chaining.md index de40a06..9bce880 100644 --- a/docs/workflows/call-chaining.md +++ b/docs/workflows/call-chaining.md @@ -34,7 +34,7 @@ The calling agent uses standard challenge handling. The SDK automatically handle ```csharp using AAuth.Agent; using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var keyStore = FileKeyStore.Default(); var localKeyHandle = configuration["AAuth:LocalKeyHandle"]!; diff --git a/docs/workflows/deferred-consent.md b/docs/workflows/deferred-consent.md index 5e1a46a..ab24485 100644 --- a/docs/workflows/deferred-consent.md +++ b/docs/workflows/deferred-consent.md @@ -73,7 +73,7 @@ catch (AAuthInteractionTimeoutException) ```csharp using AAuth.Agent; using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var keyStore = FileKeyStore.Default(); var key = await keyStore.LoadAsync(configuration["AAuth:LocalKeyHandle"]!) @@ -148,7 +148,7 @@ var presenter = new ConsoleInteractionPresenter(); // Custom: open browser, send push notification, etc. class BrowserPresenter : IInteractionPresenter { - public Task PresentAsync(AAuthInteraction interaction, CancellationToken ct) + public Task PresentAsync(Interaction interaction, CancellationToken ct) { Process.Start(new ProcessStartInfo(interaction.Url) { UseShellExecute = true }); return Task.CompletedTask; diff --git a/docs/workflows/federated-access.md b/docs/workflows/federated-access.md index a6490f4..a67d24a 100644 --- a/docs/workflows/federated-access.md +++ b/docs/workflows/federated-access.md @@ -27,7 +27,7 @@ Identical to PS-asserted — `WithChallengeHandling()` handles it transparently. ```csharp using AAuth.Agent; using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var keyStore = FileKeyStore.Default(); var key = await keyStore.LoadAsync(configuration["AAuth:LocalKeyHandle"]!) @@ -121,6 +121,8 @@ verifies the agent and resource tokens, evaluates policy through a pluggable `IAccessPolicy`, and mints the auth token. ```csharp +using AAuth.Access; + // Register the policy decision point (stub | keycloak) and the store that // parks deferred decisions (§Claims Required / interactive consent). builder.Services.AddSingleton(new StubAccessPolicy(requiredClaims)); @@ -267,7 +269,7 @@ var fedRequest = new AccessServerRequest claims[name] = value; } } - return Task.FromResult(new AAuthClaimsResponse + return Task.FromResult(new ClaimsResponse { Subject = directedSubject, Claims = claims, @@ -277,7 +279,7 @@ var fedRequest = new AccessServerRequest ``` `AccessServerClient.FederateAsync` reads `required_claims`, invokes the -callback, POSTs the returned `AAuthClaimsResponse` (signed, origin-pinned to +callback, POSTs the returned `ClaimsResponse` (signed, origin-pinned to the AS) to the `Location`, and continues the poll loop to the `200` auth token. The `Subject` (directed `sub`) is mandatory — leaving it blank surfaces an `InvalidOperationException`. The issued auth token asserts the pushed claims diff --git a/docs/workflows/identity-based-access.md b/docs/workflows/identity-based-access.md index d40893c..e75e78f 100644 --- a/docs/workflows/identity-based-access.md +++ b/docs/workflows/identity-based-access.md @@ -25,7 +25,7 @@ Valid Signing Modes: `hwk` (pseudonymous) or `jwks_uri` (agent identity). NOT `j ```csharp using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var key = AAuthKey.Generate(); diff --git a/docs/workflows/ps-asserted-access.md b/docs/workflows/ps-asserted-access.md index 11c0800..0d9dbea 100644 --- a/docs/workflows/ps-asserted-access.md +++ b/docs/workflows/ps-asserted-access.md @@ -28,7 +28,7 @@ Automatic handling with `AAuthClientBuilder`: ```csharp using AAuth.Agent; using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; // Hosted service: self-issue (no AP needed) var key = AAuthKey.Generate(); @@ -53,7 +53,7 @@ For agents without a stable URL, enrol with an Agent Provider: ```csharp using AAuth.Agent; using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var keyStore = FileKeyStore.Default(); var localKeyHandle = configuration["AAuth:LocalKeyHandle"]!; diff --git a/samples/AgentConsole/Program.cs b/samples/AgentConsole/Program.cs index 65016bb..9fac508 100644 --- a/samples/AgentConsole/Program.cs +++ b/samples/AgentConsole/Program.cs @@ -4,6 +4,7 @@ using System.Text.Json; using System.Text.Json.Nodes; using System.Threading.Tasks; +using AAuth; using AAuth.Agent; using AAuth.Crypto; using AAuth.Discovery; diff --git a/samples/GuidedTour/Program.cs b/samples/GuidedTour/Program.cs index d65fac7..b980ff2 100644 --- a/samples/GuidedTour/Program.cs +++ b/samples/GuidedTour/Program.cs @@ -1,6 +1,11 @@ using AAuth.Crypto; -using AAuth.DependencyInjection; +using AAuth; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using GuidedTour; using GuidedTour.Components; diff --git a/samples/GuidedTour/TourSession.cs b/samples/GuidedTour/TourSession.cs index e391acf..5d26592 100644 --- a/samples/GuidedTour/TourSession.cs +++ b/samples/GuidedTour/TourSession.cs @@ -3,6 +3,7 @@ using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes; +using AAuth; using AAuth.Agent; using AAuth.Crypto; using AAuth.Discovery; @@ -357,7 +358,7 @@ public IReadOnlyList Plan /// The user-facing interaction URL captured during step 7 (deferred only). public string? UserInteractionUrl => _interactionUrl is null || _interactionCode is null ? null - : new AAuth.Headers.AAuthInteraction(_interactionUrl, _interactionCode).BuildUserUrl(); + : new AAuth.Headers.Interaction(_interactionUrl, _interactionCode).BuildUserUrl(); /// Path portion of the pending URL (for compact UI display). public string? PendingUrlPath @@ -1226,7 +1227,7 @@ private async Task StepDeferredExchangeAsync(CancellationToken ct) try { var parsed = AAuthRequirementHeader.Parse(raw); - var interaction = AAuth.Headers.AAuthInteraction.FromRequirement(parsed); + var interaction = AAuth.Headers.Interaction.FromRequirement(parsed); if (interaction is not null) { _interactionUrl = interaction.Url; @@ -1738,7 +1739,7 @@ private async Task StepCallChainExchangeAsync(CancellationToken ct) try { var parsed = AAuthRequirementHeader.Parse(raw); - var interaction = AAuth.Headers.AAuthInteraction.FromRequirement(parsed); + var interaction = AAuth.Headers.Interaction.FromRequirement(parsed); if (interaction is not null) { _interactionUrl = interaction.Url; @@ -1846,7 +1847,7 @@ private async Task StepCallChainRetryHop2Async(CancellationToken ct) try { var parsed = AAuthRequirementHeader.Parse(raw); - var interaction = AAuth.Headers.AAuthInteraction.FromRequirement(parsed); + var interaction = AAuth.Headers.Interaction.FromRequirement(parsed); if (interaction is not null) { _interactionUrl = interaction.Url; @@ -2212,7 +2213,7 @@ private async Task StepFederatedExchangeAsync(CancellationToken ct) try { var parsed = AAuthRequirementHeader.Parse(raw); - var interaction = AAuth.Headers.AAuthInteraction.FromRequirement(parsed); + var interaction = AAuth.Headers.Interaction.FromRequirement(parsed); if (interaction is not null) { _interactionUrl = interaction.Url; diff --git a/samples/LiveWhoAmITest/Program.cs b/samples/LiveWhoAmITest/Program.cs index 137ecc7..04e0c93 100644 --- a/samples/LiveWhoAmITest/Program.cs +++ b/samples/LiveWhoAmITest/Program.cs @@ -21,10 +21,16 @@ using System.Text.Json; using System.Text.Json.Nodes; using System.Text.RegularExpressions; +using AAuth; using AAuth.Crypto; using AAuth.Errors; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; const string WhoAmIUrl = "https://whoami.aauth.dev/"; const string PersonServer = "https://person.hello.coop"; diff --git a/samples/MockAccessServer/Policy/KeycloakAccessPolicy.cs b/samples/MockAccessServer/Policy/KeycloakAccessPolicy.cs index bc98b09..9038229 100644 --- a/samples/MockAccessServer/Policy/KeycloakAccessPolicy.cs +++ b/samples/MockAccessServer/Policy/KeycloakAccessPolicy.cs @@ -3,7 +3,13 @@ using System.Net.Http.Headers; using System.Text; using System.Text.Json.Nodes; +using AAuth.Access; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; namespace MockAccessServer.Policy; diff --git a/samples/MockAccessServer/Policy/StubAccessPolicy.cs b/samples/MockAccessServer/Policy/StubAccessPolicy.cs index ba92f51..cd16ba1 100644 --- a/samples/MockAccessServer/Policy/StubAccessPolicy.cs +++ b/samples/MockAccessServer/Policy/StubAccessPolicy.cs @@ -1,5 +1,11 @@ using System.Text.Json.Nodes; +using AAuth.Access; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; namespace MockAccessServer.Policy; diff --git a/samples/MockAccessServer/Program.cs b/samples/MockAccessServer/Program.cs index cd19ac0..1a2e97f 100644 --- a/samples/MockAccessServer/Program.cs +++ b/samples/MockAccessServer/Program.cs @@ -1,9 +1,15 @@ using System.Text.Json.Nodes; +using AAuth; +using AAuth.Access; using AAuth.Crypto; -using AAuth.DependencyInjection; using AAuth.Discovery; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using AAuth.Tokens; using MockAccessServer.Policy; diff --git a/samples/MockPersonServer/Program.cs b/samples/MockPersonServer/Program.cs index 19bafbe..70f1e31 100644 --- a/samples/MockPersonServer/Program.cs +++ b/samples/MockPersonServer/Program.cs @@ -1,13 +1,18 @@ using System.Text.Json.Nodes; using AAuth; +using AAuth.Access; using AAuth.Agent; using AAuth.Crypto; -using AAuth.DependencyInjection; using AAuth.Discovery; using AAuth.Errors; using AAuth.Headers; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using AAuth.Tokens; using MockPersonServer; @@ -310,7 +315,7 @@ static bool IsAdminAgent(string agentId) => claims[name] = value; } } - return Task.FromResult(new AAuthClaimsResponse + return Task.FromResult(new ClaimsResponse { Subject = "pairwise-sub", Claims = claims, @@ -376,7 +381,7 @@ static bool IsAdminAgent(string agentId) => ctx.Response.Headers["Retry-After"] = "1"; ctx.Response.Headers["Cache-Control"] = "no-store"; ctx.Response.Headers[AAuthRequirementHeader.Name] = - AAuthInteraction.Format(entry.InteractionUrl, entry.InteractionCode!); + Interaction.Format(entry.InteractionUrl, entry.InteractionCode!); return Results.Json(new { status = "pending" }, statusCode: StatusCodes.Status202Accepted); } @@ -486,7 +491,7 @@ static bool IsAdminAgent(string agentId) => ctx.Response.Headers["Retry-After"] = "0"; ctx.Response.Headers["Cache-Control"] = "no-store"; ctx.Response.Headers[AAuthRequirementHeader.Name] = - AAuthInteraction.Format(interactionUrl, entry.Id); + Interaction.Format(interactionUrl, entry.Id); return Results.Json(new { status = "pending" }, statusCode: StatusCodes.Status202Accepted); } @@ -525,7 +530,7 @@ static bool IsAdminAgent(string agentId) => ctx.Response.Headers["Retry-After"] = "1"; ctx.Response.Headers["Cache-Control"] = "no-store"; ctx.Response.Headers[AAuthRequirementHeader.Name] = - AAuthInteraction.Format($"{psIssuer.TrimEnd('/')}/interaction", id); + Interaction.Format($"{psIssuer.TrimEnd('/')}/interaction", id); return Results.Json(new { status = "pending" }, statusCode: StatusCodes.Status202Accepted); } @@ -573,7 +578,7 @@ static bool IsAdminAgent(string agentId) => if (entry.InteractionUrl is not null) { ctx.Response.Headers[AAuthRequirementHeader.Name] = - AAuthInteraction.Format(entry.InteractionUrl, entry.InteractionCode!); + Interaction.Format(entry.InteractionUrl, entry.InteractionCode!); } return Results.Json(new { status = "pending" }, statusCode: StatusCodes.Status202Accepted); } diff --git a/samples/Orchestrator/Program.cs b/samples/Orchestrator/Program.cs index 7ea420c..6bed927 100644 --- a/samples/Orchestrator/Program.cs +++ b/samples/Orchestrator/Program.cs @@ -1,11 +1,16 @@ using System.Text.Json.Nodes; using AAuth.Agent; using AAuth.Crypto; -using AAuth.DependencyInjection; +using AAuth; using AAuth.Discovery; using AAuth.Headers; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using AAuth.Tokens; using Orchestrator; @@ -163,7 +168,7 @@ IResult ReEmitChainedInteraction(HttpContext ctx, PendingStore.Entry entry) ctx.Response.Headers["Retry-After"] = "1"; ctx.Response.Headers["Cache-Control"] = "no-store"; ctx.Response.Headers[AAuthRequirementHeader.Name] = - AAuthInteraction.Format(entry.InteractionUrl, entry.InteractionCode); + Interaction.Format(entry.InteractionUrl, entry.InteractionCode); return Results.Json(new { status = "interaction_required" }, statusCode: StatusCodes.Status202Accepted); } diff --git a/samples/SampleApp/Components/_Imports.razor b/samples/SampleApp/Components/_Imports.razor index d8438bd..3af21a3 100644 --- a/samples/SampleApp/Components/_Imports.razor +++ b/samples/SampleApp/Components/_Imports.razor @@ -6,6 +6,7 @@ @using static Microsoft.AspNetCore.Components.Web.RenderMode @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.JSInterop +@using AAuth @using SampleApp @using SampleApp.Components @using SampleApp.Components.Layout diff --git a/samples/SampleApp/Program.cs b/samples/SampleApp/Program.cs index 42827cf..ff44750 100644 --- a/samples/SampleApp/Program.cs +++ b/samples/SampleApp/Program.cs @@ -1,6 +1,11 @@ using AAuth.Crypto; -using AAuth.DependencyInjection; +using AAuth; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using SampleApp; using SampleApp.Components; diff --git a/samples/WhoAmI/Program.cs b/samples/WhoAmI/Program.cs index 7216fbb..6761fce 100644 --- a/samples/WhoAmI/Program.cs +++ b/samples/WhoAmI/Program.cs @@ -1,8 +1,13 @@ using AAuth.Crypto; -using AAuth.DependencyInjection; +using AAuth; using AAuth.Discovery; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; var builder = WebApplication.CreateBuilder(args); diff --git a/src/AAuth/HttpSig/AAuthClientBuilder.cs b/src/AAuth/AAuthClientBuilder.cs similarity index 99% rename from src/AAuth/HttpSig/AAuthClientBuilder.cs rename to src/AAuth/AAuthClientBuilder.cs index d0e6404..eb6d38d 100644 --- a/src/AAuth/HttpSig/AAuthClientBuilder.cs +++ b/src/AAuth/AAuthClientBuilder.cs @@ -6,10 +6,12 @@ using AAuth.Agent; using AAuth.Crypto; using AAuth.Discovery; +using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.CallChaining; using Microsoft.AspNetCore.Http; -namespace AAuth.HttpSig; +namespace AAuth; /// /// Fluent builder for creating an that signs every diff --git a/src/AAuth/Server/AAuthAccessServerEndpoints.cs b/src/AAuth/Access/AAuthAccessServerEndpoints.cs similarity index 98% rename from src/AAuth/Server/AAuthAccessServerEndpoints.cs rename to src/AAuth/Access/AAuthAccessServerEndpoints.cs index 02dc745..f21454a 100644 --- a/src/AAuth/Server/AAuthAccessServerEndpoints.cs +++ b/src/AAuth/Access/AAuthAccessServerEndpoints.cs @@ -4,16 +4,18 @@ using System.Text.Json.Nodes; using System.Threading.Tasks; using AAuth.Crypto; -using AAuth.DependencyInjection; using AAuth.Discovery; using AAuth.Headers; using AAuth.HttpSig; +using AAuth.Server; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using AAuth.Tokens; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; -namespace AAuth.Server; +namespace AAuth.Access; /// /// Configuration for . @@ -362,7 +364,7 @@ string Mint( ctx.Response.Headers["Retry-After"] = "1"; ctx.Response.Headers["Cache-Control"] = "no-store"; ctx.Response.Headers[AAuthRequirementHeader.Name] = - AAuthInteraction.Format($"{issuer}{loginPath}", entry.Id); + Interaction.Format($"{issuer}{loginPath}", entry.Id); return Results.Json(new { status = "pending" }, statusCode: StatusCodes.Status202Accepted); } case AccessDecisionKind.NeedsClaims: @@ -374,7 +376,7 @@ string Mint( ctx.Response.Headers["Retry-After"] = "0"; ctx.Response.Headers["Cache-Control"] = "no-store"; ctx.Response.Headers[AAuthRequirementHeader.Name] = - $"requirement={AAuthClaimsRequirement.RequirementType}"; + $"requirement={ClaimsRequirement.RequirementType}"; return Results.Json( new { status = "pending", required_claims = decision.RequiredClaims }, statusCode: StatusCodes.Status202Accepted); @@ -433,13 +435,13 @@ string Mint( if (entry.RequiredClaims is { Count: > 0 } && entry.SuppliedClaims is null) { ctx.Response.Headers[AAuthRequirementHeader.Name] = - $"requirement={AAuthClaimsRequirement.RequirementType}"; + $"requirement={ClaimsRequirement.RequirementType}"; return Results.Json( new { status = "pending", required_claims = entry.RequiredClaims }, statusCode: StatusCodes.Status202Accepted); } ctx.Response.Headers[AAuthRequirementHeader.Name] = - AAuthInteraction.Format($"{issuer}{loginPath}", entry.Id); + Interaction.Format($"{issuer}{loginPath}", entry.Id); return Results.Json(new { status = "pending" }, statusCode: StatusCodes.Status202Accepted); } }); @@ -531,7 +533,7 @@ string Mint( ctx.Response.Headers["Retry-After"] = "0"; ctx.Response.Headers["Cache-Control"] = "no-store"; ctx.Response.Headers[AAuthRequirementHeader.Name] = - $"requirement={AAuthClaimsRequirement.RequirementType}"; + $"requirement={ClaimsRequirement.RequirementType}"; return Results.Json( new { status = "pending", required_claims = decision.RequiredClaims ?? entry.RequiredClaims }, statusCode: StatusCodes.Status202Accepted); diff --git a/src/AAuth/Tokens/AccessServerClient.cs b/src/AAuth/Access/AccessServerClient.cs similarity index 97% rename from src/AAuth/Tokens/AccessServerClient.cs rename to src/AAuth/Access/AccessServerClient.cs index 6ce9525..afb212f 100644 --- a/src/AAuth/Tokens/AccessServerClient.cs +++ b/src/AAuth/Access/AccessServerClient.cs @@ -9,8 +9,9 @@ using AAuth.Discovery; using AAuth.Errors; using AAuth.Headers; +using AAuth.Tokens; -namespace AAuth.Tokens; +namespace AAuth.Access; /// /// Performs the signed Person Server → Access Server token request and the @@ -164,7 +165,7 @@ public async Task FederateAsync( // active identity-claims PUSH: read the requested claim names, // ask the caller to supply them (incl. a directed `sub`), POST // them signed to the Location, then resume polling the same URL. - if (string.Equals(requirement, AAuthClaimsRequirement.RequirementType, StringComparison.Ordinal)) + if (string.Equals(requirement, ClaimsRequirement.RequirementType, StringComparison.Ordinal)) { if (request.OnClaimsRequired is null) { @@ -180,7 +181,7 @@ public async Task FederateAsync( var claimsResponse = await request.OnClaimsRequired(claimsRequirement, cancellationToken).ConfigureAwait(false) ?? throw new InvalidOperationException( - "AccessServerRequest.OnClaimsRequired returned null; expected an AAuthClaimsResponse."); + "AccessServerRequest.OnClaimsRequired returned null; expected a ClaimsResponse."); if (string.IsNullOrWhiteSpace(claimsResponse.Subject)) { // §Claims Required: the recipient MUST provide a directed @@ -287,7 +288,7 @@ public async Task FederateAsync( return null; } - private static AAuthInteraction? ExtractInteraction(HttpResponseMessage response) + private static Interaction? ExtractInteraction(HttpResponseMessage response) { if (!response.Headers.TryGetValues(AAuthRequirementHeader.Name, out var values)) { @@ -299,7 +300,7 @@ public async Task FederateAsync( AAuthRequirementHeader.ParsedRequirement parsed; try { parsed = AAuthRequirementHeader.Parse(raw); } catch (FormatException) { continue; } - var interaction = AAuthInteraction.FromRequirement(parsed); + var interaction = Interaction.FromRequirement(parsed); if (interaction is not null) { return interaction; } } return null; @@ -310,7 +311,7 @@ public async Task FederateAsync( /// from the body's required_claims array (§Claims Required puts the /// claim names only in the body, never the AAuth-Requirement header). /// - private static async Task ExtractClaimsRequirementAsync( + private static async Task ExtractClaimsRequirementAsync( HttpResponseMessage response, CancellationToken cancellationToken) { JsonObject? body = null; @@ -332,8 +333,8 @@ private static async Task ExtractClaimsRequirementAsync( AAuthRequirementHeader.ParsedRequirement parsed; try { parsed = AAuthRequirementHeader.Parse(raw); } catch (FormatException) { continue; } - AAuthClaimsRequirement? requirement; - try { requirement = AAuthClaimsRequirement.FromResponse(parsed, body); } + ClaimsRequirement? requirement; + try { requirement = ClaimsRequirement.FromResponse(parsed, body); } catch (FormatException ex) { throw new HttpRequestException( @@ -392,7 +393,7 @@ private async Task PollDeferredAsync( private static bool IsClaimsRequirementResponse(HttpResponseMessage response) => string.Equals( ExtractRequirementType(response), - AAuthClaimsRequirement.RequirementType, + ClaimsRequirement.RequirementType, StringComparison.Ordinal); private static Uri ResolveLocation(HttpResponseMessage response, Uri @base) diff --git a/src/AAuth/Tokens/AccessServerRequest.cs b/src/AAuth/Access/AccessServerRequest.cs similarity index 89% rename from src/AAuth/Tokens/AccessServerRequest.cs rename to src/AAuth/Access/AccessServerRequest.cs index 1592b05..6cbe858 100644 --- a/src/AAuth/Tokens/AccessServerRequest.cs +++ b/src/AAuth/Access/AccessServerRequest.cs @@ -5,8 +5,9 @@ using AAuth.Agent; using AAuth.Crypto; using AAuth.Headers; +using AAuth.Tokens; -namespace AAuth.Tokens; +namespace AAuth.Access; /// /// Parameters for . Groups the @@ -72,20 +73,20 @@ public sealed class AccessServerRequest /// before polling begins. If and the AS returns /// 202 requirement=interaction, the call throws. /// - public Func? OnInteractionRequired { get; init; } + public Func? OnInteractionRequired { get; init; } /// /// Invoked when the AS returns 202 requirement=claims (§Claims /// Required) to request identity claims it needs for a policy decision. /// The callback receives the requested claim names and MUST return an - /// carrying a directed user identifier - /// () plus the released claims; + /// carrying a directed user identifier + /// () plus the released claims; /// POSTs them (signed) to /// the AS's pending Location URL and then resumes polling. If /// and the AS returns 202 requirement=claims, /// the call throws . /// - public Func>? OnClaimsRequired { get; init; } + public Func>? OnClaimsRequired { get; init; } /// Optional polling cadence/timeout override for the deferred path. public DeferredPollerOptions? PollerOptions { get; init; } diff --git a/src/AAuth/Server/IAccessPendingStore.cs b/src/AAuth/Access/IAccessPendingStore.cs similarity index 99% rename from src/AAuth/Server/IAccessPendingStore.cs rename to src/AAuth/Access/IAccessPendingStore.cs index e97121f..54b7bd7 100644 --- a/src/AAuth/Server/IAccessPendingStore.cs +++ b/src/AAuth/Access/IAccessPendingStore.cs @@ -4,7 +4,7 @@ using System.Text.Json.Nodes; using AAuth.Crypto; -namespace AAuth.Server; +namespace AAuth.Access; /// /// Stores in-flight federated access decisions awaiting either an interactive diff --git a/src/AAuth/Server/IAccessPolicy.cs b/src/AAuth/Access/IAccessPolicy.cs similarity index 99% rename from src/AAuth/Server/IAccessPolicy.cs rename to src/AAuth/Access/IAccessPolicy.cs index 111b967..3469e00 100644 --- a/src/AAuth/Server/IAccessPolicy.cs +++ b/src/AAuth/Access/IAccessPolicy.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; -namespace AAuth.Server; +namespace AAuth.Access; /// /// Pluggable Access Server authorization-policy seam. An implementation is the diff --git a/src/AAuth/Agent/AAuthInteractionExceptions.cs b/src/AAuth/Agent/AAuthInteractionExceptions.cs index fe93204..33b1f5d 100644 --- a/src/AAuth/Agent/AAuthInteractionExceptions.cs +++ b/src/AAuth/Agent/AAuthInteractionExceptions.cs @@ -73,16 +73,16 @@ public sealed class AAuthInteractionChainedException : Exception /// the user-facing url and single-use code the intermediary /// passes through when re-emitting its own requirement. /// - public AAuthInteraction Interaction { get; } + public Interaction Interaction { get; } - public AAuthInteractionChainedException(AAuthInteraction interaction) + public AAuthInteractionChainedException(Interaction interaction) : base("Downstream exchange requires user interaction; re-emitting as a chained interaction requirement.") { ArgumentNullException.ThrowIfNull(interaction); Interaction = interaction; } - public AAuthInteractionChainedException(AAuthInteraction interaction, string message) + public AAuthInteractionChainedException(Interaction interaction, string message) : base(message) { ArgumentNullException.ThrowIfNull(interaction); diff --git a/src/AAuth/Agent/ChallengeHandler.cs b/src/AAuth/Agent/ChallengeHandler.cs index ce2e353..67d2e44 100644 --- a/src/AAuth/Agent/ChallengeHandler.cs +++ b/src/AAuth/Agent/ChallengeHandler.cs @@ -10,6 +10,7 @@ using AAuth.Headers; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.CallChaining; namespace AAuth.Agent; @@ -35,7 +36,7 @@ public sealed class ChallengeHandler : DelegatingHandler private readonly TokenExchangeClient _exchange; private readonly AAuthTokenHolder _holder; private readonly string? _personServer; - private readonly Func? _onInteractionRequired; + private readonly Func? _onInteractionRequired; private readonly DeferredPollerOptions? _pollerOptions; private readonly Func? _upstreamTokenProvider; @@ -62,7 +63,7 @@ public ChallengeHandler( TokenExchangeClient exchange, AAuthTokenHolder holder, string personServer, - Func? onInteractionRequired = null, + Func? onInteractionRequired = null, DeferredPollerOptions? pollerOptions = null) : this(exchange, holder, personServer, onInteractionRequired, pollerOptions, upstreamTokenProvider: null) @@ -87,7 +88,7 @@ public ChallengeHandler( TokenExchangeClient exchange, AAuthTokenHolder holder, string? personServer, - Func? onInteractionRequired, + Func? onInteractionRequired, DeferredPollerOptions? pollerOptions, Func? upstreamTokenProvider) { diff --git a/src/AAuth/Agent/IInteractionPresenter.cs b/src/AAuth/Agent/IInteractionPresenter.cs index fcd213a..e32707c 100644 --- a/src/AAuth/Agent/IInteractionPresenter.cs +++ b/src/AAuth/Agent/IInteractionPresenter.cs @@ -15,7 +15,7 @@ public interface IInteractionPresenter /// Present the interaction URL + code to the user. /// Returns when the user has been notified (not when they've completed it). /// - Task PresentAsync(AAuthInteraction interaction, CancellationToken ct = default); + Task PresentAsync(Interaction interaction, CancellationToken ct = default); } /// @@ -24,7 +24,7 @@ public interface IInteractionPresenter public sealed class ConsoleInteractionPresenter : IInteractionPresenter { /// - public Task PresentAsync(AAuthInteraction interaction, CancellationToken ct = default) + public Task PresentAsync(Interaction interaction, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(interaction); Console.WriteLine($"Please visit: {interaction.Url}"); diff --git a/src/AAuth/Agent/InteractionHandler.cs b/src/AAuth/Agent/InteractionHandler.cs index 46b9226..4744288 100644 --- a/src/AAuth/Agent/InteractionHandler.cs +++ b/src/AAuth/Agent/InteractionHandler.cs @@ -64,7 +64,7 @@ protected override async Task SendAsync( return response; string? requirementType = null; - AAuthInteraction? interaction = null; + Interaction? interaction = null; foreach (var raw in values) { @@ -72,10 +72,10 @@ protected override async Task SendAsync( try { var parsed = AAuthRequirementHeader.Parse(raw); - if (parsed.Requirement == AAuthInteraction.RequirementType) + if (parsed.Requirement == Interaction.RequirementType) { - interaction = AAuthInteraction.FromRequirement(parsed); - requirementType = AAuthInteraction.RequirementType; + interaction = Interaction.FromRequirement(parsed); + requirementType = Interaction.RequirementType; break; } if (parsed.Requirement == ApprovalRequirement) @@ -102,7 +102,7 @@ protected override async Task SendAsync( locationUri = new Uri(request.RequestUri, locationUri); // Invoke the appropriate callback - if (requirementType == AAuthInteraction.RequirementType && interaction is not null) + if (requirementType == Interaction.RequirementType && interaction is not null) { if (_onInteractionRequired is not null) { diff --git a/src/AAuth/Agent/AAuthMission.cs b/src/AAuth/Agent/Mission.cs similarity index 95% rename from src/AAuth/Agent/AAuthMission.cs rename to src/AAuth/Agent/Mission.cs index 4fd51eb..6b8e49e 100644 --- a/src/AAuth/Agent/AAuthMission.cs +++ b/src/AAuth/Agent/Mission.cs @@ -8,7 +8,7 @@ namespace AAuth.Agent; /// multi-step approval, clarification, or audited access. /// Agent-side model parsed from PS responses. /// -public sealed class AAuthMission +public sealed class Mission { /// Mission identifier. public required string Id { get; init; } @@ -29,10 +29,10 @@ public sealed class AAuthMission public string? InteractionUrl { get; init; } /// Parse from a JSON response body. - public static AAuthMission FromJson(JsonObject json) + public static Mission FromJson(JsonObject json) { ArgumentNullException.ThrowIfNull(json); - return new AAuthMission + return new Mission { Id = (string?)json["mission_id"] ?? throw new InvalidOperationException("Missing 'mission_id'."), Status = (string?)json["status"] ?? "pending", diff --git a/src/AAuth/Agent/TokenExchangeClient.cs b/src/AAuth/Agent/TokenExchangeClient.cs index 6e2190d..17a8429 100644 --- a/src/AAuth/Agent/TokenExchangeClient.cs +++ b/src/AAuth/Agent/TokenExchangeClient.cs @@ -211,7 +211,7 @@ public async Task ExchangeAsync( // can handle a 202 + user-facing consent redirect. An explicit // capabilities list passed to ExchangeAsync overrides this. private static IReadOnlyList InferCapabilities( - Func? onInteractionRequired) + Func? onInteractionRequired) => onInteractionRequired is not null ? new[] { "interaction" } : Array.Empty(); @@ -253,7 +253,7 @@ private static async Task IsAccessDeniedAsync( } } - private static AAuthInteraction? ExtractInteraction(HttpResponseMessage response) + private static Interaction? ExtractInteraction(HttpResponseMessage response) { if (!response.Headers.TryGetValues(AAuthRequirementHeader.Name, out var values)) { @@ -265,7 +265,7 @@ private static async Task IsAccessDeniedAsync( AAuthRequirementHeader.ParsedRequirement parsed; try { parsed = AAuthRequirementHeader.Parse(raw); } catch (FormatException) { continue; } - var interaction = AAuthInteraction.FromRequirement(parsed); + var interaction = Interaction.FromRequirement(parsed); if (interaction is not null) { return interaction; } } return null; diff --git a/src/AAuth/Agent/TokenExchangeRequest.cs b/src/AAuth/Agent/TokenExchangeRequest.cs index 1eafadf..3f3d048 100644 --- a/src/AAuth/Agent/TokenExchangeRequest.cs +++ b/src/AAuth/Agent/TokenExchangeRequest.cs @@ -16,11 +16,11 @@ public sealed class TokenExchangeRequest /// /// Invoked when the PS returns 202 with an interaction requirement, /// before polling begins. Callers display the user-facing URL/code via - /// and then return — + /// and then return — /// polling proceeds in parallel with the user's out-of-band action. If /// and the PS returns 202, the call throws. /// - public Func? OnInteractionRequired { get; init; } + public Func? OnInteractionRequired { get; init; } /// Optional polling cadence/timeout override for the deferred path. public DeferredPollerOptions? PollerOptions { get; init; } diff --git a/src/AAuth/HttpSig/BootstrapBuilder.cs b/src/AAuth/BootstrapBuilder.cs similarity index 98% rename from src/AAuth/HttpSig/BootstrapBuilder.cs rename to src/AAuth/BootstrapBuilder.cs index a9c8cbf..76a4d04 100644 --- a/src/AAuth/HttpSig/BootstrapBuilder.cs +++ b/src/AAuth/BootstrapBuilder.cs @@ -1,8 +1,9 @@ using System; using AAuth.Agent; using AAuth.Crypto; +using AAuth.HttpSig; -namespace AAuth.HttpSig; +namespace AAuth; /// /// Fluent builder for the bootstrap enrollment flow. Created via diff --git a/src/AAuth/DependencyInjection/AAuthAgentOptions.cs b/src/AAuth/DependencyInjection/AAuthAgentOptions.cs index 291965b..feba9fa 100644 --- a/src/AAuth/DependencyInjection/AAuthAgentOptions.cs +++ b/src/AAuth/DependencyInjection/AAuthAgentOptions.cs @@ -4,8 +4,9 @@ using AAuth.Agent; using AAuth.Crypto; using AAuth.Headers; +using Microsoft.Extensions.DependencyInjection; -namespace AAuth.DependencyInjection; +namespace AAuth; /// /// Options for configuring an AAuth agent via . @@ -24,7 +25,7 @@ public sealed class AAuthAgentOptions /// /// Callback invoked when the PS requires user interaction during token exchange. /// - public Func? OnInteractionRequired { get; set; } + public Func? OnInteractionRequired { get; set; } /// /// Callback invoked when a resource returns 202 + requirement=interaction. diff --git a/src/AAuth/DependencyInjection/AAuthAgentServiceCollectionExtensions.cs b/src/AAuth/DependencyInjection/AAuthAgentServiceCollectionExtensions.cs index 8505395..4a3f4f3 100644 --- a/src/AAuth/DependencyInjection/AAuthAgentServiceCollectionExtensions.cs +++ b/src/AAuth/DependencyInjection/AAuthAgentServiceCollectionExtensions.cs @@ -1,10 +1,10 @@ using System; using System.Threading; +using AAuth; using AAuth.Agent; using AAuth.HttpSig; -using Microsoft.Extensions.DependencyInjection; -namespace AAuth.DependencyInjection; +namespace Microsoft.Extensions.DependencyInjection; /// /// Extension methods for registering named AAuth agent HTTP clients via DI. diff --git a/src/AAuth/DependencyInjection/AAuthApplicationBuilderExtensions.cs b/src/AAuth/DependencyInjection/AAuthApplicationBuilderExtensions.cs index 76ae640..02474d2 100644 --- a/src/AAuth/DependencyInjection/AAuthApplicationBuilderExtensions.cs +++ b/src/AAuth/DependencyInjection/AAuthApplicationBuilderExtensions.cs @@ -1,14 +1,19 @@ using System; using System.Collections.Generic; +using AAuth; using AAuth.Crypto; using AAuth.Discovery; using AAuth.HttpSig; using AAuth.Server; -using Microsoft.AspNetCore.Builder; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; -namespace AAuth.DependencyInjection; +namespace Microsoft.AspNetCore.Builder; /// /// Extension methods for configuring AAuth verification middleware and diff --git a/src/AAuth/DependencyInjection/AAuthDiscoveryOptions.cs b/src/AAuth/DependencyInjection/AAuthDiscoveryOptions.cs index cea3552..00ef1e2 100644 --- a/src/AAuth/DependencyInjection/AAuthDiscoveryOptions.cs +++ b/src/AAuth/DependencyInjection/AAuthDiscoveryOptions.cs @@ -1,6 +1,6 @@ using System; -namespace AAuth.DependencyInjection; +namespace AAuth; /// /// Options for configuring shared discovery clients in DI. diff --git a/src/AAuth/DependencyInjection/AAuthDiscoveryServiceCollectionExtensions.cs b/src/AAuth/DependencyInjection/AAuthDiscoveryServiceCollectionExtensions.cs index b4e7d6d..22d7d25 100644 --- a/src/AAuth/DependencyInjection/AAuthDiscoveryServiceCollectionExtensions.cs +++ b/src/AAuth/DependencyInjection/AAuthDiscoveryServiceCollectionExtensions.cs @@ -1,10 +1,10 @@ using System; using System.Net.Http; +using AAuth; using AAuth.Discovery; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -namespace AAuth.DependencyInjection; +namespace Microsoft.Extensions.DependencyInjection; /// /// Extension methods for registering shared AAuth discovery clients in DI. diff --git a/src/AAuth/DependencyInjection/AAuthResourceOptions.cs b/src/AAuth/DependencyInjection/AAuthResourceOptions.cs index 3412092..968b653 100644 --- a/src/AAuth/DependencyInjection/AAuthResourceOptions.cs +++ b/src/AAuth/DependencyInjection/AAuthResourceOptions.cs @@ -2,8 +2,9 @@ using System.Collections.Generic; using AAuth.Crypto; using AAuth.HttpSig; +using Microsoft.Extensions.DependencyInjection; -namespace AAuth.DependencyInjection; +namespace AAuth; /// /// Options for configuring an AAuth resource server via diff --git a/src/AAuth/DependencyInjection/AAuthResourcePipelineOptions.cs b/src/AAuth/DependencyInjection/AAuthResourcePipelineOptions.cs index d0db9ab..4faf53d 100644 --- a/src/AAuth/DependencyInjection/AAuthResourcePipelineOptions.cs +++ b/src/AAuth/DependencyInjection/AAuthResourcePipelineOptions.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; using AAuth.Server; +using AAuth.Server.Verification; +using Microsoft.AspNetCore.Builder; -namespace AAuth.DependencyInjection; +namespace AAuth; /// /// Options for the unified diff --git a/src/AAuth/DependencyInjection/AAuthResourceServiceCollectionExtensions.cs b/src/AAuth/DependencyInjection/AAuthResourceServiceCollectionExtensions.cs index 780a4c6..7e3a82e 100644 --- a/src/AAuth/DependencyInjection/AAuthResourceServiceCollectionExtensions.cs +++ b/src/AAuth/DependencyInjection/AAuthResourceServiceCollectionExtensions.cs @@ -1,15 +1,18 @@ using System; using System.Net.Http; +using AAuth; using AAuth.Discovery; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -namespace AAuth.DependencyInjection; +namespace Microsoft.Extensions.DependencyInjection; /// /// Extension methods for registering AAuth resource server services via DI. diff --git a/src/AAuth/HttpSig/EnrolledBuilder.cs b/src/AAuth/EnrolledBuilder.cs similarity index 99% rename from src/AAuth/HttpSig/EnrolledBuilder.cs rename to src/AAuth/EnrolledBuilder.cs index 4cf91d3..ea0d9ee 100644 --- a/src/AAuth/HttpSig/EnrolledBuilder.cs +++ b/src/AAuth/EnrolledBuilder.cs @@ -2,10 +2,11 @@ using System.Net.Http; using AAuth.Agent; using AAuth.Crypto; +using AAuth.HttpSig; using AAuth.Server; using Microsoft.AspNetCore.Http; -namespace AAuth.HttpSig; +namespace AAuth; /// /// Fluent sub-builder for configuring an AP-enrolled agent client. diff --git a/src/AAuth/Headers/AAuthClaimsRequirement.cs b/src/AAuth/Headers/ClaimsRequirement.cs similarity index 91% rename from src/AAuth/Headers/AAuthClaimsRequirement.cs rename to src/AAuth/Headers/ClaimsRequirement.cs index fd90325..5497498 100644 --- a/src/AAuth/Headers/AAuthClaimsRequirement.cs +++ b/src/AAuth/Headers/ClaimsRequirement.cs @@ -19,7 +19,7 @@ namespace AAuth.Headers; /// body's required_claims field. The AAuth-Requirement header /// carries no claim names. /// -public sealed record AAuthClaimsRequirement(IReadOnlyList RequiredClaims) +public sealed record ClaimsRequirement(IReadOnlyList RequiredClaims) { /// Requirement type: claims. public const string RequirementType = "claims"; @@ -36,7 +36,7 @@ public sealed record AAuthClaimsRequirement(IReadOnlyList RequiredClaims /// when the requirement is claims but /// the body does not carry a non-empty required_claims array. /// - public static AAuthClaimsRequirement? FromResponse( + public static ClaimsRequirement? FromResponse( AAuthRequirementHeader.ParsedRequirement requirement, JsonObject? body) { @@ -67,6 +67,6 @@ public sealed record AAuthClaimsRequirement(IReadOnlyList RequiredClaims + $"(expected a '{RequiredClaimsField}' array in the response body)."); } - return new AAuthClaimsRequirement(names.Distinct(StringComparer.Ordinal).ToArray()); + return new ClaimsRequirement(names.Distinct(StringComparer.Ordinal).ToArray()); } } diff --git a/src/AAuth/Headers/AAuthClaimsResponse.cs b/src/AAuth/Headers/ClaimsResponse.cs similarity index 92% rename from src/AAuth/Headers/AAuthClaimsResponse.cs rename to src/AAuth/Headers/ClaimsResponse.cs index 2367810..1d4d0bb 100644 --- a/src/AAuth/Headers/AAuthClaimsResponse.cs +++ b/src/AAuth/Headers/ClaimsResponse.cs @@ -5,13 +5,13 @@ namespace AAuth.Headers; /// /// Typed reply a Person Server returns from an -/// callback (AAuth protocol §Claims +/// callback (AAuth protocol §Claims /// Required). Carries the directed user identifier () the /// recipient MUST supply as sub, plus any of the requested identity /// claims the PS holds for the bound principal. The SDK serializes this into /// the signed POST to the Access Server's Location URL. /// -public sealed record AAuthClaimsResponse +public sealed record ClaimsResponse { /// /// The directed (pairwise) user identifier for the requesting resource — diff --git a/src/AAuth/Headers/AAuthInteraction.cs b/src/AAuth/Headers/Interaction.cs similarity index 95% rename from src/AAuth/Headers/AAuthInteraction.cs rename to src/AAuth/Headers/Interaction.cs index b94c60c..0454351 100644 --- a/src/AAuth/Headers/AAuthInteraction.cs +++ b/src/AAuth/Headers/Interaction.cs @@ -17,7 +17,7 @@ namespace AAuth.Headers; /// projects the interaction-specific shape so callers don't have /// to fish the parameters out by string key. /// -public sealed record AAuthInteraction(string Url, string Code) +public sealed record Interaction(string Url, string Code) { /// Requirement type: interaction. public const string RequirementType = "interaction"; @@ -52,7 +52,7 @@ public string BuildUserUrl(string? callback = null) /// interaction but is missing the mandatory url or /// code parameters. /// - public static AAuthInteraction? FromRequirement(AAuthRequirementHeader.ParsedRequirement requirement) + public static Interaction? FromRequirement(AAuthRequirementHeader.ParsedRequirement requirement) { ArgumentNullException.ThrowIfNull(requirement); if (requirement.Requirement != RequirementType) @@ -81,7 +81,7 @@ public string BuildUserUrl(string? callback = null) $"AAuth-Requirement requirement=interaction is missing the '{CodeParameter}' parameter."); } - return new AAuthInteraction(url, code); + return new Interaction(url, code); } /// diff --git a/src/AAuth/HttpSig/ChallengeHandlingOptions.cs b/src/AAuth/HttpSig/ChallengeHandlingOptions.cs index 88c646b..b7272de 100644 --- a/src/AAuth/HttpSig/ChallengeHandlingOptions.cs +++ b/src/AAuth/HttpSig/ChallengeHandlingOptions.cs @@ -15,7 +15,7 @@ public sealed class ChallengeHandlingOptions /// during token exchange. The caller should display the interaction URL to the user. /// When , a deferred PS response surfaces as an exception. /// - public Func? OnInteractionRequired { get; set; } + public Func? OnInteractionRequired { get; set; } /// /// Maximum time to poll a deferred PS response before timing out. diff --git a/src/AAuth/Identifiers/AAuthAgentId.cs b/src/AAuth/Identifiers/AgentId.cs similarity index 83% rename from src/AAuth/Identifiers/AAuthAgentId.cs rename to src/AAuth/Identifiers/AgentId.cs index c922440..63f5e9d 100644 --- a/src/AAuth/Identifiers/AAuthAgentId.cs +++ b/src/AAuth/Identifiers/AgentId.cs @@ -7,11 +7,11 @@ namespace AAuth.Identifiers; /// Format: aauth:local@domain where local is [a-z0-9\-_+.]{1,255} /// and domain is a valid server identifier domain. /// -public readonly struct AAuthAgentId : IEquatable +public readonly struct AgentId : IEquatable { private readonly string _value; - private AAuthAgentId(string value) => _value = value; + private AgentId(string value) => _value = value; /// The validated identifier value. public string Value => _value; @@ -23,7 +23,7 @@ namespace AAuth.Identifiers; public string Domain => _value[(_value.IndexOf('@') + 1)..]; /// Parse and validate an agent identifier. Throws on invalid input. - public static AAuthAgentId Parse(string input) + public static AgentId Parse(string input) { if (!TryParse(input, out var id, out var error)) throw new FormatException(error); @@ -31,7 +31,7 @@ public static AAuthAgentId Parse(string input) } /// Try to parse and validate an agent identifier. - public static bool TryParse(string? input, out AAuthAgentId result, out string? error) + public static bool TryParse(string? input, out AgentId result, out string? error) { result = default; error = null; @@ -98,7 +98,7 @@ public static bool TryParse(string? input, out AAuthAgentId result, out string? } } - result = new AAuthAgentId(input); + result = new AgentId(input); return true; } @@ -106,10 +106,10 @@ private static bool IsValidLocalChar(char c) => c is (>= 'a' and <= 'z') or (>= '0' and <= '9') or '-' or '_' or '+' or '.'; /// - public bool Equals(AAuthAgentId other) => string.Equals(_value, other._value, StringComparison.Ordinal); + public bool Equals(AgentId other) => string.Equals(_value, other._value, StringComparison.Ordinal); /// - public override bool Equals(object? obj) => obj is AAuthAgentId other && Equals(other); + public override bool Equals(object? obj) => obj is AgentId other && Equals(other); /// public override int GetHashCode() => _value?.GetHashCode(StringComparison.Ordinal) ?? 0; @@ -118,8 +118,8 @@ private static bool IsValidLocalChar(char c) => public override string ToString() => _value ?? string.Empty; /// Equality operator. - public static bool operator ==(AAuthAgentId left, AAuthAgentId right) => left.Equals(right); + public static bool operator ==(AgentId left, AgentId right) => left.Equals(right); /// Inequality operator. - public static bool operator !=(AAuthAgentId left, AAuthAgentId right) => !left.Equals(right); + public static bool operator !=(AgentId left, AgentId right) => !left.Equals(right); } diff --git a/src/AAuth/Identifiers/AAuthServerId.cs b/src/AAuth/Identifiers/ServerId.cs similarity index 83% rename from src/AAuth/Identifiers/AAuthServerId.cs rename to src/AAuth/Identifiers/ServerId.cs index 8dd1185..8136e06 100644 --- a/src/AAuth/Identifiers/AAuthServerId.cs +++ b/src/AAuth/Identifiers/ServerId.cs @@ -9,17 +9,17 @@ namespace AAuth.Identifiers; /// no trailing slash, lowercase, IDN → ACE form. Loopback (localhost, /// 127.0.0.1, ::1) may include port for dev use. /// -public readonly struct AAuthServerId : IEquatable +public readonly struct ServerId : IEquatable { private readonly string _value; - private AAuthServerId(string value) => _value = value; + private ServerId(string value) => _value = value; /// The normalised identifier value. public string Value => _value; /// Parse and validate a server identifier string. Throws on invalid input. - public static AAuthServerId Parse(string input) + public static ServerId Parse(string input) { if (!TryParse(input, out var id, out var error)) throw new FormatException(error); @@ -27,7 +27,7 @@ public static AAuthServerId Parse(string input) } /// Try to parse and validate a server identifier string. - public static bool TryParse(string? input, out AAuthServerId result, out string? error) + public static bool TryParse(string? input, out ServerId result, out string? error) { result = default; error = null; @@ -110,15 +110,15 @@ public static bool TryParse(string? input, out AAuthServerId result, out string? return false; } - result = new AAuthServerId(canonical); + result = new ServerId(canonical); return true; } /// - public bool Equals(AAuthServerId other) => string.Equals(_value, other._value, StringComparison.Ordinal); + public bool Equals(ServerId other) => string.Equals(_value, other._value, StringComparison.Ordinal); /// - public override bool Equals(object? obj) => obj is AAuthServerId other && Equals(other); + public override bool Equals(object? obj) => obj is ServerId other && Equals(other); /// public override int GetHashCode() => _value?.GetHashCode(StringComparison.Ordinal) ?? 0; @@ -127,8 +127,8 @@ public static bool TryParse(string? input, out AAuthServerId result, out string? public override string ToString() => _value ?? string.Empty; /// Equality operator. - public static bool operator ==(AAuthServerId left, AAuthServerId right) => left.Equals(right); + public static bool operator ==(ServerId left, ServerId right) => left.Equals(right); /// Inequality operator. - public static bool operator !=(AAuthServerId left, AAuthServerId right) => !left.Equals(right); + public static bool operator !=(ServerId left, ServerId right) => !left.Equals(right); } diff --git a/src/AAuth/README.md b/src/AAuth/README.md index fb63f49..a2e6074 100644 --- a/src/AAuth/README.md +++ b/src/AAuth/README.md @@ -16,7 +16,7 @@ The simplest mode is **pseudonymous (HWK)** — the agent signs every request wi ```csharp using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var key = AAuthKey.Generate(); // Ed25519 keypair @@ -46,7 +46,7 @@ The PS-Asserted flow is the primary authorization model. The resource delegates ```csharp using AAuth.Crypto; -using AAuth.HttpSig; +using AAuth; var key = AAuthKey.Generate(); diff --git a/src/AAuth/HttpSig/SelfIssuingBuilder.cs b/src/AAuth/SelfIssuingBuilder.cs similarity index 99% rename from src/AAuth/HttpSig/SelfIssuingBuilder.cs rename to src/AAuth/SelfIssuingBuilder.cs index 82b41e4..f665cbc 100644 --- a/src/AAuth/HttpSig/SelfIssuingBuilder.cs +++ b/src/AAuth/SelfIssuingBuilder.cs @@ -1,10 +1,11 @@ using System; using System.Net.Http; using AAuth.Crypto; +using AAuth.HttpSig; using AAuth.Server; using Microsoft.AspNetCore.Http; -namespace AAuth.HttpSig; +namespace AAuth; /// /// Fluent sub-builder for configuring a self-issued agent identity. diff --git a/src/AAuth/Server/AAuthScopeHandler.cs b/src/AAuth/Server/Authorization/AAuthScopeHandler.cs similarity index 95% rename from src/AAuth/Server/AAuthScopeHandler.cs rename to src/AAuth/Server/Authorization/AAuthScopeHandler.cs index d18d446..13c63c5 100644 --- a/src/AAuth/Server/AAuthScopeHandler.cs +++ b/src/AAuth/Server/Authorization/AAuthScopeHandler.cs @@ -2,7 +2,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; -namespace AAuth.Server; +using AAuth.Server.Verification; + +namespace AAuth.Server.Authorization; /// /// Authorization handler that evaluates diff --git a/src/AAuth/Server/AAuthScopeRequirement.cs b/src/AAuth/Server/Authorization/AAuthScopeRequirement.cs similarity index 93% rename from src/AAuth/Server/AAuthScopeRequirement.cs rename to src/AAuth/Server/Authorization/AAuthScopeRequirement.cs index eebb73e..0a13707 100644 --- a/src/AAuth/Server/AAuthScopeRequirement.cs +++ b/src/AAuth/Server/Authorization/AAuthScopeRequirement.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Authorization; -namespace AAuth.Server; +namespace AAuth.Server.Authorization; /// /// Authorization requirement that demands a specific AAuth scope be present diff --git a/src/AAuth/Server/CallChainingHandler.cs b/src/AAuth/Server/CallChaining/CallChainingHandler.cs similarity index 96% rename from src/AAuth/Server/CallChainingHandler.cs rename to src/AAuth/Server/CallChaining/CallChainingHandler.cs index b63a6de..2986bdd 100644 --- a/src/AAuth/Server/CallChainingHandler.cs +++ b/src/AAuth/Server/CallChaining/CallChainingHandler.cs @@ -5,7 +5,7 @@ using AAuth.Discovery; using AAuth.Headers; -namespace AAuth.Server; +namespace AAuth.Server.CallChaining; /// /// that enables a resource to act as an @@ -64,7 +64,7 @@ public CallChainingHandler(TokenExchangeClient exchangeClient, CallChainingOptio public async Task ExchangeForDownstreamAsync( string upstreamAuthToken, string resourceToken, - Func? onInteractionRequired = null, + Func? onInteractionRequired = null, DeferredPollerOptions? pollerOptions = null, CancellationToken cancellationToken = default) { diff --git a/src/AAuth/Server/CallChainingOptions.cs b/src/AAuth/Server/CallChaining/CallChainingOptions.cs similarity index 96% rename from src/AAuth/Server/CallChainingOptions.cs rename to src/AAuth/Server/CallChaining/CallChainingOptions.cs index 81e1108..06c5088 100644 --- a/src/AAuth/Server/CallChainingOptions.cs +++ b/src/AAuth/Server/CallChaining/CallChainingOptions.cs @@ -3,7 +3,7 @@ using AAuth.Crypto; using AAuth.HttpSig; -namespace AAuth.Server; +namespace AAuth.Server.CallChaining; /// /// Configuration for a resource acting as an agent to access downstream diff --git a/src/AAuth/Server/CallChainingRouter.cs b/src/AAuth/Server/CallChaining/CallChainingRouter.cs similarity index 99% rename from src/AAuth/Server/CallChainingRouter.cs rename to src/AAuth/Server/CallChaining/CallChainingRouter.cs index 98c0179..2127bb4 100644 --- a/src/AAuth/Server/CallChainingRouter.cs +++ b/src/AAuth/Server/CallChaining/CallChainingRouter.cs @@ -3,7 +3,7 @@ using System.Text.Json.Nodes; using Microsoft.IdentityModel.Tokens; -namespace AAuth.Server; +namespace AAuth.Server.CallChaining; /// /// Pure-function routing logic for call chaining per §Call Chaining. diff --git a/src/AAuth/Server/UpstreamAuthTokenFeature.cs b/src/AAuth/Server/CallChaining/UpstreamAuthTokenFeature.cs similarity index 96% rename from src/AAuth/Server/UpstreamAuthTokenFeature.cs rename to src/AAuth/Server/CallChaining/UpstreamAuthTokenFeature.cs index 569ba0e..0714497 100644 --- a/src/AAuth/Server/UpstreamAuthTokenFeature.cs +++ b/src/AAuth/Server/CallChaining/UpstreamAuthTokenFeature.cs @@ -1,4 +1,4 @@ -namespace AAuth.Server; +namespace AAuth.Server.CallChaining; /// /// Feature set on diff --git a/src/AAuth/Server/AAuthChallengeMiddleware.cs b/src/AAuth/Server/Challenge/AAuthChallengeMiddleware.cs similarity index 99% rename from src/AAuth/Server/AAuthChallengeMiddleware.cs rename to src/AAuth/Server/Challenge/AAuthChallengeMiddleware.cs index 977eca1..2bdb6f9 100644 --- a/src/AAuth/Server/AAuthChallengeMiddleware.cs +++ b/src/AAuth/Server/Challenge/AAuthChallengeMiddleware.cs @@ -3,10 +3,11 @@ using AAuth.Crypto; using AAuth.Headers; using AAuth.HttpSig; +using AAuth.Server.Verification; using AAuth.Tokens; using Microsoft.AspNetCore.Http; -namespace AAuth.Server; +namespace AAuth.Server.Challenge; /// /// ASP.NET Core middleware that automatically issues 401 challenges with diff --git a/src/AAuth/Server/ChallengeOptions.cs b/src/AAuth/Server/Challenge/ChallengeOptions.cs similarity index 97% rename from src/AAuth/Server/ChallengeOptions.cs rename to src/AAuth/Server/Challenge/ChallengeOptions.cs index d03df67..88f12f2 100644 --- a/src/AAuth/Server/ChallengeOptions.cs +++ b/src/AAuth/Server/Challenge/ChallengeOptions.cs @@ -1,7 +1,8 @@ using System.Collections.Generic; using AAuth.Crypto; +using AAuth.Server.Verification; -namespace AAuth.Server; +namespace AAuth.Server.Challenge; /// /// Configuration for which automatically diff --git a/src/AAuth/Server/AAuthAccessServerMetadataOptions.cs b/src/AAuth/Server/Metadata/AAuthAccessServerMetadataOptions.cs similarity index 97% rename from src/AAuth/Server/AAuthAccessServerMetadataOptions.cs rename to src/AAuth/Server/Metadata/AAuthAccessServerMetadataOptions.cs index 6ac5336..60eff51 100644 --- a/src/AAuth/Server/AAuthAccessServerMetadataOptions.cs +++ b/src/AAuth/Server/Metadata/AAuthAccessServerMetadataOptions.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using AAuth.Crypto; -namespace AAuth.Server; +namespace AAuth.Server.Metadata; /// /// Configuration for the /.well-known/aauth-access.json endpoint. diff --git a/src/AAuth/Server/AAuthAgentMetadataOptions.cs b/src/AAuth/Server/Metadata/AAuthAgentMetadataOptions.cs similarity index 97% rename from src/AAuth/Server/AAuthAgentMetadataOptions.cs rename to src/AAuth/Server/Metadata/AAuthAgentMetadataOptions.cs index 1cc7283..709aee5 100644 --- a/src/AAuth/Server/AAuthAgentMetadataOptions.cs +++ b/src/AAuth/Server/Metadata/AAuthAgentMetadataOptions.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using AAuth.Crypto; -namespace AAuth.Server; +namespace AAuth.Server.Metadata; /// /// Configuration for the /.well-known/aauth-agent.json endpoint. diff --git a/src/AAuth/Server/AAuthPersonServerMetadataOptions.cs b/src/AAuth/Server/Metadata/AAuthPersonServerMetadataOptions.cs similarity index 98% rename from src/AAuth/Server/AAuthPersonServerMetadataOptions.cs rename to src/AAuth/Server/Metadata/AAuthPersonServerMetadataOptions.cs index 1a3aac5..2e33035 100644 --- a/src/AAuth/Server/AAuthPersonServerMetadataOptions.cs +++ b/src/AAuth/Server/Metadata/AAuthPersonServerMetadataOptions.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using AAuth.Crypto; -namespace AAuth.Server; +namespace AAuth.Server.Metadata; /// /// Configuration for the /.well-known/aauth-person.json endpoint. diff --git a/src/AAuth/Server/WellKnownEndpoints.cs b/src/AAuth/Server/Metadata/WellKnownEndpoints.cs similarity index 99% rename from src/AAuth/Server/WellKnownEndpoints.cs rename to src/AAuth/Server/Metadata/WellKnownEndpoints.cs index 87cc98e..9107186 100644 --- a/src/AAuth/Server/WellKnownEndpoints.cs +++ b/src/AAuth/Server/Metadata/WellKnownEndpoints.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; -namespace AAuth.Server; +namespace AAuth.Server.Metadata; /// /// Extension methods that map AAuth well-known endpoints onto an ASP.NET Core diff --git a/src/AAuth/Server/AAuthAccessMode.cs b/src/AAuth/Server/Verification/AAuthAccessMode.cs similarity index 94% rename from src/AAuth/Server/AAuthAccessMode.cs rename to src/AAuth/Server/Verification/AAuthAccessMode.cs index 50a90d5..c56323f 100644 --- a/src/AAuth/Server/AAuthAccessMode.cs +++ b/src/AAuth/Server/Verification/AAuthAccessMode.cs @@ -1,4 +1,4 @@ -namespace AAuth.Server; +namespace AAuth.Server.Verification; /// /// Specifies how a resource handles access decisions after identity verification. diff --git a/src/AAuth/Server/AAuthAuthenticationHandler.cs b/src/AAuth/Server/Verification/AAuthAuthenticationHandler.cs similarity index 99% rename from src/AAuth/Server/AAuthAuthenticationHandler.cs rename to src/AAuth/Server/Verification/AAuthAuthenticationHandler.cs index 421d251..f6bc6fc 100644 --- a/src/AAuth/Server/AAuthAuthenticationHandler.cs +++ b/src/AAuth/Server/Verification/AAuthAuthenticationHandler.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -namespace AAuth.Server; +namespace AAuth.Server.Verification; /// /// ASP.NET Core authentication handler that maps diff --git a/src/AAuth/Server/AAuthHttpContextExtensions.cs b/src/AAuth/Server/Verification/AAuthHttpContextExtensions.cs similarity index 99% rename from src/AAuth/Server/AAuthHttpContextExtensions.cs rename to src/AAuth/Server/Verification/AAuthHttpContextExtensions.cs index f442828..c58b9de 100644 --- a/src/AAuth/Server/AAuthHttpContextExtensions.cs +++ b/src/AAuth/Server/Verification/AAuthHttpContextExtensions.cs @@ -2,7 +2,7 @@ using AAuth.HttpSig; using Microsoft.AspNetCore.Http; -namespace AAuth.Server; +namespace AAuth.Server.Verification; /// /// Extension methods on for convenient access to diff --git a/src/AAuth/Server/AAuthLevel.cs b/src/AAuth/Server/Verification/AAuthLevel.cs similarity index 95% rename from src/AAuth/Server/AAuthLevel.cs rename to src/AAuth/Server/Verification/AAuthLevel.cs index 4709790..7fc1775 100644 --- a/src/AAuth/Server/AAuthLevel.cs +++ b/src/AAuth/Server/Verification/AAuthLevel.cs @@ -1,4 +1,4 @@ -namespace AAuth.Server; +namespace AAuth.Server.Verification; /// /// Authorization level determined by the type of AAuth credential presented. diff --git a/src/AAuth/Server/AAuthVerificationMiddleware.cs b/src/AAuth/Server/Verification/AAuthVerificationMiddleware.cs similarity index 99% rename from src/AAuth/Server/AAuthVerificationMiddleware.cs rename to src/AAuth/Server/Verification/AAuthVerificationMiddleware.cs index 49e5981..2c5c0f2 100644 --- a/src/AAuth/Server/AAuthVerificationMiddleware.cs +++ b/src/AAuth/Server/Verification/AAuthVerificationMiddleware.cs @@ -7,10 +7,11 @@ using AAuth.Discovery; using AAuth.Errors; using AAuth.HttpSig; +using AAuth.Server.CallChaining; using AAuth.Tokens; using Microsoft.AspNetCore.Http; -namespace AAuth.Server; +namespace AAuth.Server.Verification; /// /// ASP.NET Core middleware that verifies AAuth HTTP signatures (RFC 9421 PoP) diff --git a/src/AAuth/Server/AAuthVerificationOptions.cs b/src/AAuth/Server/Verification/AAuthVerificationOptions.cs similarity index 98% rename from src/AAuth/Server/AAuthVerificationOptions.cs rename to src/AAuth/Server/Verification/AAuthVerificationOptions.cs index 0e7b6cd..ef6774b 100644 --- a/src/AAuth/Server/AAuthVerificationOptions.cs +++ b/src/AAuth/Server/Verification/AAuthVerificationOptions.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace AAuth.Server; +namespace AAuth.Server.Verification; /// /// Configuration for which diff --git a/src/AAuth/Server/AAuthVerificationResult.cs b/src/AAuth/Server/Verification/AAuthVerificationResult.cs similarity index 98% rename from src/AAuth/Server/AAuthVerificationResult.cs rename to src/AAuth/Server/Verification/AAuthVerificationResult.cs index 5ed4a67..16803e0 100644 --- a/src/AAuth/Server/AAuthVerificationResult.cs +++ b/src/AAuth/Server/Verification/AAuthVerificationResult.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace AAuth.Server; +namespace AAuth.Server.Verification; /// /// Typed verification result exposed via HttpContext.Features after diff --git a/tests/AAuth.Conformance/AuthTokens/CallChainingTests.cs b/tests/AAuth.Conformance/AuthTokens/CallChainingTests.cs index 4db81db..ede485d 100644 --- a/tests/AAuth.Conformance/AuthTokens/CallChainingTests.cs +++ b/tests/AAuth.Conformance/AuthTokens/CallChainingTests.cs @@ -10,6 +10,11 @@ using AAuth.Crypto; using AAuth.Discovery; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using AAuth.Tokens; using Microsoft.IdentityModel.Tokens; using Xunit; diff --git a/tests/AAuth.Conformance/CallChaining/CallChainingHandlerTests.cs b/tests/AAuth.Conformance/CallChaining/CallChainingHandlerTests.cs index 22f193e..7db72a7 100644 --- a/tests/AAuth.Conformance/CallChaining/CallChainingHandlerTests.cs +++ b/tests/AAuth.Conformance/CallChaining/CallChainingHandlerTests.cs @@ -11,6 +11,11 @@ using AAuth.Headers; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using Microsoft.IdentityModel.Tokens; using Xunit; diff --git a/tests/AAuth.Conformance/CallChaining/CallChainingRouterTests.cs b/tests/AAuth.Conformance/CallChaining/CallChainingRouterTests.cs index 22ef991..a50fd4c 100644 --- a/tests/AAuth.Conformance/CallChaining/CallChainingRouterTests.cs +++ b/tests/AAuth.Conformance/CallChaining/CallChainingRouterTests.cs @@ -3,6 +3,11 @@ using System.Text.Json.Nodes; using AAuth.Crypto; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using AAuth.Tokens; using Microsoft.IdentityModel.Tokens; using Xunit; diff --git a/tests/AAuth.Conformance/CallChaining/UseAAuthIntermediaryTests.cs b/tests/AAuth.Conformance/CallChaining/UseAAuthIntermediaryTests.cs index 699f390..4276b89 100644 --- a/tests/AAuth.Conformance/CallChaining/UseAAuthIntermediaryTests.cs +++ b/tests/AAuth.Conformance/CallChaining/UseAAuthIntermediaryTests.cs @@ -5,11 +5,16 @@ using System.Threading; using System.Threading.Tasks; using AAuth.Crypto; -using AAuth.DependencyInjection; +using AAuth; using AAuth.Discovery; using AAuth.Headers; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using AAuth.Tokens; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; diff --git a/tests/AAuth.Conformance/Discovery/AllRolesWellKnownMetadataTests.cs b/tests/AAuth.Conformance/Discovery/AllRolesWellKnownMetadataTests.cs index bcdde8b..89829ed 100644 --- a/tests/AAuth.Conformance/Discovery/AllRolesWellKnownMetadataTests.cs +++ b/tests/AAuth.Conformance/Discovery/AllRolesWellKnownMetadataTests.cs @@ -4,6 +4,11 @@ using System.Threading.Tasks; using AAuth.Crypto; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; diff --git a/tests/AAuth.Conformance/Discovery/JtiStoreAndRevocationTests.cs b/tests/AAuth.Conformance/Discovery/JtiStoreAndRevocationTests.cs index 8295eaa..a8490e0 100644 --- a/tests/AAuth.Conformance/Discovery/JtiStoreAndRevocationTests.cs +++ b/tests/AAuth.Conformance/Discovery/JtiStoreAndRevocationTests.cs @@ -4,6 +4,11 @@ using System.Net.Http; using System.Threading.Tasks; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; diff --git a/tests/AAuth.Conformance/Discovery/WellKnownMetadataTests.cs b/tests/AAuth.Conformance/Discovery/WellKnownMetadataTests.cs index 885aa43..41d7c03 100644 --- a/tests/AAuth.Conformance/Discovery/WellKnownMetadataTests.cs +++ b/tests/AAuth.Conformance/Discovery/WellKnownMetadataTests.cs @@ -5,6 +5,11 @@ using System.Threading.Tasks; using AAuth.Crypto; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; diff --git a/tests/AAuth.Conformance/Errors/SignatureErrorTests.cs b/tests/AAuth.Conformance/Errors/SignatureErrorTests.cs index 078c35d..37c76f6 100644 --- a/tests/AAuth.Conformance/Errors/SignatureErrorTests.cs +++ b/tests/AAuth.Conformance/Errors/SignatureErrorTests.cs @@ -1,10 +1,15 @@ using System; using System.Threading.Tasks; using AAuth.Crypto; -using AAuth.DependencyInjection; +using AAuth; using AAuth.Errors; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; diff --git a/tests/AAuth.Conformance/HttpSignatures/AuthorizationIntegrationTests.cs b/tests/AAuth.Conformance/HttpSignatures/AuthorizationIntegrationTests.cs index 60c3313..9be8faf 100644 --- a/tests/AAuth.Conformance/HttpSignatures/AuthorizationIntegrationTests.cs +++ b/tests/AAuth.Conformance/HttpSignatures/AuthorizationIntegrationTests.cs @@ -8,10 +8,15 @@ using System.Threading; using System.Threading.Tasks; using AAuth.Crypto; -using AAuth.DependencyInjection; +using AAuth; using AAuth.Discovery; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using AAuth.Tokens; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; diff --git a/tests/AAuth.Conformance/HttpSignatures/ChallengeMiddlewareTests.cs b/tests/AAuth.Conformance/HttpSignatures/ChallengeMiddlewareTests.cs index 3cf1c85..e1a3174 100644 --- a/tests/AAuth.Conformance/HttpSignatures/ChallengeMiddlewareTests.cs +++ b/tests/AAuth.Conformance/HttpSignatures/ChallengeMiddlewareTests.cs @@ -6,11 +6,16 @@ using System.Threading; using System.Threading.Tasks; using AAuth.Crypto; -using AAuth.DependencyInjection; +using AAuth; using AAuth.Discovery; using AAuth.Headers; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using AAuth.Tokens; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; diff --git a/tests/AAuth.Conformance/HttpSignatures/JktJwtAndEcdsaTests.cs b/tests/AAuth.Conformance/HttpSignatures/JktJwtAndEcdsaTests.cs index 4fe110f..a60da40 100644 --- a/tests/AAuth.Conformance/HttpSignatures/JktJwtAndEcdsaTests.cs +++ b/tests/AAuth.Conformance/HttpSignatures/JktJwtAndEcdsaTests.cs @@ -7,10 +7,15 @@ using System.Threading; using System.Threading.Tasks; using AAuth.Crypto; -using AAuth.DependencyInjection; +using AAuth; using AAuth.Discovery; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using AAuth.Tokens; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; diff --git a/tests/AAuth.Conformance/HttpSignatures/NamingJwtValidationTests.cs b/tests/AAuth.Conformance/HttpSignatures/NamingJwtValidationTests.cs index ed5deab..da90f6e 100644 --- a/tests/AAuth.Conformance/HttpSignatures/NamingJwtValidationTests.cs +++ b/tests/AAuth.Conformance/HttpSignatures/NamingJwtValidationTests.cs @@ -4,9 +4,14 @@ using System.Text.Json.Nodes; using System.Threading.Tasks; using AAuth.Crypto; -using AAuth.DependencyInjection; +using AAuth; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using AAuth.Tokens; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; diff --git a/tests/AAuth.Conformance/HttpSignatures/VerificationMiddlewareTests.cs b/tests/AAuth.Conformance/HttpSignatures/VerificationMiddlewareTests.cs index 6c3eb11..d9ea682 100644 --- a/tests/AAuth.Conformance/HttpSignatures/VerificationMiddlewareTests.cs +++ b/tests/AAuth.Conformance/HttpSignatures/VerificationMiddlewareTests.cs @@ -8,11 +8,16 @@ using System.Threading; using System.Threading.Tasks; using AAuth.Crypto; -using AAuth.DependencyInjection; +using AAuth; using AAuth.Discovery; using AAuth.Errors; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using AAuth.Tokens; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; diff --git a/tests/AAuth.Conformance/Identifiers/AgentIdTests.cs b/tests/AAuth.Conformance/Identifiers/AgentIdTests.cs index 34ed4b6..47cfadb 100644 --- a/tests/AAuth.Conformance/Identifiers/AgentIdTests.cs +++ b/tests/AAuth.Conformance/Identifiers/AgentIdTests.cs @@ -15,28 +15,28 @@ public class AgentIdTests [InlineData("aauth:test_user@domain.example")] public void ValidIdentifiers_Accepted(string input) { - Assert.True(AAuthAgentId.TryParse(input, out var id, out _)); + Assert.True(AgentId.TryParse(input, out var id, out _)); Assert.Equal(input, id.Value); } [Fact(DisplayName = "§Agent Identifiers — uppercase rejected")] public void Rejects_Uppercase() { - Assert.False(AAuthAgentId.TryParse("aauth:MyAgent@agent.example", out _, out var err)); + Assert.False(AgentId.TryParse("aauth:MyAgent@agent.example", out _, out var err)); Assert.Contains("invalid character", err!); } [Fact(DisplayName = "§Agent Identifiers — missing 'aauth:' rejected")] public void Rejects_MissingScheme() { - Assert.False(AAuthAgentId.TryParse("assistant@agent.example", out _, out var err)); + Assert.False(AgentId.TryParse("assistant@agent.example", out _, out var err)); Assert.Contains("aauth:", err!); } [Fact(DisplayName = "§Agent Identifiers — empty local part rejected")] public void Rejects_EmptyLocal() { - Assert.False(AAuthAgentId.TryParse("aauth:@agent.example", out _, out var err)); + Assert.False(AgentId.TryParse("aauth:@agent.example", out _, out var err)); Assert.Contains("local part must not be empty", err!); } @@ -44,43 +44,43 @@ public void Rejects_EmptyLocal() public void Rejects_LongLocal() { var longLocal = new string('a', 256); - Assert.False(AAuthAgentId.TryParse($"aauth:{longLocal}@agent.example", out _, out var err)); + Assert.False(AgentId.TryParse($"aauth:{longLocal}@agent.example", out _, out var err)); Assert.Contains("255", err!); } [Fact(DisplayName = "§Agent Identifiers — invalid chars rejected (space)")] public void Rejects_Space() { - Assert.False(AAuthAgentId.TryParse("aauth:my agent@agent.example", out _, out var err)); + Assert.False(AgentId.TryParse("aauth:my agent@agent.example", out _, out var err)); Assert.Contains("invalid character", err!); } [Fact(DisplayName = "§Agent Identifiers — missing @ rejected")] public void Rejects_MissingAt() { - Assert.False(AAuthAgentId.TryParse("aauth:agentnoat", out _, out var err)); + Assert.False(AgentId.TryParse("aauth:agentnoat", out _, out var err)); Assert.Contains("@", err!); } [Fact(DisplayName = "§Agent Identifiers — uppercase domain rejected")] public void Rejects_UppercaseDomain() { - Assert.False(AAuthAgentId.TryParse("aauth:agent@Agent.Example", out _, out var err)); + Assert.False(AgentId.TryParse("aauth:agent@Agent.Example", out _, out var err)); Assert.Contains("lowercase", err!); } [Fact(DisplayName = "§Agent Identifiers — case-sensitive exact comparison")] public void ExactComparison() { - var id1 = AAuthAgentId.Parse("aauth:alice@ap.example"); - var id2 = AAuthAgentId.Parse("aauth:alice@ap.example"); + var id1 = AgentId.Parse("aauth:alice@ap.example"); + var id2 = AgentId.Parse("aauth:alice@ap.example"); Assert.Equal(id1, id2); } [Fact(DisplayName = "§Agent Identifiers — local and domain parts extracted")] public void PartsExtracted() { - var id = AAuthAgentId.Parse("aauth:alice@ap.example"); + var id = AgentId.Parse("aauth:alice@ap.example"); Assert.Equal("alice", id.Local); Assert.Equal("ap.example", id.Domain); } diff --git a/tests/AAuth.Conformance/Identifiers/ServerIdTests.cs b/tests/AAuth.Conformance/Identifiers/ServerIdTests.cs index 77159f4..4296004 100644 --- a/tests/AAuth.Conformance/Identifiers/ServerIdTests.cs +++ b/tests/AAuth.Conformance/Identifiers/ServerIdTests.cs @@ -14,56 +14,56 @@ public class ServerIdTests [InlineData("https://sub.domain.example")] public void ValidIdentifiers_Accepted(string input) { - Assert.True(AAuthServerId.TryParse(input, out var id, out _)); + Assert.True(ServerId.TryParse(input, out var id, out _)); Assert.Equal(input, id.Value); } [Fact(DisplayName = "§Server Identifiers — path rejected")] public void Rejects_Path() { - Assert.False(AAuthServerId.TryParse("https://agent.example/v1", out _, out var err)); + Assert.False(ServerId.TryParse("https://agent.example/v1", out _, out var err)); Assert.Contains("path", err!, System.StringComparison.OrdinalIgnoreCase); } [Fact(DisplayName = "§Server Identifiers — port (non-loopback) rejected")] public void Rejects_NonLoopbackPort() { - Assert.False(AAuthServerId.TryParse("https://agent.example:8443", out _, out var err)); + Assert.False(ServerId.TryParse("https://agent.example:8443", out _, out var err)); Assert.Contains("port", err!, System.StringComparison.OrdinalIgnoreCase); } [Fact(DisplayName = "§Server Identifiers — trailing slash rejected")] public void Rejects_TrailingSlash() { - Assert.False(AAuthServerId.TryParse("https://agent.example/", out _, out var err)); + Assert.False(ServerId.TryParse("https://agent.example/", out _, out var err)); Assert.Contains("trailing slash", err!, System.StringComparison.OrdinalIgnoreCase); } [Fact(DisplayName = "§Server Identifiers — mixed case rejected")] public void Rejects_MixedCase() { - Assert.False(AAuthServerId.TryParse("https://Agent.Example", out _, out var err)); + Assert.False(ServerId.TryParse("https://Agent.Example", out _, out var err)); Assert.Contains("lowercase", err!, System.StringComparison.OrdinalIgnoreCase); } [Fact(DisplayName = "§Server Identifiers — http (non-loopback) rejected")] public void Rejects_HttpNonLoopback() { - Assert.False(AAuthServerId.TryParse("http://agent.example", out _, out var err)); + Assert.False(ServerId.TryParse("http://agent.example", out _, out var err)); Assert.Contains("https", err!, System.StringComparison.OrdinalIgnoreCase); } [Fact(DisplayName = "§Server Identifiers — loopback+port accepted for dev")] public void Accepts_LoopbackWithPort() { - Assert.True(AAuthServerId.TryParse("http://localhost:5100", out var id, out _)); + Assert.True(ServerId.TryParse("http://localhost:5100", out var id, out _)); Assert.Equal("http://localhost:5100", id.Value); } [Fact(DisplayName = "§Server Identifiers — loopback 127.0.0.1+port accepted")] public void Accepts_Loopback127WithPort() { - Assert.True(AAuthServerId.TryParse("http://127.0.0.1:8080", out var id, out _)); + Assert.True(ServerId.TryParse("http://127.0.0.1:8080", out var id, out _)); Assert.Equal("http://127.0.0.1:8080", id.Value); } @@ -72,29 +72,29 @@ public void IdnNormalisedToAce() { // "münchen.example" → "xn--mnchen-3ya.example" (but parsed through URI) // Use a known punycode domain directly to test we accept it. - Assert.True(AAuthServerId.TryParse("https://xn--nxasmq6b.example", out var id, out _)); + Assert.True(ServerId.TryParse("https://xn--nxasmq6b.example", out var id, out _)); Assert.Equal("https://xn--nxasmq6b.example", id.Value); } [Fact(DisplayName = "§Server Identifiers — query string rejected")] public void Rejects_QueryString() { - Assert.False(AAuthServerId.TryParse("https://agent.example?foo=bar", out _, out var err)); + Assert.False(ServerId.TryParse("https://agent.example?foo=bar", out _, out var err)); Assert.Contains("query", err!, System.StringComparison.OrdinalIgnoreCase); } [Fact(DisplayName = "§Server Identifiers — fragment rejected")] public void Rejects_Fragment() { - Assert.False(AAuthServerId.TryParse("https://agent.example#frag", out _, out var err)); + Assert.False(ServerId.TryParse("https://agent.example#frag", out _, out var err)); Assert.Contains("fragment", err!, System.StringComparison.OrdinalIgnoreCase); } [Fact(DisplayName = "§Server Identifiers — equality by value")] public void EqualityByValue() { - var id1 = AAuthServerId.Parse("https://ps.example"); - var id2 = AAuthServerId.Parse("https://ps.example"); + var id1 = ServerId.Parse("https://ps.example"); + var id2 = ServerId.Parse("https://ps.example"); Assert.Equal(id1, id2); Assert.True(id1 == id2); } diff --git a/tests/AAuth.Conformance/Observability/ActivityDiagnosticsTests.cs b/tests/AAuth.Conformance/Observability/ActivityDiagnosticsTests.cs index 63e2956..c7dc473 100644 --- a/tests/AAuth.Conformance/Observability/ActivityDiagnosticsTests.cs +++ b/tests/AAuth.Conformance/Observability/ActivityDiagnosticsTests.cs @@ -10,10 +10,15 @@ using System.Threading.Tasks; using AAuth.Agent; using AAuth.Crypto; -using AAuth.DependencyInjection; +using AAuth; using AAuth.Discovery; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using AAuth.Tokens; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; diff --git a/tests/AAuth.Conformance/Observability/PreferWaitHeaderTests.cs b/tests/AAuth.Conformance/Observability/PreferWaitHeaderTests.cs index 944662f..8eef5ba 100644 --- a/tests/AAuth.Conformance/Observability/PreferWaitHeaderTests.cs +++ b/tests/AAuth.Conformance/Observability/PreferWaitHeaderTests.cs @@ -12,6 +12,11 @@ using AAuth.Discovery; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using AAuth.Tokens; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; diff --git a/tests/AAuth.Conformance/ResourceTokens/OpaqueTokenStoreTests.cs b/tests/AAuth.Conformance/ResourceTokens/OpaqueTokenStoreTests.cs index 1c9b6f2..7b61d48 100644 --- a/tests/AAuth.Conformance/ResourceTokens/OpaqueTokenStoreTests.cs +++ b/tests/AAuth.Conformance/ResourceTokens/OpaqueTokenStoreTests.cs @@ -1,6 +1,11 @@ using System; using System.Threading.Tasks; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using Xunit; namespace AAuth.Conformance.ResourceTokens; diff --git a/tests/AAuth.Tests/AccessServerClientTests.cs b/tests/AAuth.Tests/AccessServerClientTests.cs index 611b5d4..7cd535d 100644 --- a/tests/AAuth.Tests/AccessServerClientTests.cs +++ b/tests/AAuth.Tests/AccessServerClientTests.cs @@ -7,6 +7,7 @@ using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; +using AAuth.Access; using AAuth.Agent; using AAuth.Crypto; using AAuth.Discovery; @@ -143,7 +144,7 @@ public async Task FederateAsync_PushesClaimsAndReturnsAuthToken_OnClaimsRequirem claimsResponse: _ => Ok(authToken)); var client = BuildClient(stub); - AAuthClaimsRequirement? seen = null; + ClaimsRequirement? seen = null; var result = await client.FederateAsync(AsIssuer, new AccessServerRequest { ResourceToken = "the-resource-token", @@ -155,7 +156,7 @@ public async Task FederateAsync_PushesClaimsAndReturnsAuthToken_OnClaimsRequirem OnClaimsRequired = (requirement, _) => { seen = requirement; - return Task.FromResult(new AAuthClaimsResponse + return Task.FromResult(new ClaimsResponse { Subject = "directed-123", Claims = new Dictionary @@ -208,7 +209,7 @@ public async Task FederateAsync_ThrowsInvalidOperation_WhenClaimsHandlerOmitsSub AgentKey = agentKey, RequestedScope = "whoami", OnClaimsRequired = (_, _) => Task.FromResult( - new AAuthClaimsResponse { Subject = string.Empty }), + new ClaimsResponse { Subject = string.Empty }), })); Assert.Contains("sub", ex.Message); @@ -252,7 +253,7 @@ public async Task FederateAsync_ComposesInteractionThenClaims_OnSameLocation() var client = BuildClient(stub); var interactionSeen = false; - AAuthClaimsRequirement? claimsSeen = null; + ClaimsRequirement? claimsSeen = null; var result = await client.FederateAsync(AsIssuer, new AccessServerRequest { ResourceToken = "the-resource-token", @@ -265,7 +266,7 @@ public async Task FederateAsync_ComposesInteractionThenClaims_OnSameLocation() OnClaimsRequired = (requirement, _) => { claimsSeen = requirement; - return Task.FromResult(new AAuthClaimsResponse + return Task.FromResult(new ClaimsResponse { Subject = "directed-xyz", Claims = new Dictionary diff --git a/tests/AAuth.Tests/Agent/ChallengeHandlerTests.cs b/tests/AAuth.Tests/Agent/ChallengeHandlerTests.cs index 3c6ffdd..d858e1a 100644 --- a/tests/AAuth.Tests/Agent/ChallengeHandlerTests.cs +++ b/tests/AAuth.Tests/Agent/ChallengeHandlerTests.cs @@ -363,7 +363,7 @@ await Assert.ThrowsAsync( } private static async Task CaptureExchangeBodyAsync( - Func? onInteractionRequired, + Func? onInteractionRequired, IReadOnlyList? capabilities = null, string? prompt = null) { diff --git a/tests/AAuth.Tests/Agent/InteractionChainingTests.cs b/tests/AAuth.Tests/Agent/InteractionChainingTests.cs index d713e4e..83c1828 100644 --- a/tests/AAuth.Tests/Agent/InteractionChainingTests.cs +++ b/tests/AAuth.Tests/Agent/InteractionChainingTests.cs @@ -33,7 +33,7 @@ public async Task ChainedException_AbortsBeforePolling_AndSurfacesInteraction() var metaClient = new MetadataClient(new HttpClient(handler)); var exchangeClient = new TokenExchangeClient(new HttpClient(handler), metaClient); - AAuthInteraction? captured = null; + Interaction? captured = null; var ex = await Assert.ThrowsAsync( () => exchangeClient.ExchangeAsync( @@ -143,7 +143,7 @@ protected override Task SendAsync( response.Headers.TryAddWithoutValidation("Cache-Control", "no-store"); response.Headers.TryAddWithoutValidation( AAuthRequirementHeader.Name, - AAuthInteraction.Format(InteractionUrl, InteractionCode)); + Interaction.Format(InteractionUrl, InteractionCode)); return Task.FromResult(response); } } diff --git a/tests/AAuth.Tests/Configuration/OptionsThreadingTests.cs b/tests/AAuth.Tests/Configuration/OptionsThreadingTests.cs index d8a422d..a2f1ec0 100644 --- a/tests/AAuth.Tests/Configuration/OptionsThreadingTests.cs +++ b/tests/AAuth.Tests/Configuration/OptionsThreadingTests.cs @@ -1,8 +1,13 @@ using System; using System.Net.Http; -using AAuth.DependencyInjection; +using AAuth; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using AAuth.Tokens; using Xunit; diff --git a/tests/AAuth.Tests/DependencyInjection/AAuthAgentDITests.cs b/tests/AAuth.Tests/DependencyInjection/AAuthAgentDITests.cs index 2251104..bb71558 100644 --- a/tests/AAuth.Tests/DependencyInjection/AAuthAgentDITests.cs +++ b/tests/AAuth.Tests/DependencyInjection/AAuthAgentDITests.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using AAuth.Agent; using AAuth.Crypto; -using AAuth.DependencyInjection; +using AAuth; using AAuth.Tokens; using Microsoft.Extensions.DependencyInjection; using Xunit; diff --git a/tests/AAuth.Tests/DependencyInjection/AAuthDiscoveryDITests.cs b/tests/AAuth.Tests/DependencyInjection/AAuthDiscoveryDITests.cs index 7184285..f7fb4d8 100644 --- a/tests/AAuth.Tests/DependencyInjection/AAuthDiscoveryDITests.cs +++ b/tests/AAuth.Tests/DependencyInjection/AAuthDiscoveryDITests.cs @@ -1,5 +1,5 @@ using System; -using AAuth.DependencyInjection; +using AAuth; using AAuth.Discovery; using Microsoft.Extensions.DependencyInjection; using Xunit; diff --git a/tests/AAuth.Tests/DependencyInjection/AAuthResourceDITests.cs b/tests/AAuth.Tests/DependencyInjection/AAuthResourceDITests.cs index fcde9f6..7ed7c64 100644 --- a/tests/AAuth.Tests/DependencyInjection/AAuthResourceDITests.cs +++ b/tests/AAuth.Tests/DependencyInjection/AAuthResourceDITests.cs @@ -1,9 +1,14 @@ using System; using AAuth.Crypto; -using AAuth.DependencyInjection; +using AAuth; using AAuth.Discovery; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using Microsoft.Extensions.DependencyInjection; using Xunit; diff --git a/tests/AAuth.Tests/Headers/AAuthInteractionTests.cs b/tests/AAuth.Tests/Headers/InteractionTests.cs similarity index 76% rename from tests/AAuth.Tests/Headers/AAuthInteractionTests.cs rename to tests/AAuth.Tests/Headers/InteractionTests.cs index 4d3bdf8..f60e875 100644 --- a/tests/AAuth.Tests/Headers/AAuthInteractionTests.cs +++ b/tests/AAuth.Tests/Headers/InteractionTests.cs @@ -4,14 +4,14 @@ namespace AAuth.Tests.Headers; -public class AAuthInteractionTests +public class InteractionTests { [Fact] public void Format_RoundTrips_Through_ParsedRequirement() { - var header = AAuthInteraction.Format("https://ps.example/interaction", "ABCD1234"); + var header = Interaction.Format("https://ps.example/interaction", "ABCD1234"); var parsed = AAuthRequirementHeader.Parse(header); - var interaction = AAuthInteraction.FromRequirement(parsed); + var interaction = Interaction.FromRequirement(parsed); Assert.NotNull(interaction); Assert.Equal("https://ps.example/interaction", interaction!.Url); @@ -23,20 +23,20 @@ public void FromRequirement_ReturnsNull_ForNonInteractionType() { var parsed = AAuthRequirementHeader.Parse( AAuthRequirementHeader.FormatAuthToken("eyJ.aGVsbG8.signature")); - Assert.Null(AAuthInteraction.FromRequirement(parsed)); + Assert.Null(Interaction.FromRequirement(parsed)); } [Fact] public void FromRequirement_Throws_WhenInteractionParametersMissing() { var parsed = AAuthRequirementHeader.Parse("requirement=interaction; url=\"https://ps/i\""); - Assert.Throws(() => AAuthInteraction.FromRequirement(parsed)); + Assert.Throws(() => Interaction.FromRequirement(parsed)); } [Fact] public void BuildUserUrl_AppendsCodeAsQueryParameter() { - var i = new AAuthInteraction("https://ps.example/interaction", "ABCD/1234"); + var i = new Interaction("https://ps.example/interaction", "ABCD/1234"); Assert.Equal( "https://ps.example/interaction?code=ABCD%2F1234", i.BuildUserUrl()); @@ -45,7 +45,7 @@ public void BuildUserUrl_AppendsCodeAsQueryParameter() [Fact] public void BuildUserUrl_PreservesExistingQuery_AndAppendsCallback() { - var i = new AAuthInteraction("https://ps.example/interaction?ref=foo", "XYZ"); + var i = new Interaction("https://ps.example/interaction?ref=foo", "XYZ"); Assert.Equal( "https://ps.example/interaction?ref=foo&code=XYZ&callback=https%3A%2F%2Fagent.example%2Fcb", i.BuildUserUrl("https://agent.example/cb")); @@ -58,7 +58,7 @@ public void BuildUserUrl_PreservesExistingQuery_AndAppendsCallback() [InlineData("\"", "code")] public void Format_RejectsControlCharactersAndQuotes(string url, string code) { - Assert.Throws(() => AAuthInteraction.Format(url, code)); + Assert.Throws(() => Interaction.Format(url, code)); } [Theory] @@ -69,7 +69,7 @@ public void Format_RejectsControlCharactersAndQuotes(string url, string code) [InlineData("relative/path")] public void Format_RejectsNonHttpsSchemes(string url) { - Assert.Throws(() => AAuthInteraction.Format(url, "ABCD")); + Assert.Throws(() => Interaction.Format(url, "ABCD")); } [Theory] @@ -83,7 +83,7 @@ public void FromRequirement_RejectsNonHttpsSchemes(string url) // a malicious PS could emit the header bytes directly). var raw = $"requirement=interaction; url=\"{url}\"; code=\"ABCD\""; var parsed = AAuthRequirementHeader.Parse(raw); - Assert.Throws(() => AAuthInteraction.FromRequirement(parsed)); + Assert.Throws(() => Interaction.FromRequirement(parsed)); } [Fact] @@ -91,7 +91,7 @@ public void FromRequirement_AllowsLoopbackHttp() { var raw = "requirement=interaction; url=\"http://localhost:5100/interaction\"; code=\"ABCD\""; var parsed = AAuthRequirementHeader.Parse(raw); - var i = AAuthInteraction.FromRequirement(parsed); + var i = Interaction.FromRequirement(parsed); Assert.NotNull(i); Assert.Equal("http://localhost:5100/interaction", i!.Url); } diff --git a/tests/AAuth.Tests/HttpSig/AAuthClientBuilderTests.cs b/tests/AAuth.Tests/HttpSig/AAuthClientBuilderTests.cs index a0a5d75..8a0f295 100644 --- a/tests/AAuth.Tests/HttpSig/AAuthClientBuilderTests.cs +++ b/tests/AAuth.Tests/HttpSig/AAuthClientBuilderTests.cs @@ -1,6 +1,11 @@ using AAuth.Crypto; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Xunit; diff --git a/tests/AAuth.Tests/HttpSig/InteractionHandlerTests.cs b/tests/AAuth.Tests/HttpSig/InteractionHandlerTests.cs index 895ff27..c7a3cfb 100644 --- a/tests/AAuth.Tests/HttpSig/InteractionHandlerTests.cs +++ b/tests/AAuth.Tests/HttpSig/InteractionHandlerTests.cs @@ -204,7 +204,7 @@ public async Task Builder_WithInteractionHandling_AddsCapability() private static HttpResponseMessage Make202Interaction(string interactUrl, string code, string pendingUrl) { - var headerValue = AAuthInteraction.Format(interactUrl, code); + var headerValue = Interaction.Format(interactUrl, code); var response = new HttpResponseMessage(HttpStatusCode.Accepted) { Headers = diff --git a/tests/AAuth.Tests/Integration/MockPersonServerFederationTests.cs b/tests/AAuth.Tests/Integration/MockPersonServerFederationTests.cs index 10feb91..010a9f2 100644 --- a/tests/AAuth.Tests/Integration/MockPersonServerFederationTests.cs +++ b/tests/AAuth.Tests/Integration/MockPersonServerFederationTests.cs @@ -6,6 +6,7 @@ using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; +using AAuth.Access; using AAuth.Agent; using AAuth.Crypto; using AAuth.Discovery; @@ -286,7 +287,7 @@ protected override Task SendAsync( deferred.Headers.TryAddWithoutValidation("Retry-After", "0"); deferred.Headers.TryAddWithoutValidation( "AAuth-Requirement", - AAuth.Headers.AAuthInteraction.Format($"{AsIssuer}/interaction/login", "abc")); + AAuth.Headers.Interaction.Format($"{AsIssuer}/interaction/login", "abc")); return Task.FromResult(deferred); } diff --git a/tests/AAuth.Tests/Integration/MockPersonServerTests.cs b/tests/AAuth.Tests/Integration/MockPersonServerTests.cs index 9d16968..154ee92 100644 --- a/tests/AAuth.Tests/Integration/MockPersonServerTests.cs +++ b/tests/AAuth.Tests/Integration/MockPersonServerTests.cs @@ -349,7 +349,7 @@ public async Task Token_Returns202WithInteractionRequirement_WhenConsentMissing( Assert.NotNull(response.Headers.Location); Assert.True(response.Headers.TryGetValues("AAuth-Requirement", out var values)); var parsed = AAuth.Headers.AAuthRequirementHeader.Parse(string.Join(", ", values!)); - var interaction = AAuth.Headers.AAuthInteraction.FromRequirement(parsed); + var interaction = AAuth.Headers.Interaction.FromRequirement(parsed); Assert.NotNull(interaction); Assert.StartsWith($"{PsIssuer}/interaction", interaction!.Url); Assert.False(string.IsNullOrEmpty(interaction.Code)); @@ -426,7 +426,7 @@ public async Task Interaction_GetRendersConsentForm_ThenPostApproveFlipsPending( Assert.Equal(HttpStatusCode.Accepted, initial.StatusCode); Assert.True(initial.Headers.TryGetValues("AAuth-Requirement", out var reqValues)); var parsed = AAuth.Headers.AAuthRequirementHeader.Parse(string.Join(", ", reqValues!)); - var interaction = AAuth.Headers.AAuthInteraction.FromRequirement(parsed); + var interaction = AAuth.Headers.Interaction.FromRequirement(parsed); Assert.NotNull(interaction); // User's browser → GET /interaction?code=… renders a consent form. @@ -485,7 +485,7 @@ public async Task Pending_Returns403AccessDenied_AfterDeny() Assert.Equal(HttpStatusCode.Accepted, initial.StatusCode); var parsed = AAuth.Headers.AAuthRequirementHeader.Parse( string.Join(", ", initial.Headers.GetValues("AAuth-Requirement"))); - var interaction = AAuth.Headers.AAuthInteraction.FromRequirement(parsed); + var interaction = AAuth.Headers.Interaction.FromRequirement(parsed); Assert.NotNull(interaction); // User's browser → POST /interaction/deny. diff --git a/tests/AAuth.Tests/Integration/WhoAmIFlowTests.cs b/tests/AAuth.Tests/Integration/WhoAmIFlowTests.cs index 7e87e47..9a4f8bd 100644 --- a/tests/AAuth.Tests/Integration/WhoAmIFlowTests.cs +++ b/tests/AAuth.Tests/Integration/WhoAmIFlowTests.cs @@ -13,6 +13,11 @@ using AAuth.Headers; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using AAuth.Tokens; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; @@ -448,7 +453,7 @@ public async Task ThreePartyUserConsentFlow_WaitsForApproval() // to /interaction/approve as a form, exactly as the HTML form in // the user's browser would. Once consent lands, the next poll on // /pending/{id} returns 200 + auth_token. - Func approveAsUser = + Func approveAsUser = async (interaction, ct) => { Assert.NotNull(interaction.Code); @@ -575,7 +580,7 @@ public async Task ThreePartyUserConsentFlow_ThrowsAAuthInteractionDenied_WhenUse var holder = new AAuthTokenHolder(agentToken); - Func denyAsUser = + Func denyAsUser = async (interaction, ct) => { using var browser = new HttpClient(consentPsHandler, disposeHandler: false); diff --git a/tests/AAuth.Tests/Server/AAuthHttpContextExtensionsTests.cs b/tests/AAuth.Tests/Server/AAuthHttpContextExtensionsTests.cs index 6a6092f..6d66625 100644 --- a/tests/AAuth.Tests/Server/AAuthHttpContextExtensionsTests.cs +++ b/tests/AAuth.Tests/Server/AAuthHttpContextExtensionsTests.cs @@ -3,6 +3,11 @@ using AAuth.Headers; using AAuth.HttpSig; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using Microsoft.AspNetCore.Http; using Xunit; diff --git a/tests/AAuth.Tests/Server/AAuthScopeHandlerTests.cs b/tests/AAuth.Tests/Server/AAuthScopeHandlerTests.cs index 519ee3b..3cb622d 100644 --- a/tests/AAuth.Tests/Server/AAuthScopeHandlerTests.cs +++ b/tests/AAuth.Tests/Server/AAuthScopeHandlerTests.cs @@ -2,6 +2,11 @@ using System.Threading.Tasks; using AAuth; using AAuth.Server; +using AAuth.Server.Authorization; +using AAuth.Server.CallChaining; +using AAuth.Server.Challenge; +using AAuth.Server.Metadata; +using AAuth.Server.Verification; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Xunit;