@udibo/oauth2 0.1.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 (375) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +174 -0
  3. package/SECURITY.md +77 -0
  4. package/esm/_dnt.polyfills.d.ts +17 -0
  5. package/esm/_dnt.polyfills.js +15 -0
  6. package/esm/adapters/hono/_common.d.ts +66 -0
  7. package/esm/adapters/hono/_common.js +108 -0
  8. package/esm/adapters/hono/authorization-server.d.ts +211 -0
  9. package/esm/adapters/hono/authorization-server.js +214 -0
  10. package/esm/adapters/hono/bff/auth-request-store.d.ts +164 -0
  11. package/esm/adapters/hono/bff/auth-request-store.js +154 -0
  12. package/esm/adapters/hono/bff/bff.d.ts +852 -0
  13. package/esm/adapters/hono/bff/bff.js +1194 -0
  14. package/esm/adapters/hono/bff/cookie-prefix.d.ts +72 -0
  15. package/esm/adapters/hono/bff/cookie-prefix.js +85 -0
  16. package/esm/adapters/hono/bff/mod.d.ts +19 -0
  17. package/esm/adapters/hono/bff/mod.js +19 -0
  18. package/esm/adapters/hono/bff/proxy.d.ts +108 -0
  19. package/esm/adapters/hono/bff/proxy.js +314 -0
  20. package/esm/adapters/hono/bff/session-store.d.ts +250 -0
  21. package/esm/adapters/hono/bff/session-store.js +216 -0
  22. package/esm/adapters/hono/bff/testing.d.ts +165 -0
  23. package/esm/adapters/hono/bff/testing.js +254 -0
  24. package/esm/adapters/hono/identity.d.ts +117 -0
  25. package/esm/adapters/hono/identity.js +215 -0
  26. package/esm/adapters/hono/log.d.ts +49 -0
  27. package/esm/adapters/hono/log.js +66 -0
  28. package/esm/adapters/hono/resource-server.d.ts +101 -0
  29. package/esm/adapters/hono/resource-server.js +108 -0
  30. package/esm/client/_http.d.ts +91 -0
  31. package/esm/client/_http.js +303 -0
  32. package/esm/client/base.d.ts +184 -0
  33. package/esm/client/base.js +93 -0
  34. package/esm/client/bff-client.d.ts +235 -0
  35. package/esm/client/bff-client.js +280 -0
  36. package/esm/client/browser-storage.d.ts +152 -0
  37. package/esm/client/browser-storage.js +246 -0
  38. package/esm/client/check.d.ts +114 -0
  39. package/esm/client/check.js +97 -0
  40. package/esm/client/direct-client.d.ts +707 -0
  41. package/esm/client/direct-client.js +1237 -0
  42. package/esm/client/discovery-cache.d.ts +138 -0
  43. package/esm/client/discovery-cache.js +124 -0
  44. package/esm/client/events.d.ts +54 -0
  45. package/esm/client/events.js +37 -0
  46. package/esm/client/mod.d.ts +35 -0
  47. package/esm/client/mod.js +33 -0
  48. package/esm/client/storage.d.ts +127 -0
  49. package/esm/client/storage.js +81 -0
  50. package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.d.ts +29 -0
  51. package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.js +47 -0
  52. package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.d.ts +28 -0
  53. package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.js +65 -0
  54. package/esm/deps/jsr.io/@std/assert/1.0.19/assert.d.ts +16 -0
  55. package/esm/deps/jsr.io/@std/assert/1.0.19/assert.js +23 -0
  56. package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.d.ts +25 -0
  57. package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.js +30 -0
  58. package/esm/deps/jsr.io/@std/assert/1.0.19/equal.d.ts +24 -0
  59. package/esm/deps/jsr.io/@std/assert/1.0.19/equal.js +193 -0
  60. package/esm/deps/jsr.io/@std/assert/1.0.19/equals.d.ts +36 -0
  61. package/esm/deps/jsr.io/@std/assert/1.0.19/equals.js +61 -0
  62. package/esm/deps/jsr.io/@std/assert/1.0.19/exists.d.ts +17 -0
  63. package/esm/deps/jsr.io/@std/assert/1.0.19/exists.js +27 -0
  64. package/esm/deps/jsr.io/@std/assert/1.0.19/fail.d.ts +14 -0
  65. package/esm/deps/jsr.io/@std/assert/1.0.19/fail.js +20 -0
  66. package/esm/deps/jsr.io/@std/assert/1.0.19/false.d.ts +18 -0
  67. package/esm/deps/jsr.io/@std/assert/1.0.19/false.js +23 -0
  68. package/esm/deps/jsr.io/@std/assert/1.0.19/greater.d.ts +19 -0
  69. package/esm/deps/jsr.io/@std/assert/1.0.19/greater.js +29 -0
  70. package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.d.ts +19 -0
  71. package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.js +29 -0
  72. package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.d.ts +22 -0
  73. package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.js +52 -0
  74. package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.d.ts +24 -0
  75. package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.js +53 -0
  76. package/esm/deps/jsr.io/@std/assert/1.0.19/less.d.ts +18 -0
  77. package/esm/deps/jsr.io/@std/assert/1.0.19/less.js +28 -0
  78. package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.d.ts +19 -0
  79. package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.js +29 -0
  80. package/esm/deps/jsr.io/@std/assert/1.0.19/match.d.ts +17 -0
  81. package/esm/deps/jsr.io/@std/assert/1.0.19/match.js +26 -0
  82. package/esm/deps/jsr.io/@std/assert/1.0.19/mod.d.ts +43 -0
  83. package/esm/deps/jsr.io/@std/assert/1.0.19/mod.js +45 -0
  84. package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.d.ts +26 -0
  85. package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.js +39 -0
  86. package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.d.ts +19 -0
  87. package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.js +29 -0
  88. package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.d.ts +17 -0
  89. package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.js +26 -0
  90. package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.d.ts +22 -0
  91. package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.js +32 -0
  92. package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.d.ts +28 -0
  93. package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.js +177 -0
  94. package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.d.ts +41 -0
  95. package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.js +53 -0
  96. package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.d.ts +23 -0
  97. package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.js +58 -0
  98. package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.d.ts +17 -0
  99. package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.js +26 -0
  100. package/esm/deps/jsr.io/@std/assert/1.0.19/throws.d.ts +44 -0
  101. package/esm/deps/jsr.io/@std/assert/1.0.19/throws.js +44 -0
  102. package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.d.ts +14 -0
  103. package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.js +20 -0
  104. package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.d.ts +14 -0
  105. package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.js +20 -0
  106. package/esm/deps/jsr.io/@std/async/1.5.0/delay.d.ts +44 -0
  107. package/esm/deps/jsr.io/@std/async/1.5.0/delay.js +84 -0
  108. package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.d.ts +39 -0
  109. package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.js +61 -0
  110. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common16.d.ts +22 -0
  111. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common16.js +51 -0
  112. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common32.d.ts +34 -0
  113. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common32.js +192 -0
  114. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common64.d.ts +34 -0
  115. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common64.js +113 -0
  116. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common_detach.d.ts +3 -0
  117. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common_detach.js +13 -0
  118. package/esm/deps/jsr.io/@std/encoding/1.0.11/_types.d.ts +8 -0
  119. package/esm/deps/jsr.io/@std/encoding/1.0.11/_types.js +2 -0
  120. package/esm/deps/jsr.io/@std/encoding/1.0.11/_validate_binary_like.d.ts +1 -0
  121. package/esm/deps/jsr.io/@std/encoding/1.0.11/_validate_binary_like.js +26 -0
  122. package/esm/deps/jsr.io/@std/encoding/1.0.11/ascii85.d.ts +60 -0
  123. package/esm/deps/jsr.io/@std/encoding/1.0.11/ascii85.js +152 -0
  124. package/esm/deps/jsr.io/@std/encoding/1.0.11/base32.d.ts +39 -0
  125. package/esm/deps/jsr.io/@std/encoding/1.0.11/base32.js +87 -0
  126. package/esm/deps/jsr.io/@std/encoding/1.0.11/base58.d.ts +39 -0
  127. package/esm/deps/jsr.io/@std/encoding/1.0.11/base58.js +131 -0
  128. package/esm/deps/jsr.io/@std/encoding/1.0.11/base64.d.ts +39 -0
  129. package/esm/deps/jsr.io/@std/encoding/1.0.11/base64.js +82 -0
  130. package/esm/deps/jsr.io/@std/encoding/1.0.11/base64url.d.ts +39 -0
  131. package/esm/deps/jsr.io/@std/encoding/1.0.11/base64url.js +72 -0
  132. package/esm/deps/jsr.io/@std/encoding/1.0.11/hex.d.ts +38 -0
  133. package/esm/deps/jsr.io/@std/encoding/1.0.11/hex.js +87 -0
  134. package/esm/deps/jsr.io/@std/encoding/1.0.11/mod.d.ts +97 -0
  135. package/esm/deps/jsr.io/@std/encoding/1.0.11/mod.js +99 -0
  136. package/esm/deps/jsr.io/@std/encoding/1.0.11/varint.d.ts +119 -0
  137. package/esm/deps/jsr.io/@std/encoding/1.0.11/varint.js +209 -0
  138. package/esm/deps/jsr.io/@std/http/1.1.3/status.d.ts +343 -0
  139. package/esm/deps/jsr.io/@std/http/1.1.3/status.js +343 -0
  140. package/esm/deps/jsr.io/@std/internal/1.0.14/assertion_state.d.ts +159 -0
  141. package/esm/deps/jsr.io/@std/internal/1.0.14/assertion_state.js +223 -0
  142. package/esm/deps/jsr.io/@std/internal/1.0.14/build_message.d.ts +79 -0
  143. package/esm/deps/jsr.io/@std/internal/1.0.14/build_message.js +116 -0
  144. package/esm/deps/jsr.io/@std/internal/1.0.14/diff.d.ts +139 -0
  145. package/esm/deps/jsr.io/@std/internal/1.0.14/diff.js +277 -0
  146. package/esm/deps/jsr.io/@std/internal/1.0.14/diff_str.d.ts +98 -0
  147. package/esm/deps/jsr.io/@std/internal/1.0.14/diff_str.js +181 -0
  148. package/esm/deps/jsr.io/@std/internal/1.0.14/format.d.ts +29 -0
  149. package/esm/deps/jsr.io/@std/internal/1.0.14/format.js +68 -0
  150. package/esm/deps/jsr.io/@std/internal/1.0.14/styles.d.ts +158 -0
  151. package/esm/deps/jsr.io/@std/internal/1.0.14/styles.js +206 -0
  152. package/esm/deps/jsr.io/@std/internal/1.0.14/types.d.ts +24 -0
  153. package/esm/deps/jsr.io/@std/internal/1.0.14/types.js +3 -0
  154. package/esm/deps/jsr.io/@std/testing/1.0.20/_test_suite.d.ts +91 -0
  155. package/esm/deps/jsr.io/@std/testing/1.0.20/_test_suite.js +347 -0
  156. package/esm/deps/jsr.io/@std/testing/1.0.20/bdd.d.ts +382 -0
  157. package/esm/deps/jsr.io/@std/testing/1.0.20/bdd.js +1110 -0
  158. package/esm/deps/jsr.io/@udibo/http-error/0.11.1/mod.d.ts +363 -0
  159. package/esm/deps/jsr.io/@udibo/http-error/0.11.1/mod.js +616 -0
  160. package/esm/errors.d.ts +203 -0
  161. package/esm/errors.js +287 -0
  162. package/esm/identity/captcha.d.ts +147 -0
  163. package/esm/identity/captcha.js +64 -0
  164. package/esm/identity/delivery.d.ts +96 -0
  165. package/esm/identity/delivery.js +41 -0
  166. package/esm/identity/errors.d.ts +39 -0
  167. package/esm/identity/errors.js +46 -0
  168. package/esm/identity/events.d.ts +242 -0
  169. package/esm/identity/events.js +39 -0
  170. package/esm/identity/external/_shared.d.ts +86 -0
  171. package/esm/identity/external/_shared.js +107 -0
  172. package/esm/identity/external/_token-exchange.d.ts +61 -0
  173. package/esm/identity/external/_token-exchange.js +179 -0
  174. package/esm/identity/external/apple-client-secret.d.ts +82 -0
  175. package/esm/identity/external/apple-client-secret.js +133 -0
  176. package/esm/identity/external/apple.d.ts +64 -0
  177. package/esm/identity/external/apple.js +248 -0
  178. package/esm/identity/external/discord.d.ts +38 -0
  179. package/esm/identity/external/discord.js +62 -0
  180. package/esm/identity/external/errors.d.ts +48 -0
  181. package/esm/identity/external/errors.js +36 -0
  182. package/esm/identity/external/flow.d.ts +107 -0
  183. package/esm/identity/external/flow.js +158 -0
  184. package/esm/identity/external/github.d.ts +42 -0
  185. package/esm/identity/external/github.js +164 -0
  186. package/esm/identity/external/google.d.ts +52 -0
  187. package/esm/identity/external/google.js +38 -0
  188. package/esm/identity/external/mod.d.ts +63 -0
  189. package/esm/identity/external/mod.js +61 -0
  190. package/esm/identity/external/oauth2.d.ts +110 -0
  191. package/esm/identity/external/oauth2.js +158 -0
  192. package/esm/identity/external/oidc.d.ts +81 -0
  193. package/esm/identity/external/oidc.js +250 -0
  194. package/esm/identity/external/provider.d.ts +132 -0
  195. package/esm/identity/external/provider.js +12 -0
  196. package/esm/identity/hibp.d.ts +67 -0
  197. package/esm/identity/hibp.js +87 -0
  198. package/esm/identity/identifier.d.ts +44 -0
  199. package/esm/identity/identifier.js +47 -0
  200. package/esm/identity/lockout.d.ts +155 -0
  201. package/esm/identity/lockout.js +117 -0
  202. package/esm/identity/mfa/mod.d.ts +42 -0
  203. package/esm/identity/mfa/mod.js +42 -0
  204. package/esm/identity/mfa/recovery-codes.d.ts +96 -0
  205. package/esm/identity/mfa/recovery-codes.js +80 -0
  206. package/esm/identity/mfa/service.d.ts +338 -0
  207. package/esm/identity/mfa/service.js +351 -0
  208. package/esm/identity/mfa/totp.d.ts +186 -0
  209. package/esm/identity/mfa/totp.js +194 -0
  210. package/esm/identity/migration.d.ts +155 -0
  211. package/esm/identity/migration.js +243 -0
  212. package/esm/identity/mod.d.ts +65 -0
  213. package/esm/identity/mod.js +64 -0
  214. package/esm/identity/otp.d.ts +255 -0
  215. package/esm/identity/otp.js +245 -0
  216. package/esm/identity/password-policy.d.ts +45 -0
  217. package/esm/identity/password-policy.js +60 -0
  218. package/esm/identity/password.d.ts +171 -0
  219. package/esm/identity/password.js +160 -0
  220. package/esm/identity/rate-limit.d.ts +192 -0
  221. package/esm/identity/rate-limit.js +168 -0
  222. package/esm/identity/service.d.ts +579 -0
  223. package/esm/identity/service.js +835 -0
  224. package/esm/identity/session.d.ts +122 -0
  225. package/esm/identity/session.js +48 -0
  226. package/esm/identity/token-flow.d.ts +214 -0
  227. package/esm/identity/token-flow.js +173 -0
  228. package/esm/models/authentication.d.ts +24 -0
  229. package/esm/models/authentication.js +1 -0
  230. package/esm/models/authorization-code.d.ts +63 -0
  231. package/esm/models/authorization-code.js +1 -0
  232. package/esm/models/authorization.d.ts +165 -0
  233. package/esm/models/authorization.js +160 -0
  234. package/esm/models/client.d.ts +78 -0
  235. package/esm/models/client.js +1 -0
  236. package/esm/models/device-authorization.d.ts +30 -0
  237. package/esm/models/device-authorization.js +1 -0
  238. package/esm/models/responses.d.ts +93 -0
  239. package/esm/models/responses.js +1 -0
  240. package/esm/models/scope.d.ts +81 -0
  241. package/esm/models/scope.js +125 -0
  242. package/esm/models/token.d.ts +135 -0
  243. package/esm/models/token.js +1 -0
  244. package/esm/package.json +3 -0
  245. package/esm/react/callback.d.ts +52 -0
  246. package/esm/react/callback.js +71 -0
  247. package/esm/react/components/class-names.d.ts +91 -0
  248. package/esm/react/components/class-names.js +118 -0
  249. package/esm/react/components/internal.d.ts +62 -0
  250. package/esm/react/components/internal.js +102 -0
  251. package/esm/react/components/mfa-challenge-form.d.ts +66 -0
  252. package/esm/react/components/mfa-challenge-form.js +46 -0
  253. package/esm/react/components/mfa-enrollment-form.d.ts +83 -0
  254. package/esm/react/components/mfa-enrollment-form.js +41 -0
  255. package/esm/react/components/mod.d.ts +103 -0
  256. package/esm/react/components/mod.js +93 -0
  257. package/esm/react/components/request-password-reset-form.d.ts +61 -0
  258. package/esm/react/components/request-password-reset-form.js +41 -0
  259. package/esm/react/components/reset-password-form.d.ts +66 -0
  260. package/esm/react/components/reset-password-form.js +56 -0
  261. package/esm/react/components/sign-in-form.d.ts +74 -0
  262. package/esm/react/components/sign-in-form.js +43 -0
  263. package/esm/react/components/sign-up-form.d.ts +85 -0
  264. package/esm/react/components/sign-up-form.js +57 -0
  265. package/esm/react/components/types.d.ts +208 -0
  266. package/esm/react/components/types.js +14 -0
  267. package/esm/react/components/use-auth-form.d.ts +118 -0
  268. package/esm/react/components/use-auth-form.js +172 -0
  269. package/esm/react/components/user-menu.d.ts +79 -0
  270. package/esm/react/components/user-menu.js +73 -0
  271. package/esm/react/context.d.ts +51 -0
  272. package/esm/react/context.js +16 -0
  273. package/esm/react/mod.d.ts +26 -0
  274. package/esm/react/mod.js +26 -0
  275. package/esm/react/provider.d.ts +40 -0
  276. package/esm/react/provider.js +189 -0
  277. package/esm/react/require-auth.d.ts +31 -0
  278. package/esm/react/require-auth.js +37 -0
  279. package/esm/react/testing.d.ts +322 -0
  280. package/esm/react/testing.js +461 -0
  281. package/esm/react/use-authorization.d.ts +31 -0
  282. package/esm/react/use-authorization.js +36 -0
  283. package/esm/react/use-oauth2.d.ts +112 -0
  284. package/esm/react/use-oauth2.js +127 -0
  285. package/esm/server/authorization-server.d.ts +736 -0
  286. package/esm/server/authorization-server.js +1304 -0
  287. package/esm/server/client-authentication.d.ts +28 -0
  288. package/esm/server/client-authentication.js +51 -0
  289. package/esm/server/grants/authorization-code.d.ts +179 -0
  290. package/esm/server/grants/authorization-code.js +232 -0
  291. package/esm/server/grants/client-credentials.d.ts +52 -0
  292. package/esm/server/grants/client-credentials.js +53 -0
  293. package/esm/server/grants/device-authorization.d.ts +103 -0
  294. package/esm/server/grants/device-authorization.js +156 -0
  295. package/esm/server/grants/grant.d.ts +165 -0
  296. package/esm/server/grants/grant.js +173 -0
  297. package/esm/server/grants/password.d.ts +80 -0
  298. package/esm/server/grants/password.js +92 -0
  299. package/esm/server/grants/refresh-token.d.ts +80 -0
  300. package/esm/server/grants/refresh-token.js +136 -0
  301. package/esm/server/introspection-token-reader.d.ts +153 -0
  302. package/esm/server/introspection-token-reader.js +164 -0
  303. package/esm/server/jwks-token-reader.d.ts +263 -0
  304. package/esm/server/jwks-token-reader.js +508 -0
  305. package/esm/server/mod.d.ts +23 -0
  306. package/esm/server/mod.js +15 -0
  307. package/esm/server/public-suffix/mod.d.ts +62 -0
  308. package/esm/server/public-suffix/mod.js +103 -0
  309. package/esm/server/public-suffix/rules.d.ts +23 -0
  310. package/esm/server/public-suffix/rules.js +23 -0
  311. package/esm/server/redirect-uri.d.ts +187 -0
  312. package/esm/server/redirect-uri.js +331 -0
  313. package/esm/server/resource-server.d.ts +286 -0
  314. package/esm/server/resource-server.js +365 -0
  315. package/esm/server/services/authorization-code.d.ts +84 -0
  316. package/esm/server/services/authorization-code.js +35 -0
  317. package/esm/server/services/client.d.ts +35 -0
  318. package/esm/server/services/client.js +1 -0
  319. package/esm/server/services/device-authorization.d.ts +134 -0
  320. package/esm/server/services/device-authorization.js +7 -0
  321. package/esm/server/services/token.d.ts +242 -0
  322. package/esm/server/services/token.js +78 -0
  323. package/esm/server/services/user.d.ts +29 -0
  324. package/esm/server/services/user.js +7 -0
  325. package/esm/server/signing-keys.d.ts +201 -0
  326. package/esm/server/signing-keys.js +225 -0
  327. package/esm/server/utils/hash.d.ts +6 -0
  328. package/esm/server/utils/hash.js +11 -0
  329. package/esm/testing/contract/authorization-code.d.ts +37 -0
  330. package/esm/testing/contract/authorization-code.js +93 -0
  331. package/esm/testing/contract/client.d.ts +85 -0
  332. package/esm/testing/contract/client.js +126 -0
  333. package/esm/testing/contract/device-authorization.d.ts +37 -0
  334. package/esm/testing/contract/device-authorization.js +193 -0
  335. package/esm/testing/contract/lockout-store.d.ts +47 -0
  336. package/esm/testing/contract/lockout-store.js +122 -0
  337. package/esm/testing/contract/mfa-store.d.ts +50 -0
  338. package/esm/testing/contract/mfa-store.js +226 -0
  339. package/esm/testing/contract/mod.d.ts +39 -0
  340. package/esm/testing/contract/mod.js +39 -0
  341. package/esm/testing/contract/otp-store.d.ts +48 -0
  342. package/esm/testing/contract/otp-store.js +197 -0
  343. package/esm/testing/contract/rate-limit-store.d.ts +52 -0
  344. package/esm/testing/contract/rate-limit-store.js +105 -0
  345. package/esm/testing/contract/token-flow-store.d.ts +54 -0
  346. package/esm/testing/contract/token-flow-store.js +179 -0
  347. package/esm/testing/contract/token-reader.d.ts +77 -0
  348. package/esm/testing/contract/token-reader.js +78 -0
  349. package/esm/testing/contract/token.d.ts +61 -0
  350. package/esm/testing/contract/token.js +287 -0
  351. package/esm/testing/contract/user.d.ts +63 -0
  352. package/esm/testing/contract/user.js +75 -0
  353. package/esm/testing/mod.d.ts +34 -0
  354. package/esm/testing/mod.js +34 -0
  355. package/esm/testing/server.d.ts +197 -0
  356. package/esm/testing/server.js +193 -0
  357. package/esm/testing/services.d.ts +373 -0
  358. package/esm/testing/services.js +703 -0
  359. package/esm/utils/_buffer.d.ts +10 -0
  360. package/esm/utils/_buffer.js +12 -0
  361. package/esm/utils/_timing-safe.d.ts +13 -0
  362. package/esm/utils/_timing-safe.js +22 -0
  363. package/esm/utils/authentication-context.d.ts +3 -0
  364. package/esm/utils/authentication-context.js +14 -0
  365. package/esm/utils/basic-auth.d.ts +50 -0
  366. package/esm/utils/basic-auth.js +106 -0
  367. package/esm/utils/crypto.d.ts +97 -0
  368. package/esm/utils/crypto.js +162 -0
  369. package/esm/utils/pkce.d.ts +151 -0
  370. package/esm/utils/pkce.js +179 -0
  371. package/esm/utils/text.d.ts +24 -0
  372. package/esm/utils/text.js +28 -0
  373. package/esm/utils/url.d.ts +66 -0
  374. package/esm/utils/url.js +90 -0
  375. package/package.json +92 -0
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Internal helpers shared by the external-provider connectors.
3
+ *
4
+ * Not part of the public API — reach for a connector from
5
+ * `@udibo/oauth2/identity/external` instead, which re-exports the one type an
6
+ * adopter configures ({@link AzpPolicy}). These live here so the same failure
7
+ * reads the same way whichever connector hit it: one error renderer, one
8
+ * error-body reader, one provider-text sanitizer, and one id_token claim gate
9
+ * rather than a copy per provider. The token exchange itself is
10
+ * `_token-exchange.ts`.
11
+ *
12
+ * @module
13
+ */
14
+ import { isOAuth2Error } from "../../errors.js";
15
+ import { timingSafeEqualString } from "../../utils/crypto.js";
16
+ import { PROVIDER_TEXT_MAX_LENGTH, sanitizeProviderText, } from "../../utils/text.js";
17
+ import { ExternalAuthError } from "./errors.js";
18
+ export { PROVIDER_TEXT_MAX_LENGTH, sanitizeProviderText };
19
+ /**
20
+ * Clock-skew allowance, in seconds, when checking an id_token's `exp` claim.
21
+ * The default for {@link assertIdTokenClaims}.
22
+ */
23
+ export const ID_TOKEN_EXPIRY_LEEWAY_SECONDS = 120;
24
+ /**
25
+ * Renders a thrown value for an error message, unwrapping an OAuth2 error into
26
+ * its `error` / `error_description` extensions so a provider-reported failure
27
+ * is quoted rather than buried in a generic message. Every branch is
28
+ * {@link sanitizeProviderText}-bounded: the thrown value routinely carries
29
+ * provider bytes — a JSON parse error quotes the body it choked on, and an
30
+ * OAuth2 error carries a description the provider wrote.
31
+ */
32
+ export function describeError(error) {
33
+ if (isOAuth2Error(error)) {
34
+ const code = sanitizeProviderText(error.extensions.error ?? "server_error");
35
+ const description = sanitizeProviderText(error.extensions.error_description ?? error.message);
36
+ return description && description !== code
37
+ ? `${code}: ${description}`
38
+ : code;
39
+ }
40
+ return sanitizeProviderText(error instanceof Error ? error.message : String(error));
41
+ }
42
+ /**
43
+ * Reads a failed response's body into a bounded, single-line `": <text>"`
44
+ * suffix to append to an error message, or `""` when the body is empty or
45
+ * unreadable. Consumes the body, so the caller must not read it again.
46
+ */
47
+ export async function readErrorBody(res) {
48
+ try {
49
+ const snippet = sanitizeProviderText(await res.text());
50
+ return snippet ? `: ${snippet}` : "";
51
+ }
52
+ catch {
53
+ return "";
54
+ }
55
+ }
56
+ /**
57
+ * Drops one trailing `"/"` so issuer URLs compare and concatenate consistently
58
+ * (`"https://x/"` and `"https://x"` identify the same issuer).
59
+ */
60
+ export function stripTrailingSlash(value) {
61
+ return value.endsWith("/") ? value.slice(0, -1) : value;
62
+ }
63
+ /**
64
+ * Enforces the id_token claim rules of OpenID Connect Core §3.1.3.7 that do not
65
+ * depend on the signature: `iss`, `aud`, `azp`, `exp`, and `nonce`, in that
66
+ * order. Callers verify the signature (or establish the token's provenance)
67
+ * themselves before calling this.
68
+ *
69
+ * Throws {@link ExternalAuthError} with code `nonce_mismatch` when the `nonce`
70
+ * claim does not match, and `provider_error` for every other rule.
71
+ */
72
+ export function assertIdTokenClaims(input) {
73
+ const { provider, claims, issuer, clientId, expectedNonce } = input;
74
+ if (claims.iss !== issuer) {
75
+ throw new ExternalAuthError(provider, "provider_error", `id_token "iss" is "${String(claims.iss)}" but the expected issuer is ` +
76
+ `"${issuer}" — the token was issued by a different provider, or the ` +
77
+ `configured issuer points at a proxy or the wrong tenant.`);
78
+ }
79
+ const aud = claims.aud;
80
+ const audienceOk = aud === clientId ||
81
+ (Array.isArray(aud) && aud.includes(clientId));
82
+ if (!audienceOk) {
83
+ throw new ExternalAuthError(provider, "provider_error", `id_token "aud" (${JSON.stringify(aud)}) does not include clientId ` +
84
+ `"${clientId}" — the token was issued to a different client ` +
85
+ `registration.`);
86
+ }
87
+ const audienceCount = Array.isArray(aud) ? aud.length : 1;
88
+ if ((input.azp ?? "conditional") === "conditional" &&
89
+ (audienceCount > 1 || claims.azp !== undefined) &&
90
+ claims.azp !== clientId) {
91
+ throw new ExternalAuthError(provider, "provider_error", `id_token "azp" (${JSON.stringify(claims.azp)}) is not this clientId ` +
92
+ `"${clientId}" — the token's authorized party is a different client ` +
93
+ `(OpenID Connect Core §3.1.3.7).`);
94
+ }
95
+ const exp = claims.exp;
96
+ const leewaySeconds = input.leewaySeconds ?? ID_TOKEN_EXPIRY_LEEWAY_SECONDS;
97
+ if (typeof exp !== "number" || Date.now() / 1000 > exp + leewaySeconds) {
98
+ throw new ExternalAuthError(provider, "provider_error", `id_token is expired or has no "exp" claim (exp=${JSON.stringify(exp)}) — reject per OpenID Connect Core §3.1.3.7. Ask the user to start ` +
99
+ `again.`);
100
+ }
101
+ if (expectedNonce !== undefined &&
102
+ !(typeof claims.nonce === "string" &&
103
+ timingSafeEqualString(claims.nonce, expectedNonce))) {
104
+ throw new ExternalAuthError(provider, "nonce_mismatch", `id_token "nonce" does not match this sign-in attempt — possible token ` +
105
+ `replay or a mixed-up callback. Ask the user to start again.`);
106
+ }
107
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The `authorization_code` token exchange the plain-OAuth2 connectors post
3
+ * through — {@link githubProvider}, {@link oauth2Provider} (and its
4
+ * {@link discordProvider} preset), and {@link appleProvider}.
5
+ * {@link oidcProvider}, and {@link googleProvider} on top of it, exchange via
6
+ * `OAuth2Client` instead and do not reach this module.
7
+ *
8
+ * Not part of the public API. Split out of `_shared.ts` so the ladder — the one
9
+ * place a provider's bytes become an error message or a token — sits next to
10
+ * the tests that pin it.
11
+ *
12
+ * @module
13
+ */
14
+ /** Response bytes accepted from a token endpoint before the exchange is refused. */
15
+ export declare const MAX_TOKEN_RESPONSE_BYTES: number;
16
+ /** Wall-clock budget for the exchange, covering the connect and the body read. */
17
+ export declare const TOKEN_EXCHANGE_TIMEOUT_MS = 10000;
18
+ /** Input for {@link runTokenExchange}. */
19
+ export interface TokenExchangeInput {
20
+ /** Provider id carried on every thrown {@link ExternalAuthError}. */
21
+ provider: string;
22
+ /** Human-readable provider name used in the error messages. */
23
+ displayName: string;
24
+ /** The provider's token endpoint. */
25
+ endpoint: string;
26
+ /** The form body to POST; client authentication must already be in it. */
27
+ body: URLSearchParams;
28
+ /**
29
+ * The token-response field the connector cannot proceed without — usually
30
+ * `"access_token"`, or `"id_token"` for a connector that only reads claims.
31
+ */
32
+ requiredField: string;
33
+ /**
34
+ * Fetch implementation for the exchange. A wrapper **must forward the whole
35
+ * `RequestInit`** — `redirect` and `signal` in particular. They are the only
36
+ * channel the redirect refusal and the deadline reach the transport, so a
37
+ * wrapper that rebuilds `init` from scratch silently reopens the credential
38
+ * replay hole. `createGuardedFetch` forwards both.
39
+ */
40
+ fetch: typeof fetch;
41
+ /** Guidance appended when a failure carries no OAuth2 error code. */
42
+ httpErrorHint: string;
43
+ /** Guidance appended for the OAuth2 error code the provider reported. */
44
+ tokenErrorHint: (code: string | undefined) => string;
45
+ /** Exchange deadline. Defaults to {@link TOKEN_EXCHANGE_TIMEOUT_MS}. */
46
+ timeoutMs?: number;
47
+ }
48
+ /** A token response that cleared every step of {@link runTokenExchange}. */
49
+ export interface TokenExchangeResult {
50
+ /** The value of {@link TokenExchangeInput.requiredField}, non-empty. */
51
+ value: string;
52
+ /** The full token-response body, for provider-specific fields. */
53
+ raw: Record<string, unknown>;
54
+ }
55
+ /**
56
+ * Posts the authorization code and returns a token response known to be a JSON
57
+ * object carrying `requiredField`. Every failure throws
58
+ * {@link ExternalAuthError} `provider_error`; a redirect is refused rather than
59
+ * followed, so the client credentials are never replayed to another host.
60
+ */
61
+ export declare function runTokenExchange(input: TokenExchangeInput): Promise<TokenExchangeResult>;
@@ -0,0 +1,179 @@
1
+ /**
2
+ * The `authorization_code` token exchange the plain-OAuth2 connectors post
3
+ * through — {@link githubProvider}, {@link oauth2Provider} (and its
4
+ * {@link discordProvider} preset), and {@link appleProvider}.
5
+ * {@link oidcProvider}, and {@link googleProvider} on top of it, exchange via
6
+ * `OAuth2Client` instead and do not reach this module.
7
+ *
8
+ * Not part of the public API. Split out of `_shared.ts` so the ladder — the one
9
+ * place a provider's bytes become an error message or a token — sits next to
10
+ * the tests that pin it.
11
+ *
12
+ * @module
13
+ */
14
+ import { describeError, sanitizeProviderText } from "./_shared.js";
15
+ import { ExternalAuthError } from "./errors.js";
16
+ /** Response bytes accepted from a token endpoint before the exchange is refused. */
17
+ export const MAX_TOKEN_RESPONSE_BYTES = 64 * 1024;
18
+ /** Wall-clock budget for the exchange, covering the connect and the body read. */
19
+ export const TOKEN_EXCHANGE_TIMEOUT_MS = 10_000;
20
+ /**
21
+ * Posts the authorization code and returns a token response known to be a JSON
22
+ * object carrying `requiredField`. Every failure throws
23
+ * {@link ExternalAuthError} `provider_error`; a redirect is refused rather than
24
+ * followed, so the client credentials are never replayed to another host.
25
+ */
26
+ export async function runTokenExchange(input) {
27
+ const { provider, displayName, requiredField } = input;
28
+ const fail = (message, options) => new ExternalAuthError(provider, "provider_error", message, options);
29
+ let res;
30
+ try {
31
+ res = await input.fetch(input.endpoint, {
32
+ method: "POST",
33
+ headers: {
34
+ Accept: "application/json",
35
+ "Content-Type": "application/x-www-form-urlencoded",
36
+ },
37
+ body: input.body,
38
+ redirect: "manual",
39
+ signal: AbortSignal.timeout(input.timeoutMs ?? TOKEN_EXCHANGE_TIMEOUT_MS),
40
+ });
41
+ }
42
+ catch (error) {
43
+ throw fail(`could not reach the ${displayName} token endpoint ` +
44
+ `(${describeError(error)}). Try again.`, { cause: error });
45
+ }
46
+ if (isRedirect(res)) {
47
+ await res.body?.cancel();
48
+ throw fail(`token endpoint answered with a redirect (HTTP ${res.status}), which ` +
49
+ `this exchange refuses to follow — replaying the client credentials ` +
50
+ `to another host would leak them. Point the connector at the ` +
51
+ `${displayName} token endpoint directly.`);
52
+ }
53
+ let body = { text: "", oversized: false };
54
+ let readError;
55
+ try {
56
+ body = await readBoundedBody(res);
57
+ }
58
+ catch (error) {
59
+ readError = error;
60
+ }
61
+ if (!res.ok) {
62
+ const reported = reportedOAuth2Error(safeParseJson(body.text));
63
+ if (reported) {
64
+ throw fail(`token endpoint responded with HTTP ${res.status}: ` +
65
+ `"${reported.code}"${reported.description}${reported.uri}. ` +
66
+ `${input.tokenErrorHint(reported.code)} ${input.httpErrorHint}`);
67
+ }
68
+ throw fail(`token endpoint responded with HTTP ${res.status}` +
69
+ `${errorBodySuffix(body.text)}${body.oversized ? " (truncated)" : ""}` +
70
+ `. ${input.httpErrorHint}`);
71
+ }
72
+ if (readError) {
73
+ throw fail(`the ${displayName} token endpoint response could not be read ` +
74
+ `(${describeError(readError)}).`, { cause: readError });
75
+ }
76
+ if (body.oversized) {
77
+ throw fail(`the ${displayName} token endpoint response exceeded ` +
78
+ `${MAX_TOKEN_RESPONSE_BYTES} bytes — refusing to parse a token ` +
79
+ `response this large.`);
80
+ }
81
+ let parsed;
82
+ try {
83
+ parsed = JSON.parse(body.text);
84
+ }
85
+ catch (error) {
86
+ throw fail(`the ${displayName} token endpoint returned ` +
87
+ `${describeMediaType(res)} that was not valid JSON ` +
88
+ `(${describeError(error)}).`, { cause: error });
89
+ }
90
+ if (!isJsonObject(parsed)) {
91
+ throw fail(`the ${displayName} token endpoint returned ` +
92
+ `${describeJsonValue(parsed)} where a JSON object was required.`);
93
+ }
94
+ const reported = reportedOAuth2Error(parsed);
95
+ const value = parsed[requiredField];
96
+ if (reported || typeof value !== "string" || value.length === 0) {
97
+ const code = reported?.code ?? `missing ${requiredField}`;
98
+ throw fail(`token exchange failed: "${code}"${reported?.description ?? ""}` +
99
+ `${reported?.uri ?? ""}. ${input.tokenErrorHint(reported?.code)}`);
100
+ }
101
+ return { value, raw: parsed };
102
+ }
103
+ async function readBoundedBody(res) {
104
+ if (!res.body)
105
+ return { text: "", oversized: false };
106
+ const reader = res.body.getReader();
107
+ const chunks = [];
108
+ let size = 0;
109
+ let oversized = false;
110
+ try {
111
+ while (true) {
112
+ const { done, value } = await reader.read();
113
+ if (done)
114
+ break;
115
+ if (size + value.byteLength > MAX_TOKEN_RESPONSE_BYTES) {
116
+ oversized = true;
117
+ break;
118
+ }
119
+ chunks.push(value);
120
+ size += value.byteLength;
121
+ }
122
+ }
123
+ finally {
124
+ await reader.cancel().catch(() => { });
125
+ }
126
+ const buffer = new Uint8Array(size);
127
+ let offset = 0;
128
+ for (const chunk of chunks) {
129
+ buffer.set(chunk, offset);
130
+ offset += chunk.byteLength;
131
+ }
132
+ return { text: new TextDecoder().decode(buffer), oversized };
133
+ }
134
+ function reportedOAuth2Error(value) {
135
+ if (!isJsonObject(value))
136
+ return null;
137
+ const code = sanitizeProviderText(asString(value.error) ?? "");
138
+ if (!code)
139
+ return null;
140
+ const description = sanitizeProviderText(asString(value.error_description) ?? "");
141
+ const uri = sanitizeProviderText(asString(value.error_uri) ?? "");
142
+ return {
143
+ code,
144
+ description: description ? ` (${description})` : "",
145
+ uri: uri ? ` [${uri}]` : "",
146
+ };
147
+ }
148
+ function asString(value) {
149
+ return typeof value === "string" ? value : undefined;
150
+ }
151
+ function errorBodySuffix(text) {
152
+ const snippet = sanitizeProviderText(text);
153
+ return snippet ? `: ${snippet}` : "";
154
+ }
155
+ function safeParseJson(text) {
156
+ try {
157
+ return JSON.parse(text);
158
+ }
159
+ catch {
160
+ return undefined;
161
+ }
162
+ }
163
+ function isRedirect(res) {
164
+ return res.status >= 300 && res.status < 400;
165
+ }
166
+ function isJsonObject(value) {
167
+ return typeof value === "object" && value !== null && !Array.isArray(value);
168
+ }
169
+ function describeJsonValue(value) {
170
+ if (value === null)
171
+ return "JSON null";
172
+ if (Array.isArray(value))
173
+ return "a JSON array";
174
+ return `a JSON ${typeof value}`;
175
+ }
176
+ function describeMediaType(res) {
177
+ const contentType = sanitizeProviderText(res.headers.get("content-type")?.split(";")[0] ?? "");
178
+ return contentType ? `a "${contentType}" response` : "a response";
179
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Sign in with Apple client-secret generation. Apple's "client secret" is not a
3
+ * static string but a short-lived ES256 JWT the app signs from its `.p8`
4
+ * private key — {@link generateAppleClientSecret} builds one,
5
+ * {@link createAppleClientSecretFactory} caches and renews it before expiry.
6
+ *
7
+ * @module
8
+ */
9
+ /** Apple's audience for the client-secret JWT. */
10
+ export declare const APPLE_AUDIENCE = "https://appleid.apple.com";
11
+ /** Apple's hard cap on the client secret's lifetime: 6 months, in seconds. */
12
+ export declare const APPLE_CLIENT_SECRET_MAX_TTL_SECONDS = 15777000;
13
+ /** Default client-secret lifetime: ~180 days, comfortably under Apple's cap. */
14
+ export declare const DEFAULT_APPLE_CLIENT_SECRET_TTL_SECONDS = 15552000;
15
+ /** The Apple developer key material a client secret is signed from. */
16
+ export interface AppleClientSecretOptions {
17
+ /**
18
+ * The 10-character Apple Team ID (`iss`). Found top-right in the Apple
19
+ * Developer account membership page — not the App ID.
20
+ */
21
+ teamId: string;
22
+ /**
23
+ * The Key ID (`kid`) of the `.p8` signing key, from Certificates,
24
+ * Identifiers & Profiles → Keys.
25
+ */
26
+ keyId: string;
27
+ /**
28
+ * The Services ID (`sub`), i.e. the client id the token is for — the
29
+ * identifier configured for "Sign in with Apple" on the web, not the app's
30
+ * bundle id.
31
+ */
32
+ clientId: string;
33
+ /**
34
+ * The PKCS#8 PEM contents of the `.p8` private key downloaded from Apple
35
+ * (downloadable once). Both the full `-----BEGIN PRIVATE KEY-----` block and
36
+ * the bare base64 body are accepted.
37
+ */
38
+ privateKey: string;
39
+ /**
40
+ * Lifetime of the generated secret in seconds. Defaults to
41
+ * {@link DEFAULT_APPLE_CLIENT_SECRET_TTL_SECONDS}; must not exceed
42
+ * {@link APPLE_CLIENT_SECRET_MAX_TTL_SECONDS}.
43
+ */
44
+ expiresInSeconds?: number;
45
+ /** Provider id used in error messages. Defaults to `"apple"`. */
46
+ providerId?: string;
47
+ }
48
+ /**
49
+ * Generate a single Sign in with Apple client secret: an ES256 JWT with
50
+ * `iss` = team id, `sub` = client id (Services ID), `aud` = Apple, and an `exp`
51
+ * bounded by {@link APPLE_CLIENT_SECRET_MAX_TTL_SECONDS}. Prefer
52
+ * {@link createAppleClientSecretFactory} in a long-lived process so the secret
53
+ * is cached and renewed rather than re-signed on every sign-in.
54
+ *
55
+ * @throws {ExternalAuthError} `configuration` naming the offending input when
56
+ * the team id / key id / client id is empty, the `.p8` is not a valid P-256
57
+ * key, or the requested TTL exceeds Apple's cap.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * const secret = await generateAppleClientSecret({
62
+ * teamId: "ABCDE12345",
63
+ * keyId: "KEY1234567",
64
+ * clientId: "com.example.web",
65
+ * privateKey: Deno.env.get("APPLE_PRIVATE_KEY")!,
66
+ * });
67
+ * ```
68
+ */
69
+ export declare function generateAppleClientSecret(options: AppleClientSecretOptions): Promise<string>;
70
+ /** A cached client-secret source: returns a valid secret, re-signing lazily. */
71
+ export type AppleClientSecretFactory = () => Promise<string>;
72
+ /**
73
+ * Build a self-renewing Apple client-secret source. It signs a secret with
74
+ * {@link generateAppleClientSecret} on first call, caches it, and re-signs
75
+ * automatically once the cached secret is within `renewBeforeSeconds` of expiry
76
+ * — so a long-lived {@link appleProvider} never presents an expired assertion.
77
+ * The key material is validated on the first call, surfacing a misconfiguration
78
+ * as a `configuration` {@link ExternalAuthError}.
79
+ */
80
+ export declare function createAppleClientSecretFactory(options: AppleClientSecretOptions & {
81
+ renewBeforeSeconds?: number;
82
+ }): AppleClientSecretFactory;
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Sign in with Apple client-secret generation. Apple's "client secret" is not a
3
+ * static string but a short-lived ES256 JWT the app signs from its `.p8`
4
+ * private key — {@link generateAppleClientSecret} builds one,
5
+ * {@link createAppleClientSecretFactory} caches and renews it before expiry.
6
+ *
7
+ * @module
8
+ */
9
+ import { decodeBase64 } from "../../deps/jsr.io/@std/encoding/1.0.11/base64.js";
10
+ import { toArrayBuffer } from "../../utils/_buffer.js";
11
+ import { base64urlEncode } from "../../utils/crypto.js";
12
+ import { ExternalAuthError } from "./errors.js";
13
+ /** Apple's audience for the client-secret JWT. */
14
+ export const APPLE_AUDIENCE = "https://appleid.apple.com";
15
+ /** Apple's hard cap on the client secret's lifetime: 6 months, in seconds. */
16
+ export const APPLE_CLIENT_SECRET_MAX_TTL_SECONDS = 15777000;
17
+ /** Default client-secret lifetime: ~180 days, comfortably under Apple's cap. */
18
+ export const DEFAULT_APPLE_CLIENT_SECRET_TTL_SECONDS = 15552000;
19
+ const encoder = new TextEncoder();
20
+ const ES256_IMPORT = { name: "ECDSA", namedCurve: "P-256" };
21
+ const ES256_SIGN = { name: "ECDSA", hash: "SHA-256" };
22
+ function configError(providerId, message) {
23
+ return new ExternalAuthError(providerId, "configuration", message);
24
+ }
25
+ function requireNonEmpty(providerId, value, field) {
26
+ const trimmed = value?.trim?.() ?? "";
27
+ if (!trimmed) {
28
+ throw configError(providerId, `Apple ${field} is empty — set it from the Apple Developer console.`);
29
+ }
30
+ return trimmed;
31
+ }
32
+ function pemToPkcs8(providerId, pem) {
33
+ const body = pem
34
+ .replace(/-----BEGIN [^-]+-----/g, "")
35
+ .replace(/-----END [^-]+-----/g, "")
36
+ .replace(/\s+/g, "");
37
+ if (!body) {
38
+ throw configError(providerId, `the Apple private key (.p8) is empty. Paste the PKCS#8 PEM contents ` +
39
+ `of the AuthKey_*.p8 file Apple lets you download once.`);
40
+ }
41
+ try {
42
+ return decodeBase64(body);
43
+ }
44
+ catch (error) {
45
+ throw configError(providerId, `the Apple private key (.p8) is not valid base64/PEM ` +
46
+ `(${error instanceof Error ? error.message : String(error)}).`);
47
+ }
48
+ }
49
+ async function importPrivateKey(providerId, pem) {
50
+ const der = pemToPkcs8(providerId, pem);
51
+ try {
52
+ return await crypto.subtle.importKey("pkcs8", toArrayBuffer(der), ES256_IMPORT, false, ["sign"]);
53
+ }
54
+ catch (error) {
55
+ throw configError(providerId, `the Apple private key (.p8) could not be imported as a P-256 (ES256) ` +
56
+ `key (${error instanceof Error ? error.message : String(error)}). ` +
57
+ `Confirm you downloaded a "Sign in with Apple" key and pasted it ` +
58
+ `unmodified.`);
59
+ }
60
+ }
61
+ /**
62
+ * Generate a single Sign in with Apple client secret: an ES256 JWT with
63
+ * `iss` = team id, `sub` = client id (Services ID), `aud` = Apple, and an `exp`
64
+ * bounded by {@link APPLE_CLIENT_SECRET_MAX_TTL_SECONDS}. Prefer
65
+ * {@link createAppleClientSecretFactory} in a long-lived process so the secret
66
+ * is cached and renewed rather than re-signed on every sign-in.
67
+ *
68
+ * @throws {ExternalAuthError} `configuration` naming the offending input when
69
+ * the team id / key id / client id is empty, the `.p8` is not a valid P-256
70
+ * key, or the requested TTL exceeds Apple's cap.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * const secret = await generateAppleClientSecret({
75
+ * teamId: "ABCDE12345",
76
+ * keyId: "KEY1234567",
77
+ * clientId: "com.example.web",
78
+ * privateKey: Deno.env.get("APPLE_PRIVATE_KEY")!,
79
+ * });
80
+ * ```
81
+ */
82
+ export async function generateAppleClientSecret(options) {
83
+ const providerId = options.providerId ?? "apple";
84
+ const teamId = requireNonEmpty(providerId, options.teamId, "team id");
85
+ const keyId = requireNonEmpty(providerId, options.keyId, "key id");
86
+ const clientId = requireNonEmpty(providerId, options.clientId, "client id (Services ID)");
87
+ const ttl = options.expiresInSeconds ??
88
+ DEFAULT_APPLE_CLIENT_SECRET_TTL_SECONDS;
89
+ if (ttl <= 0 || ttl > APPLE_CLIENT_SECRET_MAX_TTL_SECONDS) {
90
+ throw configError(providerId, `Apple client secret TTL must be between 1 and ` +
91
+ `${APPLE_CLIENT_SECRET_MAX_TTL_SECONDS} seconds (6 months); ` +
92
+ `got ${ttl}.`);
93
+ }
94
+ const privateKey = await importPrivateKey(providerId, options.privateKey);
95
+ const now = Math.floor(Date.now() / 1000);
96
+ const header = { alg: "ES256", kid: keyId };
97
+ const payload = {
98
+ iss: teamId,
99
+ iat: now,
100
+ exp: now + ttl,
101
+ aud: APPLE_AUDIENCE,
102
+ sub: clientId,
103
+ };
104
+ const signingInput = `${encodeSegment(header)}.${encodeSegment(payload)}`;
105
+ const signature = await crypto.subtle.sign(ES256_SIGN, privateKey, encoder.encode(signingInput));
106
+ return `${signingInput}.${base64urlEncode(new Uint8Array(signature))}`;
107
+ }
108
+ function encodeSegment(value) {
109
+ return base64urlEncode(encoder.encode(JSON.stringify(value)));
110
+ }
111
+ /**
112
+ * Build a self-renewing Apple client-secret source. It signs a secret with
113
+ * {@link generateAppleClientSecret} on first call, caches it, and re-signs
114
+ * automatically once the cached secret is within `renewBeforeSeconds` of expiry
115
+ * — so a long-lived {@link appleProvider} never presents an expired assertion.
116
+ * The key material is validated on the first call, surfacing a misconfiguration
117
+ * as a `configuration` {@link ExternalAuthError}.
118
+ */
119
+ export function createAppleClientSecretFactory(options) {
120
+ const ttl = options.expiresInSeconds ??
121
+ DEFAULT_APPLE_CLIENT_SECRET_TTL_SECONDS;
122
+ const renewBefore = options.renewBeforeSeconds ?? 60 * 60;
123
+ let cached = null;
124
+ return async () => {
125
+ const now = Math.floor(Date.now() / 1000);
126
+ if (cached && now < cached.expiresAt - renewBefore) {
127
+ return cached.secret;
128
+ }
129
+ const secret = await generateAppleClientSecret(options);
130
+ cached = { secret, expiresAt: now + ttl };
131
+ return secret;
132
+ };
133
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Sign in with Apple connector. Apple is OpenID-Connect-shaped but does not fit
3
+ * {@link oidcProvider}: its client secret is a signed ES256 JWT rather than a
4
+ * static string (see {@link generateAppleClientSecret}), it authenticates the
5
+ * client in the token body (`client_secret_post`), and it requires
6
+ * `response_mode=form_post` whenever profile scopes are requested. This is a
7
+ * dedicated {@link ExternalProvider} that exchanges the code, then verifies the
8
+ * `id_token` signature against Apple's JWKS.
9
+ *
10
+ * The user's name is returned by Apple **only** on the first authorization, in
11
+ * the `form_post` body's `user` field — never in the `id_token`. This connector
12
+ * returns what the `id_token` carries (stable subject, email); capturing the
13
+ * first-authorization name is the callback route's responsibility, since only
14
+ * it sees the form body.
15
+ *
16
+ * @module
17
+ */
18
+ import { type AppleClientSecretFactory, type AppleClientSecretOptions } from "./apple-client-secret.js";
19
+ import type { ExternalProvider } from "./provider.js";
20
+ /** Options for {@link appleProvider}. */
21
+ export interface AppleProviderOptions extends Pick<AppleClientSecretOptions, "teamId" | "keyId" | "privateKey"> {
22
+ /** The Services ID configured for web Sign in with Apple (the `client_id`). */
23
+ clientId: string;
24
+ /** Scopes requested by default. Defaults to `["name", "email"]`. */
25
+ scopes?: string[];
26
+ /**
27
+ * Client-secret lifetime in seconds. Defaults to Apple's practical maximum;
28
+ * see {@link AppleClientSecretOptions.expiresInSeconds}.
29
+ */
30
+ expiresInSeconds?: number;
31
+ /**
32
+ * Override the client-secret source — inject a stub in tests instead of real
33
+ * `.p8` key material. Defaults to a cached factory over the key material.
34
+ */
35
+ clientSecret?: AppleClientSecretFactory;
36
+ /** Fetch implementation for all provider traffic. Defaults to `globalThis.fetch`. */
37
+ fetch?: typeof fetch;
38
+ }
39
+ /**
40
+ * Creates a Sign in with Apple connector with id `"apple"`.
41
+ *
42
+ * The flow requests `response_mode=form_post` (Apple's requirement when `name`
43
+ * or `email` scope is present), exchanges the code at Apple's token endpoint
44
+ * with a freshly-signed client-secret JWT, and verifies the returned
45
+ * `id_token`'s RS256 signature against Apple's published keys before trusting
46
+ * its claims (`iss`, `aud`, `azp`, `exp`, and the `nonce` when one was sent,
47
+ * checked exactly as {@link oidcProvider} checks them). Apple's
48
+ * private-relay addresses (`@privaterelay.appleid.com`) are ordinary verified
49
+ * emails and are returned as-is. The raw `id_token` claims — including
50
+ * `is_private_email` — stay on {@link ExternalProfile.raw}.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * const flow = new ExternalAuthFlow({
55
+ * provider: appleProvider({
56
+ * clientId: "com.example.web",
57
+ * teamId: Deno.env.get("APPLE_TEAM_ID")!,
58
+ * keyId: Deno.env.get("APPLE_KEY_ID")!,
59
+ * privateKey: Deno.env.get("APPLE_PRIVATE_KEY")!,
60
+ * }),
61
+ * });
62
+ * ```
63
+ */
64
+ export declare function appleProvider(options: AppleProviderOptions): ExternalProvider;