@_mustachio/openauth 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/adapters/d1/index.js +18 -9
- package/dist/esm/adapters/d1/migrations.js +7 -2
- package/dist/esm/adapters/d1/session-store.js +39 -0
- package/dist/esm/adapters/d1/session.js +2 -2
- package/dist/esm/adapters/durable-object/index.js +4 -0
- package/dist/esm/adapters/durable-object/session-store.js +42 -0
- package/dist/esm/adapters/dynamo/index.js +24 -7
- package/dist/esm/adapters/dynamo/session-store.js +49 -0
- package/dist/esm/adapters/kms/index.js +6 -2
- package/dist/esm/adapters/kv/index.js +6 -2
- package/dist/esm/adapters/memory/index.js +24 -7
- package/dist/esm/adapters/memory/session-store.js +45 -0
- package/dist/esm/adapters/memory/token-store.js +13 -0
- package/dist/esm/adapters/postgres/executor.js +2 -2
- package/dist/esm/adapters/postgres/index.js +33 -10
- package/dist/esm/adapters/postgres/migrations.js +8 -2
- package/dist/esm/adapters/postgres/session-store.js +42 -0
- package/dist/esm/client.js +35 -28
- package/dist/esm/domain/authorize.js +7 -2
- package/dist/esm/domain/callback.js +124 -7
- package/dist/esm/domain/client-auth.js +2 -2
- package/dist/esm/domain/crypto.js +14 -14
- package/dist/esm/domain/discovery.js +46 -4
- package/dist/esm/domain/dpop.js +122 -0
- package/dist/esm/domain/id-token.js +94 -0
- package/dist/esm/domain/introspect.js +5 -1
- package/dist/esm/domain/jwt.js +30 -2
- package/dist/esm/domain/logout.js +75 -0
- package/dist/esm/domain/method-dispatch.js +27 -1
- package/dist/esm/domain/method-route.js +83 -1
- package/dist/esm/domain/par.js +51 -0
- package/dist/esm/domain/pkce.js +2 -2
- package/dist/esm/domain/refresh.js +12 -1
- package/dist/esm/domain/register.js +74 -0
- package/dist/esm/domain/revoke.js +2 -2
- package/dist/esm/domain/state-envelope.js +2 -2
- package/dist/esm/domain/token-exchange.js +1 -0
- package/dist/esm/domain/token.js +53 -12
- package/dist/esm/domain/userinfo.js +38 -3
- package/dist/esm/error.js +8 -8
- package/dist/esm/http/cookies.js +19 -2
- package/dist/esm/http/errors.js +4 -4
- package/dist/esm/http/handlers/authorize.js +92 -21
- package/dist/esm/http/handlers/callback.js +7 -1
- package/dist/esm/http/handlers/end-session.js +64 -0
- package/dist/esm/http/handlers/metadata.js +7 -4
- package/dist/esm/http/handlers/method-route.js +47 -2
- package/dist/esm/http/handlers/par.js +53 -0
- package/dist/esm/http/handlers/register.js +46 -0
- package/dist/esm/http/handlers/revocation.js +2 -2
- package/dist/esm/http/handlers/token.js +36 -6
- package/dist/esm/http/handlers/userinfo.js +35 -29
- package/dist/esm/http/middleware/tenant.js +6 -6
- package/dist/esm/http/router.js +10 -0
- package/dist/esm/http/schemas/authorize.js +8 -2
- package/dist/esm/http/schemas/end-session.js +13 -0
- package/dist/esm/http/schemas/par.js +9 -0
- package/dist/esm/http/schemas/register.js +16 -0
- package/dist/esm/http/schemas/revocation.js +2 -2
- package/dist/esm/http/schemas/token.js +7 -7
- package/dist/esm/index.js +40 -34
- package/dist/esm/methods/oauth2-factory.js +2 -2
- package/dist/esm/methods/password-hash.js +2 -2
- package/dist/esm/methods/providers/index.js +30 -14
- package/dist/esm/methods/saml-sp/acs.js +208 -0
- package/dist/esm/methods/saml-sp/attributes.js +75 -0
- package/dist/esm/methods/saml-sp/authnrequest.js +63 -0
- package/dist/esm/methods/saml-sp/cache-provider.js +33 -0
- package/dist/esm/methods/saml-sp/cert-rotation.js +13 -0
- package/dist/esm/methods/saml-sp/factory.js +70 -0
- package/dist/esm/methods/saml-sp/index.js +7 -0
- package/dist/esm/methods/saml-sp/metadata.js +69 -0
- package/dist/esm/methods/saml-sp/method.js +29 -0
- package/dist/esm/methods/saml-sp/parse-idp-metadata.js +102 -0
- package/dist/esm/methods/saml-sp/saml-instance.js +49 -0
- package/dist/esm/methods/saml-sp/slo-initiate.js +84 -0
- package/dist/esm/methods/saml-sp/sls.js +154 -0
- package/dist/esm/methods/saml-sp/types.js +0 -0
- package/dist/esm/types/error.js +5 -0
- package/dist/esm/types/result.js +3 -3
- package/dist/esm/ui/forms.js +3 -3
- package/dist/esm/ui/index.js +8 -5
- package/dist/esm/ui/picker.js +2 -2
- package/dist/types/adapters/d1/migrations.d.ts.map +1 -1
- package/dist/types/adapters/d1/session-store.d.ts +3 -0
- package/dist/types/adapters/d1/session-store.d.ts.map +1 -1
- package/dist/types/adapters/durable-object/session-store.d.ts +3 -0
- package/dist/types/adapters/durable-object/session-store.d.ts.map +1 -1
- package/dist/types/adapters/dynamo/session-store.d.ts +3 -0
- package/dist/types/adapters/dynamo/session-store.d.ts.map +1 -1
- package/dist/types/adapters/memory/session-store.d.ts +6 -1
- package/dist/types/adapters/memory/session-store.d.ts.map +1 -1
- package/dist/types/adapters/memory/token-store.d.ts +1 -0
- package/dist/types/adapters/memory/token-store.d.ts.map +1 -1
- package/dist/types/adapters/postgres/migrations.d.ts +1 -1
- package/dist/types/adapters/postgres/migrations.d.ts.map +1 -1
- package/dist/types/adapters/postgres/session-store.d.ts +3 -0
- package/dist/types/adapters/postgres/session-store.d.ts.map +1 -1
- package/dist/types/client.d.ts +19 -0
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/domain/authorize.d.ts.map +1 -1
- package/dist/types/domain/callback.d.ts +16 -1
- package/dist/types/domain/callback.d.ts.map +1 -1
- package/dist/types/domain/client-credentials.d.ts +2 -0
- package/dist/types/domain/client-credentials.d.ts.map +1 -1
- package/dist/types/domain/discovery.d.ts +37 -0
- package/dist/types/domain/discovery.d.ts.map +1 -1
- package/dist/types/domain/dpop.d.ts +61 -0
- package/dist/types/domain/dpop.d.ts.map +1 -0
- package/dist/types/domain/id-token.d.ts +83 -0
- package/dist/types/domain/id-token.d.ts.map +1 -0
- package/dist/types/domain/introspect.d.ts +8 -0
- package/dist/types/domain/introspect.d.ts.map +1 -1
- package/dist/types/domain/jwt.d.ts +27 -1
- package/dist/types/domain/jwt.d.ts.map +1 -1
- package/dist/types/domain/logout.d.ts +69 -0
- package/dist/types/domain/logout.d.ts.map +1 -0
- package/dist/types/domain/method-dispatch.d.ts.map +1 -1
- package/dist/types/domain/method-route.d.ts +57 -1
- package/dist/types/domain/method-route.d.ts.map +1 -1
- package/dist/types/domain/par.d.ts +62 -0
- package/dist/types/domain/par.d.ts.map +1 -0
- package/dist/types/domain/refresh.d.ts +9 -0
- package/dist/types/domain/refresh.d.ts.map +1 -1
- package/dist/types/domain/register.d.ts +33 -0
- package/dist/types/domain/register.d.ts.map +1 -0
- package/dist/types/domain/token-exchange.d.ts +2 -0
- package/dist/types/domain/token-exchange.d.ts.map +1 -1
- package/dist/types/domain/token.d.ts +41 -1
- package/dist/types/domain/token.d.ts.map +1 -1
- package/dist/types/domain/userinfo.d.ts +44 -5
- package/dist/types/domain/userinfo.d.ts.map +1 -1
- package/dist/types/http/context.d.ts +6 -1
- package/dist/types/http/context.d.ts.map +1 -1
- package/dist/types/http/cookies.d.ts +9 -1
- package/dist/types/http/cookies.d.ts.map +1 -1
- package/dist/types/http/handlers/authorize.d.ts.map +1 -1
- package/dist/types/http/handlers/callback.d.ts.map +1 -1
- package/dist/types/http/handlers/end-session.d.ts +3 -0
- package/dist/types/http/handlers/end-session.d.ts.map +1 -0
- package/dist/types/http/handlers/metadata.d.ts +1 -1
- package/dist/types/http/handlers/metadata.d.ts.map +1 -1
- package/dist/types/http/handlers/method-route.d.ts.map +1 -1
- package/dist/types/http/handlers/par.d.ts +3 -0
- package/dist/types/http/handlers/par.d.ts.map +1 -0
- package/dist/types/http/handlers/register.d.ts +3 -0
- package/dist/types/http/handlers/register.d.ts.map +1 -0
- package/dist/types/http/handlers/token.d.ts.map +1 -1
- package/dist/types/http/handlers/userinfo.d.ts.map +1 -1
- package/dist/types/http/router.d.ts.map +1 -1
- package/dist/types/http/schemas/authorize.d.ts +40 -0
- package/dist/types/http/schemas/authorize.d.ts.map +1 -1
- package/dist/types/http/schemas/end-session.d.ts +31 -0
- package/dist/types/http/schemas/end-session.d.ts.map +1 -0
- package/dist/types/http/schemas/par.d.ts +20 -0
- package/dist/types/http/schemas/par.d.ts.map +1 -0
- package/dist/types/http/schemas/register.d.ts +42 -0
- package/dist/types/http/schemas/register.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/methods/saml-sp/acs.d.ts +4 -0
- package/dist/types/methods/saml-sp/acs.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/attributes.d.ts +40 -0
- package/dist/types/methods/saml-sp/attributes.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/authnrequest.d.ts +4 -0
- package/dist/types/methods/saml-sp/authnrequest.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/cache-provider.d.ts +38 -0
- package/dist/types/methods/saml-sp/cache-provider.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/cert-rotation.d.ts +23 -0
- package/dist/types/methods/saml-sp/cert-rotation.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/factory.d.ts +704 -0
- package/dist/types/methods/saml-sp/factory.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/index.d.ts +24 -0
- package/dist/types/methods/saml-sp/index.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/metadata.d.ts +42 -0
- package/dist/types/methods/saml-sp/metadata.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/method.d.ts +17 -0
- package/dist/types/methods/saml-sp/method.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/parse-idp-metadata.d.ts +4 -0
- package/dist/types/methods/saml-sp/parse-idp-metadata.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/saml-instance.d.ts +81 -0
- package/dist/types/methods/saml-sp/saml-instance.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/slo-initiate.d.ts +4 -0
- package/dist/types/methods/saml-sp/slo-initiate.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/sls.d.ts +4 -0
- package/dist/types/methods/saml-sp/sls.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/types.d.ts +204 -0
- package/dist/types/methods/saml-sp/types.d.ts.map +1 -0
- package/dist/types/ports/audit-log.d.ts +40 -0
- package/dist/types/ports/audit-log.d.ts.map +1 -1
- package/dist/types/ports/session-store.d.ts +50 -0
- package/dist/types/ports/session-store.d.ts.map +1 -1
- package/dist/types/ports/token-store.d.ts +12 -0
- package/dist/types/ports/token-store.d.ts.map +1 -1
- package/dist/types/types/authorization.d.ts +23 -0
- package/dist/types/types/authorization.d.ts.map +1 -1
- package/dist/types/types/error.d.ts +9 -0
- package/dist/types/types/error.d.ts.map +1 -1
- package/dist/types/types/flow.d.ts +10 -0
- package/dist/types/types/flow.d.ts.map +1 -1
- package/dist/types/types/idp.d.ts +162 -1
- package/dist/types/types/idp.d.ts.map +1 -1
- package/dist/types/types/method.d.ts +109 -0
- package/dist/types/types/method.d.ts.map +1 -1
- package/dist/types/types/tenant.d.ts +37 -0
- package/dist/types/types/tenant.d.ts.map +1 -1
- package/dist/types/types/token.d.ts +128 -0
- package/dist/types/types/token.d.ts.map +1 -1
- package/package.json +9 -1
- package/src/adapters/d1/migrations.ts +5 -0
- package/src/adapters/d1/session-store.ts +68 -0
- package/src/adapters/durable-object/session-store.ts +61 -0
- package/src/adapters/dynamo/session-store.ts +63 -0
- package/src/adapters/memory/session-store.ts +83 -1
- package/src/adapters/memory/token-store.ts +16 -0
- package/src/adapters/postgres/migrations.ts +6 -0
- package/src/adapters/postgres/session-store.ts +69 -0
- package/src/client.ts +32 -0
- package/src/domain/authorize.ts +9 -0
- package/src/domain/callback.ts +220 -7
- package/src/domain/client-credentials.ts +2 -0
- package/src/domain/discovery.ts +80 -2
- package/src/domain/dpop.ts +293 -0
- package/src/domain/id-token.ts +213 -0
- package/src/domain/introspect.ts +10 -0
- package/src/domain/jwt.ts +80 -1
- package/src/domain/logout.ts +180 -0
- package/src/domain/method-dispatch.ts +53 -1
- package/src/domain/method-route.ts +195 -1
- package/src/domain/par.ts +141 -0
- package/src/domain/refresh.ts +41 -0
- package/src/domain/register.ts +168 -0
- package/src/domain/token-exchange.ts +11 -0
- package/src/domain/token.ts +134 -8
- package/src/domain/userinfo.ts +121 -6
- package/src/http/context.ts +10 -0
- package/src/http/cookies.ts +19 -1
- package/src/http/handlers/authorize.ts +146 -24
- package/src/http/handlers/callback.ts +9 -1
- package/src/http/handlers/end-session.ts +106 -0
- package/src/http/handlers/metadata.ts +7 -2
- package/src/http/handlers/method-route.ts +75 -2
- package/src/http/handlers/par.ts +84 -0
- package/src/http/handlers/register.ts +70 -0
- package/src/http/handlers/token.ts +56 -0
- package/src/http/handlers/userinfo.ts +62 -38
- package/src/http/router.ts +19 -0
- package/src/http/schemas/authorize.ts +27 -0
- package/src/http/schemas/end-session.ts +23 -0
- package/src/http/schemas/par.ts +23 -0
- package/src/http/schemas/register.ts +29 -0
- package/src/index.ts +21 -1
- package/src/methods/saml-sp/acs.ts +399 -0
- package/src/methods/saml-sp/attributes.ts +137 -0
- package/src/methods/saml-sp/authnrequest.ts +128 -0
- package/src/methods/saml-sp/cache-provider.ts +76 -0
- package/src/methods/saml-sp/cert-rotation.ts +34 -0
- package/src/methods/saml-sp/factory.ts +118 -0
- package/src/methods/saml-sp/index.ts +34 -0
- package/src/methods/saml-sp/metadata.ts +204 -0
- package/src/methods/saml-sp/method.ts +65 -0
- package/src/methods/saml-sp/parse-idp-metadata.ts +164 -0
- package/src/methods/saml-sp/saml-instance.ts +173 -0
- package/src/methods/saml-sp/slo-initiate.ts +177 -0
- package/src/methods/saml-sp/sls.ts +306 -0
- package/src/methods/saml-sp/types.ts +203 -0
- package/src/ports/CONSISTENCY.md +77 -21
- package/src/ports/audit-log.ts +42 -0
- package/src/ports/session-store.ts +61 -0
- package/src/ports/token-store.ts +13 -0
- package/src/types/authorization.ts +25 -0
- package/src/types/error.ts +23 -0
- package/src/types/flow.ts +10 -0
- package/src/types/idp.ts +170 -0
- package/src/types/method.ts +110 -0
- package/src/types/tenant.ts +37 -0
- package/src/types/token.ts +135 -0
package/src/ports/audit-log.ts
CHANGED
|
@@ -54,6 +54,10 @@ export type AuditEvent =
|
|
|
54
54
|
subjectId: string
|
|
55
55
|
/** Hash of the issued refresh token id — never the token itself. */
|
|
56
56
|
refreshTokenIdHash?: string
|
|
57
|
+
/** OIDC Core §2 — set when the response carried an `id_token`. */
|
|
58
|
+
idTokenIssued?: boolean
|
|
59
|
+
/** RFC 9449 — set when the access token is DPoP-bound. */
|
|
60
|
+
dpopBound?: boolean
|
|
57
61
|
}
|
|
58
62
|
| {
|
|
59
63
|
kind: "token_refreshed"
|
|
@@ -87,6 +91,44 @@ export type AuditEvent =
|
|
|
87
91
|
clientId: string
|
|
88
92
|
family: string
|
|
89
93
|
}
|
|
94
|
+
| {
|
|
95
|
+
/**
|
|
96
|
+
* Emitted after a logout is processed. Two channels, discriminated
|
|
97
|
+
* by `via`:
|
|
98
|
+
*
|
|
99
|
+
* - `rp_initiated` (or absent — the default): `/end_session`
|
|
100
|
+
* (OIDC RP-Initiated Logout 1.0 §2), regardless of whether a
|
|
101
|
+
* `post_logout_redirect_uri` was supplied. `subjectId` present
|
|
102
|
+
* when an `id_token_hint` verified; absent otherwise.
|
|
103
|
+
* - `upstream_slo`: an upstream IdP notified us a federated
|
|
104
|
+
* session ended (SAML front-channel Single Logout). `methodId`
|
|
105
|
+
* / `methodKind` identify the federation connection;
|
|
106
|
+
* `subjectId` present only when the host's `onLogout` returned
|
|
107
|
+
* a subject to revoke.
|
|
108
|
+
*
|
|
109
|
+
* `via` is general (OIDC back-channel logout would reuse
|
|
110
|
+
* `upstream_slo`), not SAML-specific surface.
|
|
111
|
+
*/
|
|
112
|
+
kind: "session_logout"
|
|
113
|
+
tenantId: TenantId
|
|
114
|
+
clientId?: string
|
|
115
|
+
subjectId?: string
|
|
116
|
+
via?: "rp_initiated" | "upstream_slo"
|
|
117
|
+
methodId?: string
|
|
118
|
+
methodKind?: string
|
|
119
|
+
}
|
|
120
|
+
| {
|
|
121
|
+
/**
|
|
122
|
+
* Emitted when a DPoP proof's `jti` is presented within the replay
|
|
123
|
+
* window (RFC 9449 §11.1). The request was rejected with
|
|
124
|
+
* `invalid_dpop_proof`. Operators / SIEM use this to spot
|
|
125
|
+
* stolen-key replay attempts.
|
|
126
|
+
*/
|
|
127
|
+
kind: "dpop_replay_detected"
|
|
128
|
+
tenantId: TenantId | null
|
|
129
|
+
/** First-half of the offending jti so logs can correlate without storing it. */
|
|
130
|
+
jtiPrefix: string
|
|
131
|
+
}
|
|
90
132
|
| {
|
|
91
133
|
kind: "flow_replay_attempt"
|
|
92
134
|
tenantId: TenantId | null
|
|
@@ -83,6 +83,67 @@ export type SessionStore = {
|
|
|
83
83
|
): Promise<Result<void>>
|
|
84
84
|
readSession?(sessionId: string): Promise<Result<SessionRecord>>
|
|
85
85
|
revokeSession?(sessionId: string): Promise<Result<void>>
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Optional: Pushed Authorization Request (RFC 9126) storage. Persists the
|
|
89
|
+
* pre-parsed `/authorize` parameters under an opaque `request_uri` for
|
|
90
|
+
* one-shot retrieval at `/authorize` time. Strong consistency + atomic
|
|
91
|
+
* delete-on-read — same semantics as `saveFlow` / `consumeFlow`.
|
|
92
|
+
*
|
|
93
|
+
* Adapters without these methods cannot satisfy `/par`; the framework's
|
|
94
|
+
* `/par` handler returns `invalid_request` when called against such a
|
|
95
|
+
* store. Implement both methods together (the framework only exposes
|
|
96
|
+
* the endpoint when both are present).
|
|
97
|
+
*/
|
|
98
|
+
savePar?(
|
|
99
|
+
requestUri: string,
|
|
100
|
+
payload: ParRecord,
|
|
101
|
+
ttl: number,
|
|
102
|
+
): Promise<Result<void>>
|
|
103
|
+
consumePar?(requestUri: string): Promise<Result<ParRecord>>
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Optional: per-method-instance scratch storage. Survives across flows
|
|
107
|
+
* (unlike `methodState`, which is per-flow). Use cases include cross-flow
|
|
108
|
+
* deduplication state — e.g., a SAML SP method remembering recently-seen
|
|
109
|
+
* assertion IDs for replay protection.
|
|
110
|
+
*
|
|
111
|
+
* The framework scopes keys per `(tenantId, methodId)` before calling
|
|
112
|
+
* these — adapters see opaque, already-namespaced keys and store the
|
|
113
|
+
* UTF-8 string value verbatim. Methods JSON-encode if they want object
|
|
114
|
+
* state.
|
|
115
|
+
*
|
|
116
|
+
* Strong consistency + TTL respect (same semantics as flow records).
|
|
117
|
+
* `readScratch` returns `unknown_state` if the key is missing or
|
|
118
|
+
* expired. `deleteScratch` is idempotent.
|
|
119
|
+
*
|
|
120
|
+
* Adapters without these methods cannot host methods that depend on
|
|
121
|
+
* scratch; the framework surfaces a clear `unsupported` error through
|
|
122
|
+
* `MethodContext.methodScratch` at call time. Implement all three
|
|
123
|
+
* methods together — partial implementations are not supported.
|
|
124
|
+
*/
|
|
125
|
+
saveScratch?(
|
|
126
|
+
key: string,
|
|
127
|
+
value: string,
|
|
128
|
+
ttlMs: number,
|
|
129
|
+
): Promise<Result<void>>
|
|
130
|
+
readScratch?(key: string): Promise<Result<string>>
|
|
131
|
+
deleteScratch?(key: string): Promise<Result<void>>
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Stored PAR payload. The `params` blob is the raw form/query record from
|
|
136
|
+
* `POST /par`, kept verbatim so the `/authorize` rehydrate path can feed
|
|
137
|
+
* it through the same Zod parser the direct path uses.
|
|
138
|
+
*/
|
|
139
|
+
export type ParRecord = {
|
|
140
|
+
requestUri: string
|
|
141
|
+
/** Raw key/value record as posted to `/par` (excluding auth fields). */
|
|
142
|
+
params: Record<string, string>
|
|
143
|
+
clientId: string
|
|
144
|
+
/** Wall-clock issuance + absolute expiry (ms). */
|
|
145
|
+
issuedAt: number
|
|
146
|
+
expiresAt: number
|
|
86
147
|
}
|
|
87
148
|
|
|
88
149
|
/** Optional long-lived session payload (used by `createSession` family). */
|
package/src/ports/token-store.ts
CHANGED
|
@@ -100,4 +100,17 @@ export type TokenStore = {
|
|
|
100
100
|
* preferred; documented eventual lag acceptable.
|
|
101
101
|
*/
|
|
102
102
|
revokeBySubject(tenantId: TenantId, subjectId: string): Promise<Result<void>>
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Optional: DPoP proof replay protection (RFC 9449 §11.1). Atomically
|
|
106
|
+
* record the proof's `jti` and reject if it's been seen within the TTL
|
|
107
|
+
* window. Returns `invalid_grant` (with `reuseSignal` omitted) when a
|
|
108
|
+
* `jti` is already present.
|
|
109
|
+
*
|
|
110
|
+
* Adapters without this method cannot satisfy DPoP at the token /
|
|
111
|
+
* userinfo endpoints; the framework's DPoP verifier returns
|
|
112
|
+
* `invalid_dpop_proof` when called against such a store. Memory adapter
|
|
113
|
+
* implements; production adapters follow in subsequent commits.
|
|
114
|
+
*/
|
|
115
|
+
recordDpopJti?(jti: string, ttlMs: number): Promise<Result<void>>
|
|
103
116
|
}
|
|
@@ -37,6 +37,31 @@ export type AuthorizationRequest = {
|
|
|
37
37
|
uiLocales?: string[]
|
|
38
38
|
/** Standard OIDC. */
|
|
39
39
|
nonce?: string
|
|
40
|
+
/**
|
|
41
|
+
* OIDC Core §5.5 — RP-requested claims, parsed from the `claims`
|
|
42
|
+
* parameter's JSON value. The library currently honors claim **names**
|
|
43
|
+
* (additive, bypasses scope gating) but does not enforce the
|
|
44
|
+
* `essential` / `value` / `values` qualifiers.
|
|
45
|
+
*/
|
|
46
|
+
claimsRequest?: ClaimsRequest
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* OIDC Core §5.5 `claims` parameter. Each section maps claim name to an
|
|
51
|
+
* optional qualifier object (`{essential, value, values}`) or `null`
|
|
52
|
+
* meaning "just request the claim without qualifiers".
|
|
53
|
+
*/
|
|
54
|
+
export type ClaimsRequest = {
|
|
55
|
+
/** Claims to include in the `/userinfo` response. */
|
|
56
|
+
userinfo?: Record<string, ClaimRequestEntry | null>
|
|
57
|
+
/** Claims to include in the `id_token`. */
|
|
58
|
+
id_token?: Record<string, ClaimRequestEntry | null>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type ClaimRequestEntry = {
|
|
62
|
+
essential?: boolean
|
|
63
|
+
value?: unknown
|
|
64
|
+
values?: unknown[]
|
|
40
65
|
}
|
|
41
66
|
|
|
42
67
|
/**
|
package/src/types/error.ts
CHANGED
|
@@ -50,6 +50,21 @@ export type AuthError =
|
|
|
50
50
|
// endpoints. Use for control-flow signalling that should never escape to a
|
|
51
51
|
// standards-compliant client.
|
|
52
52
|
| { code: "internal_error"; description: string; cause?: unknown }
|
|
53
|
+
// RFC 9449 §5.2 — DPoP proof verification failed (bad signature, htm/htu
|
|
54
|
+
// mismatch, iat outside window, replayed jti, missing/mismatched cnf.jkt).
|
|
55
|
+
// Returned as a 400 with `error="invalid_dpop_proof"` on form-body
|
|
56
|
+
// endpoints; on resource-server endpoints it becomes a 401 with
|
|
57
|
+
// `WWW-Authenticate: DPoP error="invalid_dpop_proof"`.
|
|
58
|
+
//
|
|
59
|
+
// `replaySignal`, when present, indicates `recordDpopJti` reported a
|
|
60
|
+
// jti already seen within the replay window. The HTTP layer uses this
|
|
61
|
+
// to emit a `dpop_replay_detected` audit event distinct from other
|
|
62
|
+
// proof failures.
|
|
63
|
+
| {
|
|
64
|
+
code: "invalid_dpop_proof"
|
|
65
|
+
description: string
|
|
66
|
+
replaySignal?: { jti: string }
|
|
67
|
+
}
|
|
53
68
|
|
|
54
69
|
export type AuthErrorCode = AuthError["code"]
|
|
55
70
|
|
|
@@ -120,4 +135,12 @@ export const authError = {
|
|
|
120
135
|
description,
|
|
121
136
|
...(cause !== undefined ? { cause } : {}),
|
|
122
137
|
}),
|
|
138
|
+
invalidDpopProof: (
|
|
139
|
+
description: string,
|
|
140
|
+
replaySignal?: { jti: string },
|
|
141
|
+
): AuthError => ({
|
|
142
|
+
code: "invalid_dpop_proof",
|
|
143
|
+
description,
|
|
144
|
+
...(replaySignal !== undefined ? { replaySignal } : {}),
|
|
145
|
+
}),
|
|
123
146
|
}
|
package/src/types/flow.ts
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* needed at `/token` into the auth-code payload; `methodState` is
|
|
20
20
|
* dropped.
|
|
21
21
|
*/
|
|
22
|
+
import type { ClaimsRequest } from "./authorization"
|
|
22
23
|
import type { TenantId } from "./tenant"
|
|
23
24
|
|
|
24
25
|
export type FlowRecord = {
|
|
@@ -56,6 +57,15 @@ export type FlowRecord = {
|
|
|
56
57
|
* `appState`. Compared at callback against the consumed record.
|
|
57
58
|
*/
|
|
58
59
|
nonce: string
|
|
60
|
+
/**
|
|
61
|
+
* Relying party's OIDC `nonce` parameter (OIDC Core §3.1.2.1). Distinct
|
|
62
|
+
* from `nonce` (which is the framework's CSRF nonce for state-MAC
|
|
63
|
+
* binding). When present, must be echoed in the issued `id_token`
|
|
64
|
+
* (OIDC Core §2). Snapshotted into `CodePayload.appNonce` at success.
|
|
65
|
+
*/
|
|
66
|
+
appNonce?: string
|
|
67
|
+
/** OIDC Core §5.5 — RP-requested claims, parsed at `/authorize`. */
|
|
68
|
+
claimsRequest?: ClaimsRequest
|
|
59
69
|
/**
|
|
60
70
|
* Relying-party → IdP PKCE. The RP generates the verifier and sends the
|
|
61
71
|
* challenge to `/authorize`. Verified at `/token`.
|
package/src/types/idp.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type { AnyAuthMethodFactory } from "./method"
|
|
|
15
15
|
import type { Result } from "./result"
|
|
16
16
|
import type { SubjectClaim, SubjectSchema } from "./subject"
|
|
17
17
|
import type {
|
|
18
|
+
ClientConfig,
|
|
18
19
|
StateKeyRing,
|
|
19
20
|
TenantContext,
|
|
20
21
|
TenantId,
|
|
@@ -68,6 +69,56 @@ export type FailureEvent = {
|
|
|
68
69
|
error: AuthError
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Input to the optional `IdPOptions.onLogout` hook.
|
|
74
|
+
*
|
|
75
|
+
* Fires when an upstream provider notifies this IdP that a federated
|
|
76
|
+
* session has ended — today, a SAML front-channel `LogoutRequest`
|
|
77
|
+
* delivered to the SP's SLS endpoint. By the time this runs the library
|
|
78
|
+
* has already cryptographically verified the upstream logout message
|
|
79
|
+
* (XML-DSig via the SAML method), so the hook is purely the host's
|
|
80
|
+
* teardown point.
|
|
81
|
+
*
|
|
82
|
+
* The library deliberately does **not** know which OIDC `subject` an
|
|
83
|
+
* upstream identifier (`nameId`) maps to — that mapping lives in the
|
|
84
|
+
* host's `success` callback, not the library. So the host clears its
|
|
85
|
+
* own session and returns the subject (if any) whose library-issued
|
|
86
|
+
* tokens should be revoked; the library then runs the same
|
|
87
|
+
* `revokeAllForSubject` primitive `/end_session` uses. Returning
|
|
88
|
+
* nothing skips library-side revocation (the host handled everything,
|
|
89
|
+
* or there is nothing to revoke).
|
|
90
|
+
*
|
|
91
|
+
* Method-agnostic on purpose: any federation method that can verify an
|
|
92
|
+
* upstream logout signal reuses this. SAML SLO is the first caller;
|
|
93
|
+
* OIDC back-channel logout would be the next.
|
|
94
|
+
*/
|
|
95
|
+
export type LogoutEventInput = {
|
|
96
|
+
tenant: TenantContext
|
|
97
|
+
methodId: string
|
|
98
|
+
methodKind: string
|
|
99
|
+
/** What kind of upstream logout this is. Extensible discriminant. */
|
|
100
|
+
reason: "upstream_slo"
|
|
101
|
+
/**
|
|
102
|
+
* Upstream subject identifier from the verified logout message
|
|
103
|
+
* (SAML `LogoutRequest/NameID`). Absent if the message omitted it.
|
|
104
|
+
*/
|
|
105
|
+
nameId?: string
|
|
106
|
+
/**
|
|
107
|
+
* Upstream session index from the verified logout message
|
|
108
|
+
* (SAML `SessionIndex`), when present. Lets a host that tracks
|
|
109
|
+
* per-session state scope its teardown.
|
|
110
|
+
*/
|
|
111
|
+
sessionIndex?: string
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Return of `IdPOptions.onLogout`. `revokeSubject` names the OIDC
|
|
116
|
+
* subject whose library-issued refresh tokens the library should
|
|
117
|
+
* revoke (the host resolves it from `nameId` — only the host has that
|
|
118
|
+
* map). Omit / return nothing to skip library-side revocation.
|
|
119
|
+
*/
|
|
120
|
+
export type LogoutHookResult = { revokeSubject?: string } | void
|
|
121
|
+
|
|
71
122
|
/**
|
|
72
123
|
* Optional hook called at `/token` time, after PKCE has succeeded and
|
|
73
124
|
* after the `success` callback has produced a `SubjectClaim`, but
|
|
@@ -119,6 +170,69 @@ export type ExchangeAudience = (
|
|
|
119
170
|
},
|
|
120
171
|
) => Promise<SubjectClaim | AuthError>
|
|
121
172
|
|
|
173
|
+
/**
|
|
174
|
+
* RFC 7591 Dynamic Client Registration request body, normalized to the
|
|
175
|
+
* library's type system. The framework's `/register` handler parses the
|
|
176
|
+
* raw JSON, validates structure, then calls the host's `registerClient`
|
|
177
|
+
* hook with this shape. The host owns persistence — writing through its
|
|
178
|
+
* own `ConfigStore` — and returns the final `ClientConfig`.
|
|
179
|
+
*/
|
|
180
|
+
export type RegisterClientRequest = {
|
|
181
|
+
client_name?: string
|
|
182
|
+
redirect_uris: string[]
|
|
183
|
+
/** RFC 7591 §2 — defaults to `["authorization_code"]`. */
|
|
184
|
+
grant_types?: string[]
|
|
185
|
+
/** RFC 7591 §2 — defaults to `["code"]`. */
|
|
186
|
+
response_types?: string[]
|
|
187
|
+
/** RFC 7591 §2 — `"none"` = public client, otherwise confidential. */
|
|
188
|
+
token_endpoint_auth_method?:
|
|
189
|
+
| "none"
|
|
190
|
+
| "client_secret_basic"
|
|
191
|
+
| "client_secret_post"
|
|
192
|
+
scope?: string
|
|
193
|
+
/** OIDC RP-Initiated Logout 1.0 §2. */
|
|
194
|
+
post_logout_redirect_uris?: string[]
|
|
195
|
+
/** OIDC Core §8.1. */
|
|
196
|
+
sector_identifier_uri?: string
|
|
197
|
+
/** Free-form metadata the host may interpret. */
|
|
198
|
+
contacts?: string[]
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* RFC 7591 §3.2.1 response body. Returned verbatim from `/register` when
|
|
203
|
+
* the host hook produces a `ClientConfig`. `client_secret` is included
|
|
204
|
+
* only for confidential clients; public clients omit it.
|
|
205
|
+
*/
|
|
206
|
+
export type RegisterClientResponse = {
|
|
207
|
+
client_id: string
|
|
208
|
+
client_secret?: string
|
|
209
|
+
client_id_issued_at: number
|
|
210
|
+
client_secret_expires_at?: number
|
|
211
|
+
redirect_uris: string[]
|
|
212
|
+
grant_types?: string[]
|
|
213
|
+
response_types?: string[]
|
|
214
|
+
token_endpoint_auth_method?: string
|
|
215
|
+
client_name?: string
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Optional Dynamic Client Registration hook. Hosts that want to expose
|
|
220
|
+
* RFC 7591 client provisioning supply this; the framework validates the
|
|
221
|
+
* wire format, then defers persistence to the host. If absent, the
|
|
222
|
+
* `/register` endpoint returns `invalid_request` so RPs receive a clear
|
|
223
|
+
* "not enabled" signal rather than a 404.
|
|
224
|
+
*
|
|
225
|
+
* The hook receives the parsed request, the resolved tenant, and the
|
|
226
|
+
* plaintext client secret (if any) the framework minted — hosts hash it
|
|
227
|
+
* with `hashClientSecret` before storing on `ClientConfig.secretHash`,
|
|
228
|
+
* then return the final `ClientConfig` along with the secret in the
|
|
229
|
+
* `RegisterClientResponse` so the RP can record it.
|
|
230
|
+
*/
|
|
231
|
+
export type RegisterClient = (input: {
|
|
232
|
+
tenant: TenantContext
|
|
233
|
+
request: RegisterClientRequest
|
|
234
|
+
}) => Promise<Result<{ client: ClientConfig; secret?: string }, AuthError>>
|
|
235
|
+
|
|
122
236
|
/**
|
|
123
237
|
* Optional override for the default provider picker shown when an
|
|
124
238
|
* `/authorize` request has multiple enabled methods and no `method_id`.
|
|
@@ -195,6 +309,30 @@ export type IdPOptions = {
|
|
|
195
309
|
onFailure?: (event: FailureEvent) => Promise<void>
|
|
196
310
|
}
|
|
197
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Optional hook fired when an upstream provider signals that a
|
|
314
|
+
* federated session ended — SAML front-channel Single Logout today.
|
|
315
|
+
* The library has already verified the signed logout message; this
|
|
316
|
+
* hook is where the host tears down its own session and names the
|
|
317
|
+
* OIDC subject (if any) whose library-issued tokens to revoke. See
|
|
318
|
+
* the `LogoutEventInput` / `LogoutHookResult` type docs.
|
|
319
|
+
*
|
|
320
|
+
* Unlike `hooks.onSuccess`/`onFailure` (observation only) this hook
|
|
321
|
+
* **influences** library behaviour — its return drives token
|
|
322
|
+
* revocation — so it sits at the top level alongside `success`.
|
|
323
|
+
*
|
|
324
|
+
* Absent ⇒ the library still verifies the logout, emits a
|
|
325
|
+
* `session_logout` audit event, and returns the protocol
|
|
326
|
+
* `LogoutResponse`, but performs no token revocation (it cannot map
|
|
327
|
+
* the upstream id to a subject without the host).
|
|
328
|
+
*
|
|
329
|
+
* If it throws, the SLS endpoint fails closed with an internal error
|
|
330
|
+
* rather than acknowledging a logout it could not fully process.
|
|
331
|
+
*/
|
|
332
|
+
onLogout?: (
|
|
333
|
+
input: LogoutEventInput,
|
|
334
|
+
) => Promise<LogoutHookResult> | LogoutHookResult
|
|
335
|
+
|
|
198
336
|
/**
|
|
199
337
|
* Optional escape hatch for high-sensitivity deployments — see the
|
|
200
338
|
* `PersistUpstreamTokens` type doc.
|
|
@@ -213,6 +351,13 @@ export type IdPOptions = {
|
|
|
213
351
|
*/
|
|
214
352
|
renderPicker?: RenderPicker
|
|
215
353
|
|
|
354
|
+
/**
|
|
355
|
+
* Optional RFC 7591 Dynamic Client Registration hook. See
|
|
356
|
+
* `RegisterClient` for the contract. When absent, `/register` rejects
|
|
357
|
+
* with `invalid_request: "dynamic client registration is not enabled"`.
|
|
358
|
+
*/
|
|
359
|
+
registerClient?: RegisterClient
|
|
360
|
+
|
|
216
361
|
/**
|
|
217
362
|
* Optional hook that builds the `TenantContext.request.custom` blob for
|
|
218
363
|
* each request the framework processes (the initial `/authorize`, the
|
|
@@ -234,6 +379,25 @@ export type IdPOptions = {
|
|
|
234
379
|
req: Request,
|
|
235
380
|
) => Record<string, unknown> | Promise<Record<string, unknown>>
|
|
236
381
|
|
|
382
|
+
/**
|
|
383
|
+
* Optional vendor scope → claim-names map. Merged on top of OIDC Core
|
|
384
|
+
* §5.4 when building the id_token + `/userinfo` response. Lets a host
|
|
385
|
+
* expose its own identity vocabulary (`tenant_id`, `org_role`, etc.)
|
|
386
|
+
* via custom scope names. The standard §5.4 mapping always wins on
|
|
387
|
+
* collision, so an entry for `email` is silently ignored.
|
|
388
|
+
*
|
|
389
|
+
* Per-client scope allowlist still applies — a client must list a
|
|
390
|
+
* custom scope in `ClientConfig.scopes` to be allowed to request it.
|
|
391
|
+
*
|
|
392
|
+
* ```ts
|
|
393
|
+
* customScopeClaims: {
|
|
394
|
+
* tenant: ["tenant_id", "tenant_role", "tenant_roles"],
|
|
395
|
+
* org: ["organization_id", "org_role"],
|
|
396
|
+
* }
|
|
397
|
+
* ```
|
|
398
|
+
*/
|
|
399
|
+
customScopeClaims?: Record<string, ReadonlyArray<string>>
|
|
400
|
+
|
|
237
401
|
/**
|
|
238
402
|
* Optional override for the framework's `Set-Cookie` defaults.
|
|
239
403
|
*
|
|
@@ -266,4 +430,10 @@ export type IdP = {
|
|
|
266
430
|
discovery: (req: Request) => Promise<Response>
|
|
267
431
|
revoke: (req: Request) => Promise<Response>
|
|
268
432
|
introspect: (req: Request) => Promise<Response>
|
|
433
|
+
/** OIDC RP-Initiated Logout 1.0. */
|
|
434
|
+
endSession: (req: Request) => Promise<Response>
|
|
435
|
+
/** RFC 9126 Pushed Authorization Requests. */
|
|
436
|
+
par: (req: Request) => Promise<Response>
|
|
437
|
+
/** RFC 7591 Dynamic Client Registration. */
|
|
438
|
+
register: (req: Request) => Promise<Response>
|
|
269
439
|
}
|
package/src/types/method.ts
CHANGED
|
@@ -56,6 +56,37 @@ export type AuthMethod<P = unknown, S = unknown> = {
|
|
|
56
56
|
* same factory get distinct URL spaces).
|
|
57
57
|
*/
|
|
58
58
|
routes: Record<string, MethodHandler<P, S>>
|
|
59
|
+
/**
|
|
60
|
+
* Opt-in allowlist of route keys (same `"GET /metadata"` string form
|
|
61
|
+
* as `routes` keys) that the framework dispatches **without** a flow
|
|
62
|
+
* cookie or flow record — anonymous, unauthenticated GETs. The sole
|
|
63
|
+
* intended use is publishing static, per-instance descriptive
|
|
64
|
+
* documents (SAML SP metadata XML). The handler receives
|
|
65
|
+
* `ctx.flow === null` and `ctx.methodState === null` and MUST be a
|
|
66
|
+
* pure function of `ctx.tenant` + `ctx.dispatch` + its captured
|
|
67
|
+
* config — it must not assume an authenticated principal.
|
|
68
|
+
*
|
|
69
|
+
* Absent (the default for every method) ⇒ behaviour is unchanged:
|
|
70
|
+
* every `/m/<id>/*` request requires the `idp.flow` cookie. The
|
|
71
|
+
* cookie gate is skipped *only* for a route key a method explicitly
|
|
72
|
+
* lists here — fail-closed by construction.
|
|
73
|
+
*/
|
|
74
|
+
publicRoutes?: ReadonlyArray<string>
|
|
75
|
+
/**
|
|
76
|
+
* Opt-in: this method instance handles **unsolicited** upstream
|
|
77
|
+
* callbacks — a `POST /cb/<methodId>` carrying a provider assertion
|
|
78
|
+
* with no framework state envelope and no prior flow (SAML
|
|
79
|
+
* IdP-initiated SSO). When `true`, the framework, instead of
|
|
80
|
+
* rejecting a stateless callback, dispatches `GET /callback` with
|
|
81
|
+
* `flow === null` and a derived `dispatch` (issuer/ACS); the handler
|
|
82
|
+
* verifies the assertion and returns `success` **with
|
|
83
|
+
* `unsolicitedBinding`**. Absent (every method's default) ⇒ a
|
|
84
|
+
* stateless callback stays an `invalid_request` (the conservative
|
|
85
|
+
* default — many deployments do not want IdP-initiated). General
|
|
86
|
+
* capability, not SAML-specific; set per instance (a SAML instance
|
|
87
|
+
* sets it only when its config enables IdP-initiated).
|
|
88
|
+
*/
|
|
89
|
+
unsolicitedCallback?: boolean
|
|
59
90
|
/**
|
|
60
91
|
* Token-exchange function for the `/token` endpoint when the method
|
|
61
92
|
* participates in client-credentials-style flows (e.g. `m2m`). Most
|
|
@@ -102,6 +133,47 @@ export type MethodContext<S = unknown> = {
|
|
|
102
133
|
* and the relevant data is on `flow`).
|
|
103
134
|
*/
|
|
104
135
|
dispatch: MethodDispatchData | null
|
|
136
|
+
/**
|
|
137
|
+
* Per-method-instance scratch storage scoped to
|
|
138
|
+
* `(tenant.id, method.id)`. Survives across flows — distinct from
|
|
139
|
+
* `methodState`, which is per-flow.
|
|
140
|
+
*
|
|
141
|
+
* Most methods do NOT need this. It exists for cross-flow
|
|
142
|
+
* deduplication patterns such as SAML SP assertion-ID replay
|
|
143
|
+
* protection.
|
|
144
|
+
*
|
|
145
|
+
* Backed by `SessionStore.{saveScratch,readScratch,deleteScratch}`
|
|
146
|
+
* when those optional methods are implemented. Against adapters that
|
|
147
|
+
* don't implement them, every call returns
|
|
148
|
+
* `{ ok: false, error: unsupported }` — the method should surface a
|
|
149
|
+
* `MethodResult.error` with a clear message, not silently degrade.
|
|
150
|
+
*/
|
|
151
|
+
methodScratch: MethodScratch
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Caller-facing API for per-method-instance scratch. The framework
|
|
156
|
+
* scopes user-supplied keys with a `(tenantId, methodId)` prefix before
|
|
157
|
+
* delegating to `SessionStore` — adapters never see raw method keys.
|
|
158
|
+
*
|
|
159
|
+
* Values are UTF-8 strings; methods JSON-encode if they want to stash
|
|
160
|
+
* objects. Keeping the port-level type narrow simplifies adapter
|
|
161
|
+
* implementations (one TEXT column, one Dynamo `S` attribute, etc.).
|
|
162
|
+
*/
|
|
163
|
+
export type MethodScratch = {
|
|
164
|
+
/**
|
|
165
|
+
* Persist `value` under `key` with the given TTL. Overwrites prior
|
|
166
|
+
* value for the same key. `ttlMs` must be positive.
|
|
167
|
+
*/
|
|
168
|
+
put(key: string, value: string, ttlMs: number): Promise<Result<void>>
|
|
169
|
+
/**
|
|
170
|
+
* Read the value previously stored at `key`. Returns `unknown_state`
|
|
171
|
+
* if the key is missing or expired (the underlying adapter MAY
|
|
172
|
+
* lazily evict expired entries on read).
|
|
173
|
+
*/
|
|
174
|
+
get(key: string): Promise<Result<string>>
|
|
175
|
+
/** Idempotent. Resolves `ok` whether the key existed or not. */
|
|
176
|
+
delete(key: string): Promise<Result<void>>
|
|
105
177
|
}
|
|
106
178
|
|
|
107
179
|
/** Framework-supplied data available to the method at `/authorize` time. */
|
|
@@ -143,6 +215,27 @@ export type MethodResult<P = unknown, S = unknown> =
|
|
|
143
215
|
saveMethodState?: S
|
|
144
216
|
/** Serialized into a `Cache-Control` header by the framework. */
|
|
145
217
|
cache?: CachePolicy
|
|
218
|
+
/**
|
|
219
|
+
* Verified upstream-logout notification. Set **only** by a
|
|
220
|
+
* flowless **public** logout route (e.g. SAML front-channel SLS)
|
|
221
|
+
* once it has cryptographically verified the inbound logout
|
|
222
|
+
* message. The method stays port-free — it proves authenticity
|
|
223
|
+
* and builds the protocol response (`response` = the signed
|
|
224
|
+
* `LogoutResponse` redirect / ack); the privileged side effect
|
|
225
|
+
* runs in the framework.
|
|
226
|
+
*
|
|
227
|
+
* When present on a public route the framework fires
|
|
228
|
+
* `IdPOptions.onLogout` and, if that returns `{ revokeSubject }`,
|
|
229
|
+
* runs `revokeAllForSubject` — then returns `response`. Ignored
|
|
230
|
+
* on non-public (flow-bearing) routes: an authenticated method
|
|
231
|
+
* route never logs anyone out. Mirrors the Phase 2 V′ pattern
|
|
232
|
+
* (`success.unsolicitedBinding`): an optional field on an
|
|
233
|
+
* existing variant, not a new `MethodResult` kind.
|
|
234
|
+
*/
|
|
235
|
+
logout?: {
|
|
236
|
+
nameId?: string
|
|
237
|
+
sessionIndex?: string
|
|
238
|
+
}
|
|
146
239
|
}
|
|
147
240
|
/**
|
|
148
241
|
* Authentication succeeded. The HTTP layer hands `providerSubject` +
|
|
@@ -155,6 +248,23 @@ export type MethodResult<P = unknown, S = unknown> =
|
|
|
155
248
|
providerSubject: string
|
|
156
249
|
properties: P
|
|
157
250
|
setCookies?: SetCookie[]
|
|
251
|
+
/**
|
|
252
|
+
* RP binding for a **flowless** (unsolicited / IdP-initiated)
|
|
253
|
+
* authentication. Consulted **only** when the method ran with no
|
|
254
|
+
* `flow` (e.g. an unsolicited SAML Response — see
|
|
255
|
+
* `AuthMethod.unsolicitedCallback`). On the normal SP-initiated
|
|
256
|
+
* path a `flow` exists and this is ignored; on the flowless path
|
|
257
|
+
* it is **required** (the framework has no pending RP request to
|
|
258
|
+
* read `client_id` / `redirect_uri` / `scope` from, so the method
|
|
259
|
+
* supplies the operator-configured defaults). The framework still
|
|
260
|
+
* validates `clientId` / `redirectUri` against the tenant's
|
|
261
|
+
* registered client before issuing a code.
|
|
262
|
+
*/
|
|
263
|
+
unsolicitedBinding?: {
|
|
264
|
+
clientId: string
|
|
265
|
+
redirectUri: string
|
|
266
|
+
scopes: string[]
|
|
267
|
+
}
|
|
158
268
|
}
|
|
159
269
|
/** User refused / failed auth in a non-error way (e.g. consent declined). */
|
|
160
270
|
| { kind: "denied"; reason: string; setCookies?: SetCookie[] }
|
package/src/types/tenant.ts
CHANGED
|
@@ -128,6 +128,26 @@ export type PublicClientConfig = {
|
|
|
128
128
|
scopes: string[]
|
|
129
129
|
/** Must be `true` for public clients per OAuth 2.1 §2.1.1. */
|
|
130
130
|
pkceRequired: true
|
|
131
|
+
/**
|
|
132
|
+
* Registered URIs to which `/end_session` may redirect after RP-initiated
|
|
133
|
+
* logout (OIDC RP-Initiated Logout 1.0 §2). Exact-match. If absent, the
|
|
134
|
+
* `/end_session` endpoint refuses any `post_logout_redirect_uri`.
|
|
135
|
+
*/
|
|
136
|
+
postLogoutRedirectUris?: string[]
|
|
137
|
+
/**
|
|
138
|
+
* RFC 9126 §2: when `true`, the client MUST use Pushed Authorization
|
|
139
|
+
* Requests; a direct `GET /authorize` call without `request_uri` is
|
|
140
|
+
* rejected with `invalid_request`.
|
|
141
|
+
*/
|
|
142
|
+
requirePushedAuthorizationRequests?: boolean
|
|
143
|
+
/**
|
|
144
|
+
* OIDC Core §8.1 — when set, the subject identifier (`sub`) is derived
|
|
145
|
+
* as a pairwise pseudonym keyed by this string. Two clients sharing
|
|
146
|
+
* the same `sectorIdentifier` will see the same `sub`; different
|
|
147
|
+
* values yield different `sub`s for the same end user. Absent =
|
|
148
|
+
* public subject (sub is identical across all RPs).
|
|
149
|
+
*/
|
|
150
|
+
sectorIdentifier?: string
|
|
131
151
|
/** Phase 8. */
|
|
132
152
|
dpopRequired?: boolean
|
|
133
153
|
}
|
|
@@ -146,6 +166,23 @@ export type ConfidentialClientConfig = {
|
|
|
146
166
|
* strongly discouraged; OAuth 2.1 §2.1.1 recommends PKCE for every client.
|
|
147
167
|
*/
|
|
148
168
|
pkceRequired: boolean
|
|
169
|
+
/**
|
|
170
|
+
* Registered URIs to which `/end_session` may redirect after RP-initiated
|
|
171
|
+
* logout (OIDC RP-Initiated Logout 1.0 §2). Exact-match.
|
|
172
|
+
*/
|
|
173
|
+
postLogoutRedirectUris?: string[]
|
|
174
|
+
/**
|
|
175
|
+
* RFC 9126 §2: when `true`, the client MUST use Pushed Authorization
|
|
176
|
+
* Requests; a direct `GET /authorize` call without `request_uri` is
|
|
177
|
+
* rejected with `invalid_request`.
|
|
178
|
+
*/
|
|
179
|
+
requirePushedAuthorizationRequests?: boolean
|
|
180
|
+
/**
|
|
181
|
+
* OIDC Core §8.1 — see `PublicClientConfig.sectorIdentifier` for
|
|
182
|
+
* semantics. Same field; duplicated on each branch because the
|
|
183
|
+
* `ClientConfig` discriminated union doesn't share optional fields.
|
|
184
|
+
*/
|
|
185
|
+
sectorIdentifier?: string
|
|
149
186
|
/** Phase 8. */
|
|
150
187
|
dpopRequired?: boolean
|
|
151
188
|
}
|