@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,225 @@
1
+ /**
2
+ * OIDC signing keys and compact JWS signing — Web Crypto only, no
3
+ * dependencies. Backs id_token issuance, the JWKS endpoint, and the optional
4
+ * signed (JWT) access-token generator.
5
+ *
6
+ * Keys are ES256 (the OIDC-recommended default with broad library support).
7
+ * Generate once with {@link generateSigningKey}, persist via
8
+ * {@link exportSigningKeyJwk}, and load on boot with {@link importSigningKeyJwk}
9
+ * so every instance of a multi-instance deploy signs with the same key (an
10
+ * ephemeral per-instance key would fail verification across instances).
11
+ *
12
+ * @module
13
+ */
14
+ import { base64urlDecode, base64urlEncode } from "../utils/crypto.js";
15
+ import { snapshotAuthenticationContext } from "../utils/authentication-context.js";
16
+ /** A {@link SigningKeyProvider} over one fixed key. */
17
+ export class StaticSigningKeyProvider {
18
+ #key;
19
+ /** Fixes the single key this provider signs with and publishes. */
20
+ constructor(key) {
21
+ this.#key = key;
22
+ }
23
+ /** The fixed key passed at construction. */
24
+ getSigningKey() {
25
+ return Promise.resolve(this.#key);
26
+ }
27
+ /** A JWKS containing only the fixed key's public JWK. */
28
+ getPublicJwks() {
29
+ return Promise.resolve({ keys: [this.#key.publicJwk] });
30
+ }
31
+ }
32
+ /**
33
+ * A {@link SigningKeyProvider} for rotating the signing key through a JWKS
34
+ * grace window: sign with {@link RotatingSigningKeyProviderOptions.current}
35
+ * only, while {@link RotatingSigningKeyProviderOptions.previous} keys stay
36
+ * published so outstanding tokens keep validating until they expire and every
37
+ * consumer has refetched JWKS.
38
+ *
39
+ * Rotation is: promote the new key to `current`, move the old one into
40
+ * `previous`, and hold that grace window for at least the longest token
41
+ * lifetime before dropping the old key from `previous`. Because
42
+ * {@link signJwt} stamps the current key's `kid` into every JWS header and each
43
+ * published JWK carries its own `kid`, verifiers select the right key by `kid`
44
+ * across the window. Keys are published current-first and de-duplicated by
45
+ * `kid`.
46
+ *
47
+ * A single-key configuration (`previous` empty or omitted) publishes exactly
48
+ * the one key and is behaviourally identical to
49
+ * {@link StaticSigningKeyProvider} — adopting this provider does not change a
50
+ * deployment that has not started a rotation.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * const provider = new RotatingSigningKeyProvider({
55
+ * current: await importSigningKeyJwk(JSON.parse(currentJwk)),
56
+ * previous: [await importSigningKeyJwk(JSON.parse(oldJwk))],
57
+ * });
58
+ * ```
59
+ */
60
+ export class RotatingSigningKeyProvider {
61
+ #current;
62
+ #publicJwks;
63
+ /** Fixes the current signing key and the published set (current + previous). */
64
+ constructor(options) {
65
+ this.#current = options.current;
66
+ const seen = new Set();
67
+ const keys = [];
68
+ for (const key of [options.current, ...(options.previous ?? [])]) {
69
+ if (seen.has(key.kid))
70
+ continue;
71
+ seen.add(key.kid);
72
+ keys.push(key.publicJwk);
73
+ }
74
+ this.#publicJwks = { keys };
75
+ }
76
+ /** The current key — the only key new tokens are ever signed with. */
77
+ getSigningKey() {
78
+ return Promise.resolve(this.#current);
79
+ }
80
+ /** Public JWKs for the current key and every still-published previous key. */
81
+ getPublicJwks() {
82
+ return Promise.resolve({ keys: [...this.#publicJwks.keys] });
83
+ }
84
+ }
85
+ const ES256_PARAMS = { name: "ECDSA", namedCurve: "P-256" };
86
+ const ES256_SIGN_PARAMS = { name: "ECDSA", hash: "SHA-256" };
87
+ const encoder = new TextEncoder();
88
+ const decoder = new TextDecoder();
89
+ function encodeSegment(value) {
90
+ return base64urlEncode(encoder.encode(JSON.stringify(value)));
91
+ }
92
+ /** Maps a user to a JWT `sub` claim: the user's `id` property, else the user itself. */
93
+ export function defaultSubjectOf(user) {
94
+ return String(user?.id ?? user);
95
+ }
96
+ function publicJwkOf(jwk, kid) {
97
+ return {
98
+ kty: jwk.kty,
99
+ crv: jwk.crv,
100
+ x: jwk.x,
101
+ y: jwk.y,
102
+ kid,
103
+ alg: "ES256",
104
+ use: "sig",
105
+ };
106
+ }
107
+ /** Generate a fresh ES256 signing key with a random `kid`. */
108
+ export async function generateSigningKey() {
109
+ const pair = await crypto.subtle.generateKey(ES256_PARAMS, true, [
110
+ "sign",
111
+ "verify",
112
+ ]);
113
+ const kid = crypto.randomUUID();
114
+ const jwk = await crypto.subtle.exportKey("jwk", pair.publicKey);
115
+ return {
116
+ kid,
117
+ alg: "ES256",
118
+ privateKey: pair.privateKey,
119
+ publicJwk: publicJwkOf(jwk, kid),
120
+ };
121
+ }
122
+ /**
123
+ * Export a signing key (including private material) as a JWK for persistence
124
+ * — e.g. into a secret-manager entry the app loads on boot. Guard it like any
125
+ * credential.
126
+ */
127
+ export async function exportSigningKeyJwk(key) {
128
+ const jwk = await crypto.subtle.exportKey("jwk", key.privateKey);
129
+ return { ...jwk, kid: key.kid, alg: key.alg };
130
+ }
131
+ /** Import a signing key previously exported with {@link exportSigningKeyJwk}. */
132
+ export async function importSigningKeyJwk(jwk) {
133
+ const privateKey = await crypto.subtle.importKey("jwk", jwk, ES256_PARAMS, true, ["sign"]);
134
+ const kid = jwk.kid ?? crypto.randomUUID();
135
+ return {
136
+ kid,
137
+ alg: "ES256",
138
+ privateKey,
139
+ publicJwk: publicJwkOf(jwk, kid),
140
+ };
141
+ }
142
+ /**
143
+ * Sign a compact JWS (`header.payload.signature`) with the given key —
144
+ * the primitive behind id_tokens and JWT access tokens.
145
+ */
146
+ export async function signJwt(key, payload, header = {}) {
147
+ const signingInput = `${encodeSegment({ alg: key.alg, typ: "JWT", kid: key.kid, ...header })}.${encodeSegment(payload)}`;
148
+ const signature = await crypto.subtle.sign(ES256_SIGN_PARAMS, key.privateKey, encoder.encode(signingInput));
149
+ return `${signingInput}.${base64urlEncode(new Uint8Array(signature))}`;
150
+ }
151
+ /**
152
+ * Verify a compact JWS against a public JWK and return its payload, or
153
+ * `undefined` when the signature (or shape) is invalid or the payload's `exp`
154
+ * is in the past. Exported for tests and lightweight in-process verification;
155
+ * production resource servers should verify against the JWKS endpoint with
156
+ * their JOSE library of choice.
157
+ */
158
+ export async function verifyJwt(jwt, publicJwk, options = {}) {
159
+ const parts = jwt.split(".");
160
+ if (parts.length !== 3)
161
+ return undefined;
162
+ const [headerPart, payloadPart, signaturePart] = parts;
163
+ try {
164
+ const publicKey = await crypto.subtle.importKey("jwk", publicJwk, ES256_PARAMS, false, ["verify"]);
165
+ const valid = await crypto.subtle.verify(ES256_SIGN_PARAMS, publicKey, new Uint8Array(base64urlDecode(signaturePart)), encoder.encode(`${headerPart}.${payloadPart}`));
166
+ if (!valid)
167
+ return undefined;
168
+ const payload = JSON.parse(decoder.decode(base64urlDecode(payloadPart)));
169
+ if (!options.ignoreExpiration &&
170
+ typeof payload.exp === "number" && payload.exp * 1000 <= Date.now()) {
171
+ return undefined;
172
+ }
173
+ return payload;
174
+ }
175
+ catch {
176
+ return undefined;
177
+ }
178
+ }
179
+ /**
180
+ * Build a `generateAccessToken` implementation that issues **signed JWT
181
+ * access tokens** (RFC 9068 `at+jwt`) instead of opaque strings — the
182
+ * "verifiable signed-token option": resource servers can validate offline
183
+ * against the JWKS endpoint. Wire it into your token service:
184
+ *
185
+ * ```ts
186
+ * class JwtTokenService extends MyTokenService {
187
+ * override generateAccessToken = createJwtAccessTokenGenerator({
188
+ * signingKeys, issuer: "https://auth.example.com",
189
+ * });
190
+ * }
191
+ * ```
192
+ *
193
+ * The token store still persists the (hashed) JWT string, so revocation and
194
+ * introspection keep working exactly as with opaque tokens.
195
+ */
196
+ export function createJwtAccessTokenGenerator(options) {
197
+ const lifetime = options.lifetimeSeconds ?? 3600;
198
+ const subjectOf = options.subjectOf ?? defaultSubjectOf;
199
+ return async (client, user, scope, authenticationContext) => {
200
+ const event = user
201
+ ? snapshotAuthenticationContext(authenticationContext)
202
+ : undefined;
203
+ const key = await options.signingKeys.getSigningKey();
204
+ const now = Math.floor(Date.now() / 1000);
205
+ const extraClaims = user && options.userClaims
206
+ ? await options.userClaims(user, scope, client, event)
207
+ : undefined;
208
+ const payload = {
209
+ ...extraClaims,
210
+ iss: options.issuer,
211
+ sub: user ? subjectOf(user) : client.id,
212
+ client_id: client.id,
213
+ aud: options.audience ?? client.id,
214
+ iat: now,
215
+ exp: now + lifetime,
216
+ jti: crypto.randomUUID(),
217
+ };
218
+ const scopeText = scope?.toString();
219
+ if (scopeText)
220
+ payload.scope = scopeText;
221
+ else
222
+ delete payload.scope;
223
+ return await signJwt(key, payload, { typ: "at+jwt" });
224
+ };
225
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Hashing utilities for secure token and secret storage.
3
+ * @module
4
+ */
5
+ /** Hashes a high-entropy string (UUID, token, secret) using SHA-256. */
6
+ export declare function sha256Hash(value: string): Promise<string>;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Hashing utilities for secure token and secret storage.
3
+ * @module
4
+ */
5
+ import { encodeHex } from "../../deps/jsr.io/@std/encoding/1.0.11/hex.js";
6
+ /** Hashes a high-entropy string (UUID, token, secret) using SHA-256. */
7
+ export async function sha256Hash(value) {
8
+ const data = new TextEncoder().encode(value);
9
+ const hash = await crypto.subtle.digest("SHA-256", data);
10
+ return encodeHex(hash);
11
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Contract test suite for {@link AuthorizationCodeServiceInterface}
3
+ * implementations.
4
+ *
5
+ * @module
6
+ */
7
+ import type { ClientInterface } from "../../models/client.js";
8
+ import type { AbstractScope, BasicScope } from "../../models/scope.js";
9
+ import type { AuthorizationCodeServiceInterface } from "../../server/services/authorization-code.js";
10
+ import type { ClientServiceInterface } from "../../server/services/client.js";
11
+ import type { UserServiceInterface } from "../../server/services/user.js";
12
+ import type { MemoryUserShape } from "../services.js";
13
+ /** Options for {@link runAuthorizationCodeServiceContractTests}. */
14
+ export interface AuthorizationCodeServiceContractOptions<C extends ClientInterface, U extends MemoryUserShape, S extends AbstractScope = BasicScope> {
15
+ /** Returns fresh services for each test. */
16
+ makeServices(): Promise<{
17
+ userService: UserServiceInterface<U>;
18
+ clientService: ClientServiceInterface<C, U>;
19
+ authorizationCodeService: AuthorizationCodeServiceInterface<C, U, S>;
20
+ }> | {
21
+ userService: UserServiceInterface<U>;
22
+ clientService: ClientServiceInterface<C, U>;
23
+ authorizationCodeService: AuthorizationCodeServiceInterface<C, U, S>;
24
+ };
25
+ /** Persists a user with the given password into the user service under test. */
26
+ addUser(service: UserServiceInterface<U>, user: U, password: string): Promise<void>;
27
+ /** Persists a client (optionally with a secret and owning user) into the client service under test. */
28
+ addClient(service: ClientServiceInterface<C, U>, client: C, secret?: string, ownerUserId?: string): Promise<void>;
29
+ /** Builds a distinct user fixture for the given sequence number. Defaults to a minimal `{ id, username }` shape. */
30
+ makeUser?(seq: number): U;
31
+ /** Builds a distinct client fixture for the given sequence number. Defaults to a client granting `authorization_code`. */
32
+ makeClient?(seq: number): C;
33
+ /** Overrides the name passed to the outer `describe` block. */
34
+ describeName?: string;
35
+ }
36
+ /** Runs the {@link AuthorizationCodeServiceInterface} contract suite. */
37
+ export declare function runAuthorizationCodeServiceContractTests<C extends ClientInterface, U extends MemoryUserShape, S extends AbstractScope = BasicScope>(options: AuthorizationCodeServiceContractOptions<C, U, S>): void;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Contract test suite for {@link AuthorizationCodeServiceInterface}
3
+ * implementations.
4
+ *
5
+ * @module
6
+ */
7
+ import { assertEquals, assertStrictEquals } from "../../deps/jsr.io/@std/assert/1.0.19/mod.js";
8
+ import { beforeEach, describe, it } from "../../deps/jsr.io/@std/testing/1.0.20/bdd.js";
9
+ const defaultMakeUser = (seq) => ({ id: `u${seq}`, username: `user${seq}` });
10
+ const defaultMakeClient = (seq) => ({ id: `client-${seq}`, grants: ["authorization_code"] });
11
+ /** Runs the {@link AuthorizationCodeServiceInterface} contract suite. */
12
+ export function runAuthorizationCodeServiceContractTests(options) {
13
+ const makeUser = options.makeUser ?? (defaultMakeUser);
14
+ const makeClient = options.makeClient ?? (defaultMakeClient);
15
+ describe(options.describeName ?? "AuthorizationCodeServiceInterface contract", () => {
16
+ let userService;
17
+ let clientService;
18
+ let codeService;
19
+ let user;
20
+ let client;
21
+ beforeEach(async () => {
22
+ const services = await options.makeServices();
23
+ userService = services.userService;
24
+ clientService = services.clientService;
25
+ codeService = services.authorizationCodeService;
26
+ user = makeUser(1);
27
+ client = makeClient(1);
28
+ await options.addUser(userService, user, "pw");
29
+ await options.addClient(clientService, client);
30
+ });
31
+ it("generateCode returns unique strings", async () => {
32
+ const a = await codeService.generateCode(client, user);
33
+ const b = await codeService.generateCode(client, user);
34
+ assertEquals(a !== b, true);
35
+ });
36
+ it("expiresAt returns a future date", async () => {
37
+ const expires = await codeService.expiresAt(client, user);
38
+ assertEquals(expires.getTime() > Date.now(), true);
39
+ });
40
+ it("save + get round-trips an authorization code", async () => {
41
+ const code = await codeService.generateCode(client, user);
42
+ const expiresAt = await codeService.expiresAt(client, user);
43
+ const ac = {
44
+ code,
45
+ expiresAt,
46
+ client,
47
+ user,
48
+ redirectUri: "http://app/cb",
49
+ challenge: "abc",
50
+ challengeMethod: "S256",
51
+ nonce: "nonce-1",
52
+ };
53
+ await codeService.save(ac);
54
+ const fetched = await codeService.get(code);
55
+ assertEquals(fetched?.code, code);
56
+ assertEquals(fetched?.client.id, client.id);
57
+ assertEquals(fetched?.redirectUri, "http://app/cb");
58
+ assertEquals(fetched?.challenge, "abc");
59
+ assertEquals(fetched?.nonce, "nonce-1");
60
+ });
61
+ it("get returns undefined for an unknown code", async () => {
62
+ assertStrictEquals(await codeService.get("missing"), undefined);
63
+ });
64
+ it("revoke deletes the code by string", async () => {
65
+ const code = await codeService.generateCode(client, user);
66
+ await codeService.save({
67
+ code,
68
+ expiresAt: await codeService.expiresAt(client, user),
69
+ client,
70
+ user,
71
+ });
72
+ const ok = await codeService.revoke(code);
73
+ assertStrictEquals(ok, true);
74
+ assertStrictEquals(await codeService.get(code), undefined);
75
+ });
76
+ it("revoke deletes the code by object", async () => {
77
+ const code = await codeService.generateCode(client, user);
78
+ const ac = {
79
+ code,
80
+ expiresAt: await codeService.expiresAt(client, user),
81
+ client,
82
+ user,
83
+ };
84
+ await codeService.save(ac);
85
+ const ok = await codeService.revoke(ac);
86
+ assertStrictEquals(ok, true);
87
+ assertStrictEquals(await codeService.get(code), undefined);
88
+ });
89
+ it("revoke returns false for an unknown code", async () => {
90
+ assertStrictEquals(await codeService.revoke("missing"), false);
91
+ });
92
+ });
93
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Contract test suite for {@link ClientServiceInterface} implementations.
3
+ *
4
+ * `getUser` is contract-conformant either way: resolving a user and resolving
5
+ * nothing are both required to pass. A client that resolves nothing is a
6
+ * machine client whose token carries no resource owner (RFC 6749 §4.4), not a
7
+ * misconfiguration — the client credentials grant issues it a user-less token.
8
+ * Resolve a user only when that user is a principal of its own, such as a
9
+ * per-application service account; resolving a human owner hands the machine
10
+ * that person's identity (RFC 9700 §4.15).
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { runClientServiceContractTests } from "@udibo/oauth2/testing/contract";
15
+ * import type { ClientInterface } from "@udibo/oauth2/server";
16
+ * import type {
17
+ * ClientServiceInterface,
18
+ * UserServiceInterface,
19
+ * } from "@udibo/oauth2/server/authorization";
20
+ *
21
+ * interface AppUser {
22
+ * id: string;
23
+ * username: string;
24
+ * }
25
+ * interface DrizzleUserService extends UserServiceInterface<AppUser> {
26
+ * register(user: AppUser, password: string): Promise<void>;
27
+ * }
28
+ * interface DrizzleClientService
29
+ * extends ClientServiceInterface<ClientInterface, AppUser> {
30
+ * register(
31
+ * client: ClientInterface,
32
+ * secret?: string,
33
+ * ownerUserId?: string,
34
+ * ): Promise<void>;
35
+ * }
36
+ * declare function freshServices(): Promise<{
37
+ * userService: DrizzleUserService;
38
+ * clientService: DrizzleClientService;
39
+ * }>;
40
+ *
41
+ * runClientServiceContractTests<ClientInterface, AppUser>({
42
+ * makeServices: freshServices,
43
+ * addUser: (svc, user, pw) => (svc as DrizzleUserService).register(user, pw),
44
+ * addClient: (svc, client, secret, ownerUserId) =>
45
+ * (svc as DrizzleClientService).register(client, secret, ownerUserId),
46
+ * });
47
+ * ```
48
+ *
49
+ * @module
50
+ */
51
+ import type { ClientInterface } from "../../models/client.js";
52
+ import type { ClientServiceInterface } from "../../server/services/client.js";
53
+ import type { UserServiceInterface } from "../../server/services/user.js";
54
+ import type { MemoryUserShape } from "../services.js";
55
+ /** Options for {@link runClientServiceContractTests}. */
56
+ export interface ClientServiceContractOptions<C extends ClientInterface, U extends MemoryUserShape> {
57
+ /**
58
+ * Returns fresh services for each test. The `userService` is needed
59
+ * because `clientService.getUser(client)` resolves through it.
60
+ */
61
+ makeServices(): Promise<{
62
+ userService: UserServiceInterface<U>;
63
+ clientService: ClientServiceInterface<C, U>;
64
+ }> | {
65
+ userService: UserServiceInterface<U>;
66
+ clientService: ClientServiceInterface<C, U>;
67
+ };
68
+ /** Adds a user via the consumer's preferred mechanism. */
69
+ addUser(service: UserServiceInterface<U>, user: U, password: string): Promise<void>;
70
+ /**
71
+ * Adds a client. Pass `secret` to register a confidential client; omit
72
+ * for public. `ownerUserId` is the user returned by `getUser`; omit it to
73
+ * register a client that resolves no user, which the suite requires to be
74
+ * supported.
75
+ */
76
+ addClient(service: ClientServiceInterface<C, U>, client: C, secret?: string, ownerUserId?: string): Promise<void>;
77
+ /** Builds a distinct user fixture for the given sequence number. Defaults to a minimal `{ id, username }` shape. */
78
+ makeUser?(seq: number): U;
79
+ /** Builds a distinct client fixture for the given sequence number. Defaults to a client granting `client_credentials`. */
80
+ makeClient?(seq: number): C;
81
+ /** Overrides the name passed to the outer `describe` block. */
82
+ describeName?: string;
83
+ }
84
+ /** Runs the {@link ClientServiceInterface} contract suite. */
85
+ export declare function runClientServiceContractTests<C extends ClientInterface, U extends MemoryUserShape>(options: ClientServiceContractOptions<C, U>): void;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Contract test suite for {@link ClientServiceInterface} implementations.
3
+ *
4
+ * `getUser` is contract-conformant either way: resolving a user and resolving
5
+ * nothing are both required to pass. A client that resolves nothing is a
6
+ * machine client whose token carries no resource owner (RFC 6749 §4.4), not a
7
+ * misconfiguration — the client credentials grant issues it a user-less token.
8
+ * Resolve a user only when that user is a principal of its own, such as a
9
+ * per-application service account; resolving a human owner hands the machine
10
+ * that person's identity (RFC 9700 §4.15).
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { runClientServiceContractTests } from "@udibo/oauth2/testing/contract";
15
+ * import type { ClientInterface } from "@udibo/oauth2/server";
16
+ * import type {
17
+ * ClientServiceInterface,
18
+ * UserServiceInterface,
19
+ * } from "@udibo/oauth2/server/authorization";
20
+ *
21
+ * interface AppUser {
22
+ * id: string;
23
+ * username: string;
24
+ * }
25
+ * interface DrizzleUserService extends UserServiceInterface<AppUser> {
26
+ * register(user: AppUser, password: string): Promise<void>;
27
+ * }
28
+ * interface DrizzleClientService
29
+ * extends ClientServiceInterface<ClientInterface, AppUser> {
30
+ * register(
31
+ * client: ClientInterface,
32
+ * secret?: string,
33
+ * ownerUserId?: string,
34
+ * ): Promise<void>;
35
+ * }
36
+ * declare function freshServices(): Promise<{
37
+ * userService: DrizzleUserService;
38
+ * clientService: DrizzleClientService;
39
+ * }>;
40
+ *
41
+ * runClientServiceContractTests<ClientInterface, AppUser>({
42
+ * makeServices: freshServices,
43
+ * addUser: (svc, user, pw) => (svc as DrizzleUserService).register(user, pw),
44
+ * addClient: (svc, client, secret, ownerUserId) =>
45
+ * (svc as DrizzleClientService).register(client, secret, ownerUserId),
46
+ * });
47
+ * ```
48
+ *
49
+ * @module
50
+ */
51
+ import { assertEquals, assertStrictEquals } from "../../deps/jsr.io/@std/assert/1.0.19/mod.js";
52
+ import { beforeEach, describe, it } from "../../deps/jsr.io/@std/testing/1.0.20/bdd.js";
53
+ const defaultMakeUser = (seq) => ({ id: `u${seq}`, username: `user${seq}` });
54
+ const defaultMakeClient = (seq) => ({ id: `client-${seq}`, grants: ["client_credentials"] });
55
+ /** Runs the {@link ClientServiceInterface} contract suite. */
56
+ export function runClientServiceContractTests(options) {
57
+ const makeUser = options.makeUser ?? (defaultMakeUser);
58
+ const makeClient = options.makeClient ?? (defaultMakeClient);
59
+ describe(options.describeName ?? "ClientServiceInterface contract", () => {
60
+ let userService;
61
+ let clientService;
62
+ beforeEach(async () => {
63
+ const services = await options.makeServices();
64
+ userService = services.userService;
65
+ clientService = services.clientService;
66
+ });
67
+ describe("get", () => {
68
+ it("returns undefined for an unknown id", async () => {
69
+ assertStrictEquals(await clientService.get("missing"), undefined);
70
+ });
71
+ it("returns the registered client", async () => {
72
+ const client = makeClient(1);
73
+ await options.addClient(clientService, client);
74
+ const fetched = await clientService.get(client.id);
75
+ assertEquals(fetched?.id, client.id);
76
+ });
77
+ });
78
+ describe("getAuthenticated", () => {
79
+ it("returns the public client without a secret", async () => {
80
+ const client = makeClient(2);
81
+ await options.addClient(clientService, client);
82
+ const auth = await clientService.getAuthenticated(client.id);
83
+ assertEquals(auth?.id, client.id);
84
+ });
85
+ it("returns undefined for a confidential client when no secret is supplied", async () => {
86
+ const client = makeClient(3);
87
+ await options.addClient(clientService, client, "secret");
88
+ assertStrictEquals(await clientService.getAuthenticated(client.id), undefined);
89
+ });
90
+ it("returns undefined for a confidential client with a wrong secret", async () => {
91
+ const client = makeClient(4);
92
+ await options.addClient(clientService, client, "correct");
93
+ assertStrictEquals(await clientService.getAuthenticated(client.id, "wrong"), undefined);
94
+ });
95
+ it("returns the confidential client with a matching secret", async () => {
96
+ const client = makeClient(5);
97
+ await options.addClient(clientService, client, "matching");
98
+ const auth = await clientService.getAuthenticated(client.id, "matching");
99
+ assertEquals(auth?.id, client.id);
100
+ });
101
+ });
102
+ describe("getUser", () => {
103
+ it("resolves nothing for a client registered without a user, rather than failing", async () => {
104
+ const client = makeClient(6);
105
+ await options.addClient(clientService, client);
106
+ assertStrictEquals(await clientService.getUser(client), undefined);
107
+ });
108
+ it("returns the associated user when one was registered", async () => {
109
+ const user = makeUser(7);
110
+ const client = makeClient(7);
111
+ await options.addUser(userService, user, "pw");
112
+ await options.addClient(clientService, client, undefined, user.id);
113
+ const owner = await clientService.getUser(client);
114
+ assertEquals(owner?.id, user.id);
115
+ });
116
+ it("accepts a client id (not just a client object)", async () => {
117
+ const user = makeUser(8);
118
+ const client = makeClient(8);
119
+ await options.addUser(userService, user, "pw");
120
+ await options.addClient(clientService, client, undefined, user.id);
121
+ const owner = await clientService.getUser(client.id);
122
+ assertEquals(owner?.id, user.id);
123
+ });
124
+ });
125
+ });
126
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Contract test suite for {@link DeviceAuthorizationServiceInterface}
3
+ * implementations (RFC 8628).
4
+ *
5
+ * @module
6
+ */
7
+ import type { ClientInterface } from "../../models/client.js";
8
+ import type { AbstractScope, BasicScope } from "../../models/scope.js";
9
+ import type { ClientServiceInterface } from "../../server/services/client.js";
10
+ import type { DeviceAuthorizationServiceInterface } from "../../server/services/device-authorization.js";
11
+ import type { UserServiceInterface } from "../../server/services/user.js";
12
+ import type { MemoryUserShape } from "../services.js";
13
+ /** Options for {@link runDeviceAuthorizationServiceContractTests}. */
14
+ export interface DeviceAuthorizationServiceContractOptions<C extends ClientInterface, U extends MemoryUserShape, S extends AbstractScope = BasicScope> {
15
+ /** Returns fresh services for each test. */
16
+ makeServices(): Promise<{
17
+ userService: UserServiceInterface<U>;
18
+ clientService: ClientServiceInterface<C, U>;
19
+ deviceAuthorizationService: DeviceAuthorizationServiceInterface<C, U, S>;
20
+ }> | {
21
+ userService: UserServiceInterface<U>;
22
+ clientService: ClientServiceInterface<C, U>;
23
+ deviceAuthorizationService: DeviceAuthorizationServiceInterface<C, U, S>;
24
+ };
25
+ /** Persists a user with the given password into the user service under test. */
26
+ addUser(service: UserServiceInterface<U>, user: U, password: string): Promise<void>;
27
+ /** Persists a client (optionally with a secret and owning user) into the client service under test. */
28
+ addClient(service: ClientServiceInterface<C, U>, client: C, secret?: string, ownerUserId?: string): Promise<void>;
29
+ /** Builds a distinct user fixture for the given sequence number. Defaults to a minimal `{ id, username }` shape. */
30
+ makeUser?(seq: number): U;
31
+ /** Builds a distinct client fixture for the given sequence number. Defaults to a client granting the device-code grant. */
32
+ makeClient?(seq: number): C;
33
+ /** Overrides the name passed to the outer `describe` block. */
34
+ describeName?: string;
35
+ }
36
+ /** Runs the {@link DeviceAuthorizationServiceInterface} contract suite. */
37
+ export declare function runDeviceAuthorizationServiceContractTests<C extends ClientInterface, U extends MemoryUserShape, S extends AbstractScope = BasicScope>(options: DeviceAuthorizationServiceContractOptions<C, U, S>): void;