@_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.
Files changed (277) hide show
  1. package/dist/esm/adapters/d1/index.js +18 -9
  2. package/dist/esm/adapters/d1/migrations.js +7 -2
  3. package/dist/esm/adapters/d1/session-store.js +39 -0
  4. package/dist/esm/adapters/d1/session.js +2 -2
  5. package/dist/esm/adapters/durable-object/index.js +4 -0
  6. package/dist/esm/adapters/durable-object/session-store.js +42 -0
  7. package/dist/esm/adapters/dynamo/index.js +24 -7
  8. package/dist/esm/adapters/dynamo/session-store.js +49 -0
  9. package/dist/esm/adapters/kms/index.js +6 -2
  10. package/dist/esm/adapters/kv/index.js +6 -2
  11. package/dist/esm/adapters/memory/index.js +24 -7
  12. package/dist/esm/adapters/memory/session-store.js +45 -0
  13. package/dist/esm/adapters/memory/token-store.js +13 -0
  14. package/dist/esm/adapters/postgres/executor.js +2 -2
  15. package/dist/esm/adapters/postgres/index.js +33 -10
  16. package/dist/esm/adapters/postgres/migrations.js +8 -2
  17. package/dist/esm/adapters/postgres/session-store.js +42 -0
  18. package/dist/esm/client.js +35 -28
  19. package/dist/esm/domain/authorize.js +7 -2
  20. package/dist/esm/domain/callback.js +124 -7
  21. package/dist/esm/domain/client-auth.js +2 -2
  22. package/dist/esm/domain/crypto.js +14 -14
  23. package/dist/esm/domain/discovery.js +46 -4
  24. package/dist/esm/domain/dpop.js +122 -0
  25. package/dist/esm/domain/id-token.js +94 -0
  26. package/dist/esm/domain/introspect.js +5 -1
  27. package/dist/esm/domain/jwt.js +30 -2
  28. package/dist/esm/domain/logout.js +75 -0
  29. package/dist/esm/domain/method-dispatch.js +27 -1
  30. package/dist/esm/domain/method-route.js +83 -1
  31. package/dist/esm/domain/par.js +51 -0
  32. package/dist/esm/domain/pkce.js +2 -2
  33. package/dist/esm/domain/refresh.js +12 -1
  34. package/dist/esm/domain/register.js +74 -0
  35. package/dist/esm/domain/revoke.js +2 -2
  36. package/dist/esm/domain/state-envelope.js +2 -2
  37. package/dist/esm/domain/token-exchange.js +1 -0
  38. package/dist/esm/domain/token.js +53 -12
  39. package/dist/esm/domain/userinfo.js +38 -3
  40. package/dist/esm/error.js +8 -8
  41. package/dist/esm/http/cookies.js +19 -2
  42. package/dist/esm/http/errors.js +4 -4
  43. package/dist/esm/http/handlers/authorize.js +92 -21
  44. package/dist/esm/http/handlers/callback.js +7 -1
  45. package/dist/esm/http/handlers/end-session.js +64 -0
  46. package/dist/esm/http/handlers/metadata.js +7 -4
  47. package/dist/esm/http/handlers/method-route.js +47 -2
  48. package/dist/esm/http/handlers/par.js +53 -0
  49. package/dist/esm/http/handlers/register.js +46 -0
  50. package/dist/esm/http/handlers/revocation.js +2 -2
  51. package/dist/esm/http/handlers/token.js +36 -6
  52. package/dist/esm/http/handlers/userinfo.js +35 -29
  53. package/dist/esm/http/middleware/tenant.js +6 -6
  54. package/dist/esm/http/router.js +10 -0
  55. package/dist/esm/http/schemas/authorize.js +8 -2
  56. package/dist/esm/http/schemas/end-session.js +13 -0
  57. package/dist/esm/http/schemas/par.js +9 -0
  58. package/dist/esm/http/schemas/register.js +16 -0
  59. package/dist/esm/http/schemas/revocation.js +2 -2
  60. package/dist/esm/http/schemas/token.js +7 -7
  61. package/dist/esm/index.js +40 -34
  62. package/dist/esm/methods/oauth2-factory.js +2 -2
  63. package/dist/esm/methods/password-hash.js +2 -2
  64. package/dist/esm/methods/providers/index.js +30 -14
  65. package/dist/esm/methods/saml-sp/acs.js +208 -0
  66. package/dist/esm/methods/saml-sp/attributes.js +75 -0
  67. package/dist/esm/methods/saml-sp/authnrequest.js +63 -0
  68. package/dist/esm/methods/saml-sp/cache-provider.js +33 -0
  69. package/dist/esm/methods/saml-sp/cert-rotation.js +13 -0
  70. package/dist/esm/methods/saml-sp/factory.js +70 -0
  71. package/dist/esm/methods/saml-sp/index.js +7 -0
  72. package/dist/esm/methods/saml-sp/metadata.js +69 -0
  73. package/dist/esm/methods/saml-sp/method.js +29 -0
  74. package/dist/esm/methods/saml-sp/parse-idp-metadata.js +102 -0
  75. package/dist/esm/methods/saml-sp/saml-instance.js +49 -0
  76. package/dist/esm/methods/saml-sp/slo-initiate.js +84 -0
  77. package/dist/esm/methods/saml-sp/sls.js +154 -0
  78. package/dist/esm/methods/saml-sp/types.js +0 -0
  79. package/dist/esm/types/error.js +5 -0
  80. package/dist/esm/types/result.js +3 -3
  81. package/dist/esm/ui/forms.js +3 -3
  82. package/dist/esm/ui/index.js +8 -5
  83. package/dist/esm/ui/picker.js +2 -2
  84. package/dist/types/adapters/d1/migrations.d.ts.map +1 -1
  85. package/dist/types/adapters/d1/session-store.d.ts +3 -0
  86. package/dist/types/adapters/d1/session-store.d.ts.map +1 -1
  87. package/dist/types/adapters/durable-object/session-store.d.ts +3 -0
  88. package/dist/types/adapters/durable-object/session-store.d.ts.map +1 -1
  89. package/dist/types/adapters/dynamo/session-store.d.ts +3 -0
  90. package/dist/types/adapters/dynamo/session-store.d.ts.map +1 -1
  91. package/dist/types/adapters/memory/session-store.d.ts +6 -1
  92. package/dist/types/adapters/memory/session-store.d.ts.map +1 -1
  93. package/dist/types/adapters/memory/token-store.d.ts +1 -0
  94. package/dist/types/adapters/memory/token-store.d.ts.map +1 -1
  95. package/dist/types/adapters/postgres/migrations.d.ts +1 -1
  96. package/dist/types/adapters/postgres/migrations.d.ts.map +1 -1
  97. package/dist/types/adapters/postgres/session-store.d.ts +3 -0
  98. package/dist/types/adapters/postgres/session-store.d.ts.map +1 -1
  99. package/dist/types/client.d.ts +19 -0
  100. package/dist/types/client.d.ts.map +1 -1
  101. package/dist/types/domain/authorize.d.ts.map +1 -1
  102. package/dist/types/domain/callback.d.ts +16 -1
  103. package/dist/types/domain/callback.d.ts.map +1 -1
  104. package/dist/types/domain/client-credentials.d.ts +2 -0
  105. package/dist/types/domain/client-credentials.d.ts.map +1 -1
  106. package/dist/types/domain/discovery.d.ts +37 -0
  107. package/dist/types/domain/discovery.d.ts.map +1 -1
  108. package/dist/types/domain/dpop.d.ts +61 -0
  109. package/dist/types/domain/dpop.d.ts.map +1 -0
  110. package/dist/types/domain/id-token.d.ts +83 -0
  111. package/dist/types/domain/id-token.d.ts.map +1 -0
  112. package/dist/types/domain/introspect.d.ts +8 -0
  113. package/dist/types/domain/introspect.d.ts.map +1 -1
  114. package/dist/types/domain/jwt.d.ts +27 -1
  115. package/dist/types/domain/jwt.d.ts.map +1 -1
  116. package/dist/types/domain/logout.d.ts +69 -0
  117. package/dist/types/domain/logout.d.ts.map +1 -0
  118. package/dist/types/domain/method-dispatch.d.ts.map +1 -1
  119. package/dist/types/domain/method-route.d.ts +57 -1
  120. package/dist/types/domain/method-route.d.ts.map +1 -1
  121. package/dist/types/domain/par.d.ts +62 -0
  122. package/dist/types/domain/par.d.ts.map +1 -0
  123. package/dist/types/domain/refresh.d.ts +9 -0
  124. package/dist/types/domain/refresh.d.ts.map +1 -1
  125. package/dist/types/domain/register.d.ts +33 -0
  126. package/dist/types/domain/register.d.ts.map +1 -0
  127. package/dist/types/domain/token-exchange.d.ts +2 -0
  128. package/dist/types/domain/token-exchange.d.ts.map +1 -1
  129. package/dist/types/domain/token.d.ts +41 -1
  130. package/dist/types/domain/token.d.ts.map +1 -1
  131. package/dist/types/domain/userinfo.d.ts +44 -5
  132. package/dist/types/domain/userinfo.d.ts.map +1 -1
  133. package/dist/types/http/context.d.ts +6 -1
  134. package/dist/types/http/context.d.ts.map +1 -1
  135. package/dist/types/http/cookies.d.ts +9 -1
  136. package/dist/types/http/cookies.d.ts.map +1 -1
  137. package/dist/types/http/handlers/authorize.d.ts.map +1 -1
  138. package/dist/types/http/handlers/callback.d.ts.map +1 -1
  139. package/dist/types/http/handlers/end-session.d.ts +3 -0
  140. package/dist/types/http/handlers/end-session.d.ts.map +1 -0
  141. package/dist/types/http/handlers/metadata.d.ts +1 -1
  142. package/dist/types/http/handlers/metadata.d.ts.map +1 -1
  143. package/dist/types/http/handlers/method-route.d.ts.map +1 -1
  144. package/dist/types/http/handlers/par.d.ts +3 -0
  145. package/dist/types/http/handlers/par.d.ts.map +1 -0
  146. package/dist/types/http/handlers/register.d.ts +3 -0
  147. package/dist/types/http/handlers/register.d.ts.map +1 -0
  148. package/dist/types/http/handlers/token.d.ts.map +1 -1
  149. package/dist/types/http/handlers/userinfo.d.ts.map +1 -1
  150. package/dist/types/http/router.d.ts.map +1 -1
  151. package/dist/types/http/schemas/authorize.d.ts +40 -0
  152. package/dist/types/http/schemas/authorize.d.ts.map +1 -1
  153. package/dist/types/http/schemas/end-session.d.ts +31 -0
  154. package/dist/types/http/schemas/end-session.d.ts.map +1 -0
  155. package/dist/types/http/schemas/par.d.ts +20 -0
  156. package/dist/types/http/schemas/par.d.ts.map +1 -0
  157. package/dist/types/http/schemas/register.d.ts +42 -0
  158. package/dist/types/http/schemas/register.d.ts.map +1 -0
  159. package/dist/types/index.d.ts +3 -3
  160. package/dist/types/index.d.ts.map +1 -1
  161. package/dist/types/methods/saml-sp/acs.d.ts +4 -0
  162. package/dist/types/methods/saml-sp/acs.d.ts.map +1 -0
  163. package/dist/types/methods/saml-sp/attributes.d.ts +40 -0
  164. package/dist/types/methods/saml-sp/attributes.d.ts.map +1 -0
  165. package/dist/types/methods/saml-sp/authnrequest.d.ts +4 -0
  166. package/dist/types/methods/saml-sp/authnrequest.d.ts.map +1 -0
  167. package/dist/types/methods/saml-sp/cache-provider.d.ts +38 -0
  168. package/dist/types/methods/saml-sp/cache-provider.d.ts.map +1 -0
  169. package/dist/types/methods/saml-sp/cert-rotation.d.ts +23 -0
  170. package/dist/types/methods/saml-sp/cert-rotation.d.ts.map +1 -0
  171. package/dist/types/methods/saml-sp/factory.d.ts +704 -0
  172. package/dist/types/methods/saml-sp/factory.d.ts.map +1 -0
  173. package/dist/types/methods/saml-sp/index.d.ts +24 -0
  174. package/dist/types/methods/saml-sp/index.d.ts.map +1 -0
  175. package/dist/types/methods/saml-sp/metadata.d.ts +42 -0
  176. package/dist/types/methods/saml-sp/metadata.d.ts.map +1 -0
  177. package/dist/types/methods/saml-sp/method.d.ts +17 -0
  178. package/dist/types/methods/saml-sp/method.d.ts.map +1 -0
  179. package/dist/types/methods/saml-sp/parse-idp-metadata.d.ts +4 -0
  180. package/dist/types/methods/saml-sp/parse-idp-metadata.d.ts.map +1 -0
  181. package/dist/types/methods/saml-sp/saml-instance.d.ts +81 -0
  182. package/dist/types/methods/saml-sp/saml-instance.d.ts.map +1 -0
  183. package/dist/types/methods/saml-sp/slo-initiate.d.ts +4 -0
  184. package/dist/types/methods/saml-sp/slo-initiate.d.ts.map +1 -0
  185. package/dist/types/methods/saml-sp/sls.d.ts +4 -0
  186. package/dist/types/methods/saml-sp/sls.d.ts.map +1 -0
  187. package/dist/types/methods/saml-sp/types.d.ts +204 -0
  188. package/dist/types/methods/saml-sp/types.d.ts.map +1 -0
  189. package/dist/types/ports/audit-log.d.ts +40 -0
  190. package/dist/types/ports/audit-log.d.ts.map +1 -1
  191. package/dist/types/ports/session-store.d.ts +50 -0
  192. package/dist/types/ports/session-store.d.ts.map +1 -1
  193. package/dist/types/ports/token-store.d.ts +12 -0
  194. package/dist/types/ports/token-store.d.ts.map +1 -1
  195. package/dist/types/types/authorization.d.ts +23 -0
  196. package/dist/types/types/authorization.d.ts.map +1 -1
  197. package/dist/types/types/error.d.ts +9 -0
  198. package/dist/types/types/error.d.ts.map +1 -1
  199. package/dist/types/types/flow.d.ts +10 -0
  200. package/dist/types/types/flow.d.ts.map +1 -1
  201. package/dist/types/types/idp.d.ts +162 -1
  202. package/dist/types/types/idp.d.ts.map +1 -1
  203. package/dist/types/types/method.d.ts +109 -0
  204. package/dist/types/types/method.d.ts.map +1 -1
  205. package/dist/types/types/tenant.d.ts +37 -0
  206. package/dist/types/types/tenant.d.ts.map +1 -1
  207. package/dist/types/types/token.d.ts +128 -0
  208. package/dist/types/types/token.d.ts.map +1 -1
  209. package/package.json +9 -1
  210. package/src/adapters/d1/migrations.ts +5 -0
  211. package/src/adapters/d1/session-store.ts +68 -0
  212. package/src/adapters/durable-object/session-store.ts +61 -0
  213. package/src/adapters/dynamo/session-store.ts +63 -0
  214. package/src/adapters/memory/session-store.ts +83 -1
  215. package/src/adapters/memory/token-store.ts +16 -0
  216. package/src/adapters/postgres/migrations.ts +6 -0
  217. package/src/adapters/postgres/session-store.ts +69 -0
  218. package/src/client.ts +32 -0
  219. package/src/domain/authorize.ts +9 -0
  220. package/src/domain/callback.ts +220 -7
  221. package/src/domain/client-credentials.ts +2 -0
  222. package/src/domain/discovery.ts +80 -2
  223. package/src/domain/dpop.ts +293 -0
  224. package/src/domain/id-token.ts +213 -0
  225. package/src/domain/introspect.ts +10 -0
  226. package/src/domain/jwt.ts +80 -1
  227. package/src/domain/logout.ts +180 -0
  228. package/src/domain/method-dispatch.ts +53 -1
  229. package/src/domain/method-route.ts +195 -1
  230. package/src/domain/par.ts +141 -0
  231. package/src/domain/refresh.ts +41 -0
  232. package/src/domain/register.ts +168 -0
  233. package/src/domain/token-exchange.ts +11 -0
  234. package/src/domain/token.ts +134 -8
  235. package/src/domain/userinfo.ts +121 -6
  236. package/src/http/context.ts +10 -0
  237. package/src/http/cookies.ts +19 -1
  238. package/src/http/handlers/authorize.ts +146 -24
  239. package/src/http/handlers/callback.ts +9 -1
  240. package/src/http/handlers/end-session.ts +106 -0
  241. package/src/http/handlers/metadata.ts +7 -2
  242. package/src/http/handlers/method-route.ts +75 -2
  243. package/src/http/handlers/par.ts +84 -0
  244. package/src/http/handlers/register.ts +70 -0
  245. package/src/http/handlers/token.ts +56 -0
  246. package/src/http/handlers/userinfo.ts +62 -38
  247. package/src/http/router.ts +19 -0
  248. package/src/http/schemas/authorize.ts +27 -0
  249. package/src/http/schemas/end-session.ts +23 -0
  250. package/src/http/schemas/par.ts +23 -0
  251. package/src/http/schemas/register.ts +29 -0
  252. package/src/index.ts +21 -1
  253. package/src/methods/saml-sp/acs.ts +399 -0
  254. package/src/methods/saml-sp/attributes.ts +137 -0
  255. package/src/methods/saml-sp/authnrequest.ts +128 -0
  256. package/src/methods/saml-sp/cache-provider.ts +76 -0
  257. package/src/methods/saml-sp/cert-rotation.ts +34 -0
  258. package/src/methods/saml-sp/factory.ts +118 -0
  259. package/src/methods/saml-sp/index.ts +34 -0
  260. package/src/methods/saml-sp/metadata.ts +204 -0
  261. package/src/methods/saml-sp/method.ts +65 -0
  262. package/src/methods/saml-sp/parse-idp-metadata.ts +164 -0
  263. package/src/methods/saml-sp/saml-instance.ts +173 -0
  264. package/src/methods/saml-sp/slo-initiate.ts +177 -0
  265. package/src/methods/saml-sp/sls.ts +306 -0
  266. package/src/methods/saml-sp/types.ts +203 -0
  267. package/src/ports/CONSISTENCY.md +77 -21
  268. package/src/ports/audit-log.ts +42 -0
  269. package/src/ports/session-store.ts +61 -0
  270. package/src/ports/token-store.ts +13 -0
  271. package/src/types/authorization.ts +25 -0
  272. package/src/types/error.ts +23 -0
  273. package/src/types/flow.ts +10 -0
  274. package/src/types/idp.ts +170 -0
  275. package/src/types/method.ts +110 -0
  276. package/src/types/tenant.ts +37 -0
  277. 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 bearer = extractBearer(c.req.header("authorization") ?? null)
13
- if (!bearer) {
14
- const r = new Response(
15
- JSON.stringify({
16
- error: "invalid_token",
17
- error_description: "missing bearer token",
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 res = await userinfo(bearer, {
31
- keyStore: deps.keyStore,
32
- issuerUrl: c.get("issuerUrl"),
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
- // OIDC userinfo returns 401 on invalid token (vs. 400 elsewhere).
36
- const err = res.error
37
- return new Response(
38
- JSON.stringify({
39
- error: "invalid_token",
40
- error_description: err.description,
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 extractBearer(header: string | null): string | null {
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
- return m?.[1] ?? null
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
  }
@@ -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 { SessionRecord, SessionStore } from "./ports/session-store"
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
  }