@_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
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* refresh-token snapshot is authoritative.
|
|
18
18
|
*/
|
|
19
19
|
import { clientCredentialsGrant } from "../../domain/client-credentials"
|
|
20
|
+
import { canonicalHtu, verifyDpopProof } from "../../domain/dpop"
|
|
20
21
|
import { exchangeCode } from "../../domain/token"
|
|
21
22
|
import { exchangeToken } from "../../domain/token-exchange"
|
|
22
23
|
import { refreshTokens } from "../../domain/refresh"
|
|
@@ -39,12 +40,53 @@ export function makeTokenHandler(deps: HttpDeps) {
|
|
|
39
40
|
)
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
// `Authorization` carries Basic client auth on /token. DPoP travels in
|
|
44
|
+
// its own `DPoP:` header (RFC 9449 §5) — never inside `Authorization`.
|
|
45
|
+
// Parsing Basic first lets a confidential client present its secret
|
|
46
|
+
// alongside a DPoP proof.
|
|
42
47
|
const basic = parseBasicAuth(c.req.header("authorization") ?? null)
|
|
43
48
|
if (basic) {
|
|
44
49
|
body.set("client_id", basic.id)
|
|
45
50
|
body.set("client_secret", basic.secret)
|
|
46
51
|
}
|
|
47
52
|
|
|
53
|
+
// RFC 9449 §5.1 — verify any presented DPoP proof against this
|
|
54
|
+
// request's actual method + URI. Absent header is fine here (the
|
|
55
|
+
// grant-specific path enforces `dpopRequired`).
|
|
56
|
+
const dpopHeader = c.req.header("dpop") ?? null
|
|
57
|
+
let dpopJkt: string | undefined
|
|
58
|
+
if (dpopHeader) {
|
|
59
|
+
const dpopRes = await verifyDpopProof(
|
|
60
|
+
{
|
|
61
|
+
proofJwt: dpopHeader,
|
|
62
|
+
htu: canonicalHtu(c.req.url),
|
|
63
|
+
htm: "POST",
|
|
64
|
+
nowSec: Math.floor(deps.clock() / 1000),
|
|
65
|
+
},
|
|
66
|
+
{ tokenStore: deps.tokenStore },
|
|
67
|
+
)
|
|
68
|
+
if (isErr(dpopRes)) {
|
|
69
|
+
// RFC 9449 §11.1 replay → first-class audit event so operators /
|
|
70
|
+
// SIEM can spot stolen-key probing distinct from generic proof
|
|
71
|
+
// verification failures.
|
|
72
|
+
const errVal = dpopRes.error
|
|
73
|
+
if (
|
|
74
|
+
errVal.code === "invalid_dpop_proof" &&
|
|
75
|
+
errVal.replaySignal &&
|
|
76
|
+
deps.auditLog
|
|
77
|
+
) {
|
|
78
|
+
await deps.auditLog.log({
|
|
79
|
+
kind: "dpop_replay_detected",
|
|
80
|
+
tenantId: null,
|
|
81
|
+
jtiPrefix: errVal.replaySignal.jti.slice(0, 16),
|
|
82
|
+
timestamp: deps.clock(),
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
return tokenEndpointErrorResponse(errVal)
|
|
86
|
+
}
|
|
87
|
+
dpopJkt = dpopRes.value.jkt
|
|
88
|
+
}
|
|
89
|
+
|
|
48
90
|
const parsed = tokenRequestSchema.safeParse(
|
|
49
91
|
Object.fromEntries(body.entries()),
|
|
50
92
|
)
|
|
@@ -77,6 +119,7 @@ export function makeTokenHandler(deps: HttpDeps) {
|
|
|
77
119
|
...(req.code_verifier !== undefined
|
|
78
120
|
? { codeVerifier: req.code_verifier }
|
|
79
121
|
: {}),
|
|
122
|
+
...(dpopJkt !== undefined ? { dpopJkt } : {}),
|
|
80
123
|
},
|
|
81
124
|
{
|
|
82
125
|
configStore: deps.configStore,
|
|
@@ -89,6 +132,9 @@ export function makeTokenHandler(deps: HttpDeps) {
|
|
|
89
132
|
: {}),
|
|
90
133
|
issuerUrl: c.get("issuerUrl"),
|
|
91
134
|
clock: deps.clock,
|
|
135
|
+
...(deps.customScopeClaims !== undefined
|
|
136
|
+
? { customScopeClaims: deps.customScopeClaims }
|
|
137
|
+
: {}),
|
|
92
138
|
},
|
|
93
139
|
)
|
|
94
140
|
if (isErr(result)) return tokenEndpointErrorResponse(result.error)
|
|
@@ -133,6 +179,9 @@ export function makeTokenHandler(deps: HttpDeps) {
|
|
|
133
179
|
: {}),
|
|
134
180
|
issuerUrl: c.get("issuerUrl"),
|
|
135
181
|
clock: deps.clock,
|
|
182
|
+
...(deps.customScopeClaims !== undefined
|
|
183
|
+
? { customScopeClaims: deps.customScopeClaims }
|
|
184
|
+
: {}),
|
|
136
185
|
},
|
|
137
186
|
tenantRes.value,
|
|
138
187
|
)
|
|
@@ -150,6 +199,7 @@ export function makeTokenHandler(deps: HttpDeps) {
|
|
|
150
199
|
...(req.client_secret !== undefined
|
|
151
200
|
? { clientSecret: req.client_secret }
|
|
152
201
|
: {}),
|
|
202
|
+
...(dpopJkt !== undefined ? { dpopJkt } : {}),
|
|
153
203
|
},
|
|
154
204
|
{
|
|
155
205
|
configStore: deps.configStore,
|
|
@@ -158,6 +208,9 @@ export function makeTokenHandler(deps: HttpDeps) {
|
|
|
158
208
|
...(deps.auditLog ? { auditLog: deps.auditLog } : {}),
|
|
159
209
|
issuerUrl: c.get("issuerUrl"),
|
|
160
210
|
clock: deps.clock,
|
|
211
|
+
...(deps.customScopeClaims !== undefined
|
|
212
|
+
? { customScopeClaims: deps.customScopeClaims }
|
|
213
|
+
: {}),
|
|
161
214
|
},
|
|
162
215
|
)
|
|
163
216
|
if (isErr(refreshResult))
|
|
@@ -195,6 +248,9 @@ export function makeTokenHandler(deps: HttpDeps) {
|
|
|
195
248
|
: {}),
|
|
196
249
|
issuerUrl: c.get("issuerUrl"),
|
|
197
250
|
clock: deps.clock,
|
|
251
|
+
...(deps.customScopeClaims !== undefined
|
|
252
|
+
? { customScopeClaims: deps.customScopeClaims }
|
|
253
|
+
: {}),
|
|
198
254
|
},
|
|
199
255
|
)
|
|
200
256
|
if (isErr(exchangeResult)) {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OIDC `/userinfo` handler (OIDC Core §5.3). Accepts `GET` or `POST` per
|
|
3
|
-
* the spec.
|
|
3
|
+
* the spec. Supports both `Authorization: Bearer ...` (RFC 6750) and
|
|
4
|
+
* `Authorization: DPoP ...` (RFC 9449) — the domain layer enforces the
|
|
5
|
+
* proof match against the access token's `cnf.jkt` when bound.
|
|
4
6
|
*/
|
|
7
|
+
import { canonicalHtu } from "../../domain/dpop"
|
|
5
8
|
import { userinfo } from "../../domain/userinfo"
|
|
6
9
|
import { isErr } from "../../types/result"
|
|
7
10
|
|
|
@@ -9,44 +12,43 @@ import type { HttpContext, HttpDeps } from "../context"
|
|
|
9
12
|
|
|
10
13
|
export function makeUserinfoHandler(deps: HttpDeps) {
|
|
11
14
|
return async (c: HttpContext): Promise<Response> => {
|
|
12
|
-
const
|
|
13
|
-
if (!
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
{
|
|
20
|
-
status: 401,
|
|
21
|
-
headers: {
|
|
22
|
-
"content-type": "application/json",
|
|
23
|
-
"www-authenticate": 'Bearer realm="userinfo"',
|
|
24
|
-
},
|
|
25
|
-
},
|
|
15
|
+
const auth = extractAuthorization(c.req.header("authorization") ?? null)
|
|
16
|
+
if (!auth) {
|
|
17
|
+
return wwwAuthenticateResponse(
|
|
18
|
+
"Bearer",
|
|
19
|
+
"invalid_token",
|
|
20
|
+
"missing bearer / DPoP token",
|
|
21
|
+
401,
|
|
26
22
|
)
|
|
27
|
-
return r
|
|
28
23
|
}
|
|
29
24
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
const dpopProof = c.req.header("dpop") ?? undefined
|
|
26
|
+
const res = await userinfo(
|
|
27
|
+
{
|
|
28
|
+
accessToken: auth.token,
|
|
29
|
+
scheme: auth.scheme,
|
|
30
|
+
...(dpopProof !== undefined ? { dpopProof } : {}),
|
|
31
|
+
htu: canonicalHtu(c.req.url),
|
|
32
|
+
htm: c.req.method.toUpperCase(),
|
|
33
|
+
nowSec: Math.floor(deps.clock() / 1000),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
keyStore: deps.keyStore,
|
|
37
|
+
tokenStore: deps.tokenStore,
|
|
38
|
+
issuerUrl: c.get("issuerUrl"),
|
|
39
|
+
...(deps.customScopeClaims !== undefined
|
|
40
|
+
? { customScopeClaims: deps.customScopeClaims }
|
|
41
|
+
: {}),
|
|
42
|
+
},
|
|
43
|
+
)
|
|
34
44
|
if (isErr(res)) {
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
{
|
|
43
|
-
status: 401,
|
|
44
|
-
headers: {
|
|
45
|
-
"content-type": "application/json",
|
|
46
|
-
"www-authenticate": `Bearer error="invalid_token"`,
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
)
|
|
45
|
+
const error = res.error
|
|
46
|
+
const scheme = auth.scheme
|
|
47
|
+
const wireError =
|
|
48
|
+
error.code === "invalid_dpop_proof"
|
|
49
|
+
? "invalid_dpop_proof"
|
|
50
|
+
: "invalid_token"
|
|
51
|
+
return wwwAuthenticateResponse(scheme, wireError, error.description, 401)
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
return new Response(JSON.stringify(res.value), {
|
|
@@ -59,8 +61,30 @@ export function makeUserinfoHandler(deps: HttpDeps) {
|
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
function
|
|
64
|
+
function extractAuthorization(
|
|
65
|
+
header: string | null,
|
|
66
|
+
): { scheme: "Bearer" | "DPoP"; token: string } | null {
|
|
63
67
|
if (!header) return null
|
|
64
|
-
const m = /^Bearer\s+(.+)$/i.exec(header)
|
|
65
|
-
|
|
68
|
+
const m = /^(Bearer|DPoP)\s+(.+)$/i.exec(header)
|
|
69
|
+
if (!m || !m[1] || !m[2]) return null
|
|
70
|
+
const scheme = m[1].toLowerCase() === "dpop" ? "DPoP" : "Bearer"
|
|
71
|
+
return { scheme, token: m[2] }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function wwwAuthenticateResponse(
|
|
75
|
+
scheme: "Bearer" | "DPoP",
|
|
76
|
+
error: string,
|
|
77
|
+
description: string,
|
|
78
|
+
status: number,
|
|
79
|
+
): Response {
|
|
80
|
+
return new Response(
|
|
81
|
+
JSON.stringify({ error, error_description: description }),
|
|
82
|
+
{
|
|
83
|
+
status,
|
|
84
|
+
headers: {
|
|
85
|
+
"content-type": "application/json",
|
|
86
|
+
"www-authenticate": `${scheme} error="${error}"`,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
)
|
|
66
90
|
}
|
package/src/http/router.ts
CHANGED
|
@@ -19,8 +19,11 @@ import { Hono } from "hono"
|
|
|
19
19
|
|
|
20
20
|
import { makeAuthorizeHandler } from "./handlers/authorize"
|
|
21
21
|
import { makeCallbackHandler } from "./handlers/callback"
|
|
22
|
+
import { makeEndSessionHandler } from "./handlers/end-session"
|
|
22
23
|
import { makeDiscoveryHandler, makeJwksHandler } from "./handlers/metadata"
|
|
23
24
|
import { makeMethodRouteHandler } from "./handlers/method-route"
|
|
25
|
+
import { makeParHandler } from "./handlers/par"
|
|
26
|
+
import { makeRegisterHandler } from "./handlers/register"
|
|
24
27
|
import { makeIntrospectHandler, makeRevokeHandler } from "./handlers/revocation"
|
|
25
28
|
import { makeTokenHandler } from "./handlers/token"
|
|
26
29
|
import { makeUserinfoHandler } from "./handlers/userinfo"
|
|
@@ -62,5 +65,21 @@ export function buildRouter(deps: HttpDeps): Hono<HttpEnv> {
|
|
|
62
65
|
app.post("/revoke", makeRevokeHandler(deps))
|
|
63
66
|
app.post("/introspect", makeIntrospectHandler(deps))
|
|
64
67
|
|
|
68
|
+
// OIDC RP-Initiated Logout 1.0. Tenant resolved via the standard
|
|
69
|
+
// middleware — same partitioning rules as `/authorize`.
|
|
70
|
+
app.use("/end_session", tenantMiddleware(deps))
|
|
71
|
+
app.get("/end_session", makeEndSessionHandler(deps))
|
|
72
|
+
app.post("/end_session", makeEndSessionHandler(deps))
|
|
73
|
+
|
|
74
|
+
// RFC 9126 Pushed Authorization Requests.
|
|
75
|
+
app.use("/par", tenantMiddleware(deps))
|
|
76
|
+
app.post("/par", makeParHandler(deps))
|
|
77
|
+
|
|
78
|
+
// RFC 7591 Dynamic Client Registration. Tenant resolution uses the
|
|
79
|
+
// standard middleware — partitioned hosts may carve out per-tenant
|
|
80
|
+
// registration endpoints transparently.
|
|
81
|
+
app.use("/register", tenantMiddleware(deps))
|
|
82
|
+
app.post("/register", makeRegisterHandler(deps))
|
|
83
|
+
|
|
65
84
|
return app
|
|
66
85
|
}
|
|
@@ -32,6 +32,12 @@ const scopeParam = z
|
|
|
32
32
|
)
|
|
33
33
|
.pipe(z.array(z.string().regex(SCOPE_TOKEN, "invalid scope token")))
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Standard `/authorize` query schema. Used both for direct calls and as
|
|
37
|
+
* the validation target after a PAR record is rehydrated — the PAR'd
|
|
38
|
+
* params are passed through this same parser so semantic checks stay in
|
|
39
|
+
* a single place.
|
|
40
|
+
*/
|
|
35
41
|
export const authorizeQuerySchema = z
|
|
36
42
|
.object({
|
|
37
43
|
response_type: z.string({
|
|
@@ -52,6 +58,27 @@ export const authorizeQuerySchema = z
|
|
|
52
58
|
prompt: csv.optional(),
|
|
53
59
|
ui_locales: csv.optional(),
|
|
54
60
|
nonce: z.string().optional(),
|
|
61
|
+
/**
|
|
62
|
+
* OIDC Core §5.5 — JSON-encoded claims request. Parsing + structural
|
|
63
|
+
* validation happens in the domain layer; here we just accept any
|
|
64
|
+
* non-empty string and let the handler parse + reject malformed.
|
|
65
|
+
*/
|
|
66
|
+
claims: z.string().min(1).optional(),
|
|
67
|
+
})
|
|
68
|
+
.passthrough()
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* RFC 9126 §4: when `request_uri` is presented, the user-agent's
|
|
72
|
+
* `/authorize` URL is allowed to carry only `client_id` and
|
|
73
|
+
* `request_uri`; the rest of the parameter set must come from the
|
|
74
|
+
* stored PAR record.
|
|
75
|
+
*/
|
|
76
|
+
export const authorizeRequestUriQuerySchema = z
|
|
77
|
+
.object({
|
|
78
|
+
client_id: z
|
|
79
|
+
.string({ required_error: "missing client_id" })
|
|
80
|
+
.min(1, "empty client_id"),
|
|
81
|
+
request_uri: z.string({ required_error: "missing request_uri" }).min(1),
|
|
55
82
|
})
|
|
56
83
|
.passthrough()
|
|
57
84
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schema for `/end_session` (OIDC RP-Initiated Logout 1.0 §2).
|
|
3
|
+
*
|
|
4
|
+
* Every parameter is optional per spec. The domain layer enforces the
|
|
5
|
+
* "needs identifying client to honor post_logout_redirect_uri" rule.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod"
|
|
8
|
+
|
|
9
|
+
export const endSessionParamsSchema = z
|
|
10
|
+
.object({
|
|
11
|
+
id_token_hint: z.string().optional(),
|
|
12
|
+
client_id: z.string().min(1).optional(),
|
|
13
|
+
post_logout_redirect_uri: z
|
|
14
|
+
.string()
|
|
15
|
+
.url("post_logout_redirect_uri must be a valid URL")
|
|
16
|
+
.optional(),
|
|
17
|
+
state: z.string().optional(),
|
|
18
|
+
logout_hint: z.string().optional(),
|
|
19
|
+
ui_locales: z.string().optional(),
|
|
20
|
+
})
|
|
21
|
+
.passthrough()
|
|
22
|
+
|
|
23
|
+
export type EndSessionParams = z.infer<typeof endSessionParamsSchema>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schema for the `POST /par` body (RFC 9126 §2).
|
|
3
|
+
*
|
|
4
|
+
* The shape is identical to `/authorize` query parameters; we just need
|
|
5
|
+
* `client_id` to identify the authenticating client. `client_secret` is
|
|
6
|
+
* accepted in-body for `client_secret_post` auth.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod"
|
|
9
|
+
|
|
10
|
+
export const parBodySchema = z
|
|
11
|
+
.object({
|
|
12
|
+
client_id: z
|
|
13
|
+
.string({ required_error: "missing client_id" })
|
|
14
|
+
.min(1, "empty client_id"),
|
|
15
|
+
client_secret: z.string().optional(),
|
|
16
|
+
})
|
|
17
|
+
// `.catchall(z.string())` validates AND types passthrough keys as
|
|
18
|
+
// strings — `application/x-www-form-urlencoded` values are always
|
|
19
|
+
// strings at the wire level, so this also rejects malformed bodies
|
|
20
|
+
// where a key somehow carries a non-string value.
|
|
21
|
+
.catchall(z.string())
|
|
22
|
+
|
|
23
|
+
export type ParBody = z.infer<typeof parBodySchema>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schema for `POST /register` (RFC 7591 §2).
|
|
3
|
+
*
|
|
4
|
+
* The body is `application/json`. We surface invalid structure as
|
|
5
|
+
* `invalid_request` rather than the spec's `invalid_client_metadata` /
|
|
6
|
+
* `invalid_redirect_uri` codes — the library collapses all wire-format
|
|
7
|
+
* problems to the OAuth-2.0 `invalid_request` family for consistency.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from "zod"
|
|
10
|
+
|
|
11
|
+
export const registerBodySchema = z
|
|
12
|
+
.object({
|
|
13
|
+
client_name: z.string().min(1).optional(),
|
|
14
|
+
redirect_uris: z
|
|
15
|
+
.array(z.string().url("redirect_uri must be a valid URL"))
|
|
16
|
+
.min(1, "redirect_uris must be a non-empty array"),
|
|
17
|
+
grant_types: z.array(z.string()).optional(),
|
|
18
|
+
response_types: z.array(z.string()).optional(),
|
|
19
|
+
token_endpoint_auth_method: z
|
|
20
|
+
.enum(["none", "client_secret_basic", "client_secret_post"])
|
|
21
|
+
.optional(),
|
|
22
|
+
scope: z.string().optional(),
|
|
23
|
+
post_logout_redirect_uris: z.array(z.string().url()).optional(),
|
|
24
|
+
sector_identifier_uri: z.string().url().optional(),
|
|
25
|
+
contacts: z.array(z.string()).optional(),
|
|
26
|
+
})
|
|
27
|
+
.passthrough()
|
|
28
|
+
|
|
29
|
+
export type RegisterBody = z.infer<typeof registerBodySchema>
|
package/src/index.ts
CHANGED
|
@@ -57,8 +57,11 @@ export type {
|
|
|
57
57
|
|
|
58
58
|
export type {
|
|
59
59
|
AccessTokenClaims,
|
|
60
|
+
AddressClaim,
|
|
60
61
|
CodePayload,
|
|
62
|
+
IdTokenClaims,
|
|
61
63
|
RefreshTokenPayload,
|
|
64
|
+
ScopedProfileClaims,
|
|
62
65
|
TokenResponse,
|
|
63
66
|
} from "./types/token"
|
|
64
67
|
|
|
@@ -73,7 +76,12 @@ export type {
|
|
|
73
76
|
FailureEvent,
|
|
74
77
|
IdP,
|
|
75
78
|
IdPOptions,
|
|
79
|
+
LogoutEventInput,
|
|
80
|
+
LogoutHookResult,
|
|
76
81
|
PersistUpstreamTokens,
|
|
82
|
+
RegisterClient,
|
|
83
|
+
RegisterClientRequest,
|
|
84
|
+
RegisterClientResponse,
|
|
77
85
|
RenderPicker,
|
|
78
86
|
SuccessEvent,
|
|
79
87
|
SuccessMapInput,
|
|
@@ -90,7 +98,11 @@ export type {
|
|
|
90
98
|
SigningKey,
|
|
91
99
|
} from "./ports/key-store"
|
|
92
100
|
export type { MethodStore } from "./ports/method-store"
|
|
93
|
-
export type {
|
|
101
|
+
export type {
|
|
102
|
+
ParRecord,
|
|
103
|
+
SessionRecord,
|
|
104
|
+
SessionStore,
|
|
105
|
+
} from "./ports/session-store"
|
|
94
106
|
export type { TokenStore } from "./ports/token-store"
|
|
95
107
|
|
|
96
108
|
/**
|
|
@@ -236,6 +248,7 @@ export function createIdP(opts: IdPOptions): IdP {
|
|
|
236
248
|
...(opts.callbackHostFor ? { callbackHostFor: opts.callbackHostFor } : {}),
|
|
237
249
|
resolveTenant: opts.resolveTenant,
|
|
238
250
|
success: opts.success,
|
|
251
|
+
...(opts.onLogout ? { onLogout: opts.onLogout } : {}),
|
|
239
252
|
...(opts.persistUpstreamTokens
|
|
240
253
|
? { persistUpstreamTokens: opts.persistUpstreamTokens }
|
|
241
254
|
: {}),
|
|
@@ -243,6 +256,7 @@ export function createIdP(opts: IdPOptions): IdP {
|
|
|
243
256
|
? { exchangeAudience: opts.exchangeAudience }
|
|
244
257
|
: {}),
|
|
245
258
|
...(opts.renderPicker ? { renderPicker: opts.renderPicker } : {}),
|
|
259
|
+
...(opts.registerClient ? { registerClient: opts.registerClient } : {}),
|
|
246
260
|
...(opts.buildCustomContext
|
|
247
261
|
? { buildCustomContext: opts.buildCustomContext }
|
|
248
262
|
: {}),
|
|
@@ -254,6 +268,9 @@ export function createIdP(opts: IdPOptions): IdP {
|
|
|
254
268
|
: {}),
|
|
255
269
|
...(opts.cookies?.path !== undefined ? { path: opts.cookies.path } : {}),
|
|
256
270
|
},
|
|
271
|
+
...(opts.customScopeClaims !== undefined
|
|
272
|
+
? { customScopeClaims: opts.customScopeClaims }
|
|
273
|
+
: {}),
|
|
257
274
|
}
|
|
258
275
|
|
|
259
276
|
const app = buildRouter(deps)
|
|
@@ -268,5 +285,8 @@ export function createIdP(opts: IdPOptions): IdP {
|
|
|
268
285
|
discovery: fetch,
|
|
269
286
|
revoke: fetch,
|
|
270
287
|
introspect: fetch,
|
|
288
|
+
endSession: fetch,
|
|
289
|
+
par: fetch,
|
|
290
|
+
register: fetch,
|
|
271
291
|
}
|
|
272
292
|
}
|