@udibo/oauth2 0.14.1 → 0.15.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 (411) hide show
  1. package/README.md +45 -24
  2. package/{esm → dist}/adapters/hono/_common.d.ts +5 -5
  3. package/{esm → dist}/adapters/hono/_common.js +1 -1
  4. package/{esm → dist}/adapters/hono/authorization-server.d.ts +6 -7
  5. package/{esm → dist}/adapters/hono/authorization-server.js +0 -1
  6. package/{esm → dist}/adapters/hono/bff/auth-request-store.d.ts +2 -2
  7. package/{esm → dist}/adapters/hono/bff/auth-request-store.js +2 -2
  8. package/{esm → dist}/adapters/hono/bff/bff.d.ts +8 -8
  9. package/{esm → dist}/adapters/hono/bff/bff.js +31 -26
  10. package/{esm → dist}/adapters/hono/bff/cookie-prefix.js +1 -2
  11. package/{esm → dist}/adapters/hono/bff/mod.d.ts +5 -6
  12. package/{esm → dist}/adapters/hono/bff/mod.js +1 -2
  13. package/{esm → dist}/adapters/hono/bff/proxy.js +2 -1
  14. package/{esm → dist}/adapters/hono/bff/session-store.d.ts +2 -2
  15. package/{esm → dist}/adapters/hono/bff/session-store.js +7 -6
  16. package/{esm → dist}/adapters/hono/bff/testing.d.ts +7 -8
  17. package/{esm → dist}/adapters/hono/bff/testing.js +19 -22
  18. package/{esm → dist}/adapters/hono/identity.d.ts +1 -2
  19. package/{esm → dist}/adapters/hono/identity.js +10 -10
  20. package/{esm → dist}/adapters/hono/log.d.ts +0 -1
  21. package/{esm → dist}/adapters/hono/log.js +0 -6
  22. package/{esm → dist}/adapters/hono/resource-server.d.ts +5 -6
  23. package/{esm → dist}/adapters/hono/resource-server.js +0 -1
  24. package/dist/cli/bin.d.ts +2 -0
  25. package/dist/cli/bin.js +3 -0
  26. package/dist/cli/idp/config.d.ts +110 -0
  27. package/dist/cli/idp/config.js +269 -0
  28. package/dist/cli/idp/mod.d.ts +20 -0
  29. package/dist/cli/idp/mod.js +175 -0
  30. package/dist/cli/idp/pages.d.ts +56 -0
  31. package/dist/cli/idp/pages.js +190 -0
  32. package/dist/cli/idp/server.d.ts +93 -0
  33. package/dist/cli/idp/server.js +559 -0
  34. package/dist/cli/keygen.d.ts +13 -0
  35. package/dist/cli/keygen.js +30 -0
  36. package/dist/cli/mod.d.ts +34 -0
  37. package/dist/cli/mod.js +94 -0
  38. package/{esm → dist}/client/_http.d.ts +1 -1
  39. package/{esm → dist}/client/base.d.ts +1 -1
  40. package/{esm → dist}/client/bff-client.d.ts +1 -1
  41. package/{esm → dist}/client/bff-client.js +7 -6
  42. package/{esm → dist}/client/browser-storage.d.ts +1 -1
  43. package/{esm → dist}/client/check.js +1 -1
  44. package/{esm → dist}/client/direct-client.d.ts +6 -6
  45. package/{esm → dist}/client/direct-client.js +11 -13
  46. package/{esm → dist}/client/discovery-cache.d.ts +1 -1
  47. package/{esm → dist}/client/discovery-cache.js +2 -2
  48. package/{esm → dist}/client/events.d.ts +1 -1
  49. package/{esm → dist}/client/mod.d.ts +11 -12
  50. package/{esm → dist}/client/mod.js +1 -1
  51. package/{esm → dist}/errors.d.ts +1 -1
  52. package/{esm → dist}/errors.js +1 -1
  53. package/{esm → dist}/identity/events.d.ts +1 -1
  54. package/{esm → dist}/identity/external/_shared.d.ts +1 -1
  55. package/{esm → dist}/identity/external/_shared.js +1 -2
  56. package/{esm → dist}/identity/external/apple-client-secret.d.ts +1 -1
  57. package/{esm → dist}/identity/external/apple-client-secret.js +6 -7
  58. package/{esm → dist}/identity/external/apple.d.ts +5 -5
  59. package/{esm → dist}/identity/external/apple.js +10 -11
  60. package/{esm → dist}/identity/external/discord.d.ts +3 -3
  61. package/{esm → dist}/identity/external/discord.js +2 -2
  62. package/{esm → dist}/identity/external/flow.d.ts +1 -1
  63. package/{esm → dist}/identity/external/flow.js +2 -2
  64. package/{esm → dist}/identity/external/github.d.ts +3 -3
  65. package/{esm → dist}/identity/external/github.js +12 -17
  66. package/{esm → dist}/identity/external/google.d.ts +5 -5
  67. package/{esm → dist}/identity/external/google.js +2 -2
  68. package/{esm → dist}/identity/external/mod.d.ts +12 -13
  69. package/{esm → dist}/identity/external/mod.js +0 -1
  70. package/{esm → dist}/identity/external/oauth2.d.ts +1 -1
  71. package/{esm → dist}/identity/external/oauth2.js +8 -8
  72. package/{esm → dist}/identity/external/oidc.d.ts +4 -4
  73. package/{esm → dist}/identity/external/oidc.js +9 -8
  74. package/{esm → dist}/identity/hibp.d.ts +1 -1
  75. package/{esm → dist}/identity/hibp.js +1 -1
  76. package/{esm → dist}/identity/lockout.js +1 -2
  77. package/{esm → dist}/identity/mfa/mod.d.ts +4 -5
  78. package/{esm → dist}/identity/mfa/mod.js +1 -2
  79. package/{esm → dist}/identity/mfa/service.d.ts +3 -3
  80. package/{esm → dist}/identity/mfa/service.js +4 -3
  81. package/{esm → dist}/identity/mfa/totp.d.ts +1 -1
  82. package/{esm → dist}/identity/mfa/totp.js +2 -2
  83. package/{esm → dist}/identity/migration.d.ts +2 -3
  84. package/{esm → dist}/identity/migration.js +3 -4
  85. package/{esm → dist}/identity/mod.d.ts +14 -15
  86. package/{esm → dist}/identity/mod.js +0 -1
  87. package/{esm → dist}/identity/otp.d.ts +1 -1
  88. package/{esm → dist}/identity/otp.js +4 -2
  89. package/{esm → dist}/identity/password.js +8 -5
  90. package/{esm → dist}/identity/rate-limit.d.ts +1 -1
  91. package/{esm → dist}/identity/service.d.ts +10 -10
  92. package/{esm → dist}/identity/service.js +17 -18
  93. package/{esm → dist}/identity/session.js +2 -2
  94. package/{esm → dist}/identity/token-flow.js +3 -2
  95. package/{esm → dist}/models/authorization-code.d.ts +3 -3
  96. package/{esm → dist}/models/authorization.d.ts +1 -1
  97. package/{esm → dist}/models/authorization.js +1 -2
  98. package/{esm → dist}/models/device-authorization.d.ts +2 -2
  99. package/{esm → dist}/models/token.d.ts +3 -3
  100. package/{esm → dist}/react/callback.d.ts +1 -1
  101. package/{esm → dist}/react/callback.js +0 -1
  102. package/{esm → dist}/react/components/class-names.d.ts +1 -1
  103. package/{esm → dist}/react/components/class-names.js +3 -2
  104. package/{esm → dist}/react/components/internal.d.ts +2 -2
  105. package/{esm → dist}/react/components/internal.js +5 -11
  106. package/{esm → dist}/react/components/mfa-challenge-form.d.ts +2 -2
  107. package/{esm → dist}/react/components/mfa-challenge-form.js +4 -6
  108. package/{esm → dist}/react/components/mfa-enrollment-form.d.ts +2 -2
  109. package/{esm → dist}/react/components/mfa-enrollment-form.js +2 -6
  110. package/{esm → dist}/react/components/mod.d.ts +20 -21
  111. package/{esm → dist}/react/components/mod.js +1 -2
  112. package/{esm → dist}/react/components/request-password-reset-form.d.ts +2 -2
  113. package/{esm → dist}/react/components/request-password-reset-form.js +1 -3
  114. package/{esm → dist}/react/components/reset-password-form.d.ts +2 -2
  115. package/{esm → dist}/react/components/reset-password-form.js +2 -2
  116. package/{esm → dist}/react/components/sign-in-form.d.ts +2 -2
  117. package/{esm → dist}/react/components/sign-in-form.js +1 -5
  118. package/{esm → dist}/react/components/sign-up-form.d.ts +2 -2
  119. package/{esm → dist}/react/components/sign-up-form.js +4 -10
  120. package/{esm → dist}/react/components/types.d.ts +2 -2
  121. package/{esm → dist}/react/components/use-auth-form.d.ts +3 -3
  122. package/{esm → dist}/react/components/use-auth-form.js +4 -7
  123. package/{esm → dist}/react/components/user-menu.d.ts +2 -2
  124. package/{esm → dist}/react/components/user-menu.js +9 -14
  125. package/{esm → dist}/react/context.d.ts +1 -1
  126. package/{esm → dist}/react/mod.d.ts +6 -7
  127. package/{esm → dist}/react/mod.js +0 -1
  128. package/{esm → dist}/react/provider.d.ts +2 -2
  129. package/{esm → dist}/react/require-auth.js +1 -3
  130. package/{esm → dist}/react/testing.d.ts +2 -3
  131. package/{esm → dist}/react/testing.js +1 -2
  132. package/{esm → dist}/react/use-authorization.d.ts +1 -1
  133. package/{esm → dist}/react/use-oauth2.d.ts +8 -1
  134. package/{esm → dist}/react/use-oauth2.js +0 -1
  135. package/{esm → dist}/server/_clock-skew.js +2 -1
  136. package/{esm → dist}/server/authorization-server.d.ts +40 -41
  137. package/{esm → dist}/server/authorization-server.js +32 -42
  138. package/{esm → dist}/server/client-authentication.d.ts +2 -2
  139. package/{esm → dist}/server/grants/authorization-code.d.ts +9 -9
  140. package/{esm → dist}/server/grants/authorization-code.js +5 -6
  141. package/{esm → dist}/server/grants/client-credentials.d.ts +4 -4
  142. package/{esm → dist}/server/grants/device-authorization.d.ts +6 -6
  143. package/{esm → dist}/server/grants/device-authorization.js +4 -4
  144. package/{esm → dist}/server/grants/grant.d.ts +7 -7
  145. package/{esm → dist}/server/grants/grant.js +7 -7
  146. package/{esm → dist}/server/grants/password.d.ts +5 -5
  147. package/{esm → dist}/server/grants/refresh-token.d.ts +4 -4
  148. package/{esm → dist}/server/grants/refresh-token.js +5 -6
  149. package/{esm → dist}/server/introspection-token-reader.d.ts +5 -5
  150. package/{esm → dist}/server/introspection-token-reader.js +4 -4
  151. package/{esm → dist}/server/jwks-token-reader.d.ts +5 -5
  152. package/{esm → dist}/server/jwks-token-reader.js +4 -4
  153. package/{esm → dist}/server/mod.d.ts +12 -13
  154. package/{esm → dist}/server/mod.js +0 -1
  155. package/{esm → dist}/server/public-suffix/mod.d.ts +2 -3
  156. package/{esm → dist}/server/public-suffix/mod.js +1 -2
  157. package/{esm → dist}/server/public-suffix/rules.d.ts +1 -1
  158. package/{esm → dist}/server/public-suffix/rules.js +1 -1
  159. package/{esm → dist}/server/redirect-uri.js +5 -4
  160. package/{esm → dist}/server/resource-server.d.ts +11 -12
  161. package/{esm → dist}/server/resource-server.js +12 -14
  162. package/{esm → dist}/server/services/authorization-code.d.ts +5 -5
  163. package/{esm → dist}/server/services/client.d.ts +1 -1
  164. package/{esm → dist}/server/services/device-authorization.d.ts +3 -3
  165. package/{esm → dist}/server/services/token.d.ts +6 -6
  166. package/{esm → dist}/server/services/token.js +3 -1
  167. package/{esm → dist}/server/services/user.d.ts +1 -1
  168. package/{esm → dist}/server/signing-keys.d.ts +1 -1
  169. package/{esm → dist}/server/signing-keys.js +8 -2
  170. package/{esm → dist}/server/utils/hash.js +1 -1
  171. package/{esm → dist}/testing/contract/auth-request-storage.d.ts +1 -1
  172. package/{esm → dist}/testing/contract/auth-request-storage.js +24 -29
  173. package/{esm → dist}/testing/contract/authorization-code.d.ts +6 -6
  174. package/{esm → dist}/testing/contract/authorization-code.js +16 -17
  175. package/{esm → dist}/testing/contract/client.d.ts +4 -4
  176. package/{esm → dist}/testing/contract/client.js +14 -15
  177. package/{esm → dist}/testing/contract/device-authorization.d.ts +6 -6
  178. package/{esm → dist}/testing/contract/device-authorization.js +37 -39
  179. package/{esm → dist}/testing/contract/identity-session.d.ts +6 -1
  180. package/{esm → dist}/testing/contract/identity-session.js +44 -36
  181. package/{esm → dist}/testing/contract/identity-user-store.d.ts +8 -2
  182. package/{esm → dist}/testing/contract/identity-user-store.js +48 -46
  183. package/{esm → dist}/testing/contract/lockout-store.d.ts +1 -1
  184. package/{esm → dist}/testing/contract/lockout-store.js +23 -24
  185. package/{esm → dist}/testing/contract/mfa-store.d.ts +1 -1
  186. package/{esm → dist}/testing/contract/mfa-store.js +53 -54
  187. package/{esm → dist}/testing/contract/mod.d.ts +15 -16
  188. package/{esm → dist}/testing/contract/mod.js +0 -1
  189. package/{esm → dist}/testing/contract/otp-store.d.ts +1 -1
  190. package/{esm → dist}/testing/contract/otp-store.js +33 -34
  191. package/{esm → dist}/testing/contract/rate-limit-store.d.ts +1 -1
  192. package/{esm → dist}/testing/contract/rate-limit-store.js +15 -16
  193. package/{esm → dist}/testing/contract/tenant.js +361 -330
  194. package/{esm → dist}/testing/contract/token-flow-store.d.ts +1 -1
  195. package/{esm → dist}/testing/contract/token-flow-store.js +27 -32
  196. package/{esm → dist}/testing/contract/token-reader.d.ts +3 -3
  197. package/{esm → dist}/testing/contract/token-reader.js +6 -7
  198. package/{esm → dist}/testing/contract/token.d.ts +7 -7
  199. package/{esm → dist}/testing/contract/token.js +48 -53
  200. package/{esm → dist}/testing/contract/user.d.ts +2 -2
  201. package/{esm → dist}/testing/contract/user.js +8 -9
  202. package/{esm → dist}/testing/mod.d.ts +5 -6
  203. package/{esm → dist}/testing/mod.js +2 -3
  204. package/{esm → dist}/testing/server.d.ts +7 -6
  205. package/{esm → dist}/testing/server.js +2 -1
  206. package/{esm → dist}/testing/services.d.ts +11 -11
  207. package/{esm → dist}/testing/services.js +8 -8
  208. package/{esm → dist}/testing/tenant.d.ts +11 -7
  209. package/{esm → dist}/testing/tenant.js +109 -94
  210. package/dist/utils/_constant-time.d.ts +12 -0
  211. package/dist/utils/_constant-time.js +19 -0
  212. package/{esm → dist}/utils/_default-fetch.js +4 -1
  213. package/dist/utils/_delay.d.ts +13 -0
  214. package/dist/utils/_delay.js +28 -0
  215. package/dist/utils/_encoding.d.ts +39 -0
  216. package/dist/utils/_encoding.js +126 -0
  217. package/{esm → dist}/utils/authentication-context.d.ts +1 -1
  218. package/{esm → dist}/utils/crypto.d.ts +2 -3
  219. package/{esm → dist}/utils/crypto.js +3 -4
  220. package/{esm → dist}/utils/pkce.js +1 -1
  221. package/{esm → dist}/utils/text.js +1 -1
  222. package/{esm → dist}/utils/url.d.ts +0 -1
  223. package/{esm → dist}/utils/url.js +0 -1
  224. package/package.json +156 -33
  225. package/esm/_dnt.polyfills.d.ts +0 -31
  226. package/esm/_dnt.polyfills.js +0 -26
  227. package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.d.ts +0 -29
  228. package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.js +0 -47
  229. package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.d.ts +0 -28
  230. package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.js +0 -65
  231. package/esm/deps/jsr.io/@std/assert/1.0.19/assert.d.ts +0 -16
  232. package/esm/deps/jsr.io/@std/assert/1.0.19/assert.js +0 -23
  233. package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.d.ts +0 -25
  234. package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.js +0 -30
  235. package/esm/deps/jsr.io/@std/assert/1.0.19/equal.d.ts +0 -24
  236. package/esm/deps/jsr.io/@std/assert/1.0.19/equal.js +0 -193
  237. package/esm/deps/jsr.io/@std/assert/1.0.19/equals.d.ts +0 -36
  238. package/esm/deps/jsr.io/@std/assert/1.0.19/equals.js +0 -61
  239. package/esm/deps/jsr.io/@std/assert/1.0.19/exists.d.ts +0 -17
  240. package/esm/deps/jsr.io/@std/assert/1.0.19/exists.js +0 -27
  241. package/esm/deps/jsr.io/@std/assert/1.0.19/fail.d.ts +0 -14
  242. package/esm/deps/jsr.io/@std/assert/1.0.19/fail.js +0 -20
  243. package/esm/deps/jsr.io/@std/assert/1.0.19/false.d.ts +0 -18
  244. package/esm/deps/jsr.io/@std/assert/1.0.19/false.js +0 -23
  245. package/esm/deps/jsr.io/@std/assert/1.0.19/greater.d.ts +0 -19
  246. package/esm/deps/jsr.io/@std/assert/1.0.19/greater.js +0 -29
  247. package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.d.ts +0 -19
  248. package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.js +0 -29
  249. package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.d.ts +0 -22
  250. package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.js +0 -52
  251. package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.d.ts +0 -24
  252. package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.js +0 -53
  253. package/esm/deps/jsr.io/@std/assert/1.0.19/less.d.ts +0 -18
  254. package/esm/deps/jsr.io/@std/assert/1.0.19/less.js +0 -28
  255. package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.d.ts +0 -19
  256. package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.js +0 -29
  257. package/esm/deps/jsr.io/@std/assert/1.0.19/match.d.ts +0 -17
  258. package/esm/deps/jsr.io/@std/assert/1.0.19/match.js +0 -26
  259. package/esm/deps/jsr.io/@std/assert/1.0.19/mod.d.ts +0 -43
  260. package/esm/deps/jsr.io/@std/assert/1.0.19/mod.js +0 -45
  261. package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.d.ts +0 -26
  262. package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.js +0 -39
  263. package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.d.ts +0 -19
  264. package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.js +0 -29
  265. package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.d.ts +0 -17
  266. package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.js +0 -26
  267. package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.d.ts +0 -22
  268. package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.js +0 -32
  269. package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.d.ts +0 -28
  270. package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.js +0 -177
  271. package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.d.ts +0 -41
  272. package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.js +0 -53
  273. package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.d.ts +0 -23
  274. package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.js +0 -58
  275. package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.d.ts +0 -17
  276. package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.js +0 -26
  277. package/esm/deps/jsr.io/@std/assert/1.0.19/throws.d.ts +0 -44
  278. package/esm/deps/jsr.io/@std/assert/1.0.19/throws.js +0 -44
  279. package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.d.ts +0 -14
  280. package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.js +0 -20
  281. package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.d.ts +0 -14
  282. package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.js +0 -20
  283. package/esm/deps/jsr.io/@std/async/1.5.0/delay.d.ts +0 -44
  284. package/esm/deps/jsr.io/@std/async/1.5.0/delay.js +0 -84
  285. package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.d.ts +0 -39
  286. package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.js +0 -61
  287. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common16.d.ts +0 -22
  288. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common16.js +0 -51
  289. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common32.d.ts +0 -34
  290. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common32.js +0 -192
  291. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common64.d.ts +0 -34
  292. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common64.js +0 -113
  293. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common_detach.d.ts +0 -3
  294. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common_detach.js +0 -13
  295. package/esm/deps/jsr.io/@std/encoding/1.0.11/_types.d.ts +0 -8
  296. package/esm/deps/jsr.io/@std/encoding/1.0.11/_types.js +0 -2
  297. package/esm/deps/jsr.io/@std/encoding/1.0.11/_validate_binary_like.d.ts +0 -1
  298. package/esm/deps/jsr.io/@std/encoding/1.0.11/_validate_binary_like.js +0 -26
  299. package/esm/deps/jsr.io/@std/encoding/1.0.11/ascii85.d.ts +0 -60
  300. package/esm/deps/jsr.io/@std/encoding/1.0.11/ascii85.js +0 -152
  301. package/esm/deps/jsr.io/@std/encoding/1.0.11/base32.d.ts +0 -39
  302. package/esm/deps/jsr.io/@std/encoding/1.0.11/base32.js +0 -87
  303. package/esm/deps/jsr.io/@std/encoding/1.0.11/base58.d.ts +0 -39
  304. package/esm/deps/jsr.io/@std/encoding/1.0.11/base58.js +0 -131
  305. package/esm/deps/jsr.io/@std/encoding/1.0.11/base64.d.ts +0 -39
  306. package/esm/deps/jsr.io/@std/encoding/1.0.11/base64.js +0 -82
  307. package/esm/deps/jsr.io/@std/encoding/1.0.11/base64url.d.ts +0 -39
  308. package/esm/deps/jsr.io/@std/encoding/1.0.11/base64url.js +0 -72
  309. package/esm/deps/jsr.io/@std/encoding/1.0.11/hex.d.ts +0 -38
  310. package/esm/deps/jsr.io/@std/encoding/1.0.11/hex.js +0 -87
  311. package/esm/deps/jsr.io/@std/encoding/1.0.11/mod.d.ts +0 -97
  312. package/esm/deps/jsr.io/@std/encoding/1.0.11/mod.js +0 -99
  313. package/esm/deps/jsr.io/@std/encoding/1.0.11/varint.d.ts +0 -119
  314. package/esm/deps/jsr.io/@std/encoding/1.0.11/varint.js +0 -209
  315. package/esm/deps/jsr.io/@std/http/1.1.3/status.d.ts +0 -343
  316. package/esm/deps/jsr.io/@std/http/1.1.3/status.js +0 -343
  317. package/esm/deps/jsr.io/@std/internal/1.0.14/assertion_state.d.ts +0 -159
  318. package/esm/deps/jsr.io/@std/internal/1.0.14/assertion_state.js +0 -223
  319. package/esm/deps/jsr.io/@std/internal/1.0.14/build_message.d.ts +0 -79
  320. package/esm/deps/jsr.io/@std/internal/1.0.14/build_message.js +0 -116
  321. package/esm/deps/jsr.io/@std/internal/1.0.14/diff.d.ts +0 -139
  322. package/esm/deps/jsr.io/@std/internal/1.0.14/diff.js +0 -277
  323. package/esm/deps/jsr.io/@std/internal/1.0.14/diff_str.d.ts +0 -98
  324. package/esm/deps/jsr.io/@std/internal/1.0.14/diff_str.js +0 -181
  325. package/esm/deps/jsr.io/@std/internal/1.0.14/format.d.ts +0 -29
  326. package/esm/deps/jsr.io/@std/internal/1.0.14/format.js +0 -68
  327. package/esm/deps/jsr.io/@std/internal/1.0.14/styles.d.ts +0 -158
  328. package/esm/deps/jsr.io/@std/internal/1.0.14/styles.js +0 -206
  329. package/esm/deps/jsr.io/@std/internal/1.0.14/types.d.ts +0 -24
  330. package/esm/deps/jsr.io/@std/internal/1.0.14/types.js +0 -3
  331. package/esm/deps/jsr.io/@std/testing/1.0.20/_test_suite.d.ts +0 -91
  332. package/esm/deps/jsr.io/@std/testing/1.0.20/_test_suite.js +0 -347
  333. package/esm/deps/jsr.io/@std/testing/1.0.20/bdd.d.ts +0 -382
  334. package/esm/deps/jsr.io/@std/testing/1.0.20/bdd.js +0 -1110
  335. package/esm/deps/jsr.io/@udibo/http-error/0.11.1/mod.d.ts +0 -363
  336. package/esm/deps/jsr.io/@udibo/http-error/0.11.1/mod.js +0 -616
  337. package/esm/package.json +0 -3
  338. /package/{esm → dist}/adapters/hono/bff/cookie-prefix.d.ts +0 -0
  339. /package/{esm → dist}/adapters/hono/bff/proxy.d.ts +0 -0
  340. /package/{esm → dist}/client/_http.js +0 -0
  341. /package/{esm → dist}/client/base.js +0 -0
  342. /package/{esm → dist}/client/browser-storage.js +0 -0
  343. /package/{esm → dist}/client/check.d.ts +0 -0
  344. /package/{esm → dist}/client/events.js +0 -0
  345. /package/{esm → dist}/client/storage.d.ts +0 -0
  346. /package/{esm → dist}/client/storage.js +0 -0
  347. /package/{esm → dist}/identity/captcha.d.ts +0 -0
  348. /package/{esm → dist}/identity/captcha.js +0 -0
  349. /package/{esm → dist}/identity/delivery.d.ts +0 -0
  350. /package/{esm → dist}/identity/delivery.js +0 -0
  351. /package/{esm → dist}/identity/errors.d.ts +0 -0
  352. /package/{esm → dist}/identity/errors.js +0 -0
  353. /package/{esm → dist}/identity/events.js +0 -0
  354. /package/{esm → dist}/identity/external/_token-exchange.d.ts +0 -0
  355. /package/{esm → dist}/identity/external/_token-exchange.js +0 -0
  356. /package/{esm → dist}/identity/external/errors.d.ts +0 -0
  357. /package/{esm → dist}/identity/external/errors.js +0 -0
  358. /package/{esm → dist}/identity/external/provider.d.ts +0 -0
  359. /package/{esm → dist}/identity/external/provider.js +0 -0
  360. /package/{esm → dist}/identity/identifier.d.ts +0 -0
  361. /package/{esm → dist}/identity/identifier.js +0 -0
  362. /package/{esm → dist}/identity/lockout.d.ts +0 -0
  363. /package/{esm → dist}/identity/mfa/recovery-codes.d.ts +0 -0
  364. /package/{esm → dist}/identity/mfa/recovery-codes.js +0 -0
  365. /package/{esm → dist}/identity/password-policy.d.ts +0 -0
  366. /package/{esm → dist}/identity/password-policy.js +0 -0
  367. /package/{esm → dist}/identity/password.d.ts +0 -0
  368. /package/{esm → dist}/identity/rate-limit.js +0 -0
  369. /package/{esm → dist}/identity/session.d.ts +0 -0
  370. /package/{esm → dist}/identity/token-flow.d.ts +0 -0
  371. /package/{esm → dist}/models/authentication.d.ts +0 -0
  372. /package/{esm → dist}/models/authentication.js +0 -0
  373. /package/{esm → dist}/models/authorization-code.js +0 -0
  374. /package/{esm → dist}/models/client.d.ts +0 -0
  375. /package/{esm → dist}/models/client.js +0 -0
  376. /package/{esm → dist}/models/device-authorization.js +0 -0
  377. /package/{esm → dist}/models/responses.d.ts +0 -0
  378. /package/{esm → dist}/models/responses.js +0 -0
  379. /package/{esm → dist}/models/scope.d.ts +0 -0
  380. /package/{esm → dist}/models/scope.js +0 -0
  381. /package/{esm → dist}/models/token.js +0 -0
  382. /package/{esm → dist}/react/components/types.js +0 -0
  383. /package/{esm → dist}/react/context.js +0 -0
  384. /package/{esm → dist}/react/provider.js +0 -0
  385. /package/{esm → dist}/react/require-auth.d.ts +0 -0
  386. /package/{esm → dist}/react/use-authorization.js +0 -0
  387. /package/{esm → dist}/server/_clock-skew.d.ts +0 -0
  388. /package/{esm → dist}/server/client-authentication.js +0 -0
  389. /package/{esm → dist}/server/grants/client-credentials.js +0 -0
  390. /package/{esm → dist}/server/grants/password.js +0 -0
  391. /package/{esm → dist}/server/redirect-uri.d.ts +0 -0
  392. /package/{esm → dist}/server/services/authorization-code.js +0 -0
  393. /package/{esm → dist}/server/services/client.js +0 -0
  394. /package/{esm → dist}/server/services/device-authorization.js +0 -0
  395. /package/{esm → dist}/server/services/user.js +0 -0
  396. /package/{esm → dist}/server/utils/hash.d.ts +0 -0
  397. /package/{esm → dist}/testing/contract/_race.d.ts +0 -0
  398. /package/{esm → dist}/testing/contract/_race.js +0 -0
  399. /package/{esm → dist}/testing/contract/tenant.d.ts +0 -0
  400. /package/{esm → dist}/utils/_body.d.ts +0 -0
  401. /package/{esm → dist}/utils/_body.js +0 -0
  402. /package/{esm → dist}/utils/_buffer.d.ts +0 -0
  403. /package/{esm → dist}/utils/_buffer.js +0 -0
  404. /package/{esm → dist}/utils/_default-fetch.d.ts +0 -0
  405. /package/{esm → dist}/utils/_timing-safe.d.ts +0 -0
  406. /package/{esm → dist}/utils/_timing-safe.js +0 -0
  407. /package/{esm → dist}/utils/authentication-context.js +0 -0
  408. /package/{esm → dist}/utils/basic-auth.d.ts +0 -0
  409. /package/{esm → dist}/utils/basic-auth.js +0 -0
  410. /package/{esm → dist}/utils/pkce.d.ts +0 -0
  411. /package/{esm → dist}/utils/text.d.ts +0 -0
@@ -26,8 +26,7 @@
26
26
  *
27
27
  * @module
28
28
  */
29
- import { assert, assertEquals, assertFalse, assertStringIncludes, } from "../../deps/jsr.io/@std/assert/1.0.19/mod.js";
30
- import { afterAll, beforeAll, describe, it } from "../../deps/jsr.io/@std/testing/1.0.20/bdd.js";
29
+ import { afterAll, assert, beforeAll, describe, expect, it } from "vitest";
31
30
  import { encodeBasicAuth } from "../../utils/basic-auth.js";
32
31
  /**
33
32
  * Registers the tenant contract suite. The permissions and resource type it
@@ -92,7 +91,7 @@ export function runTenantContractTests(options) {
92
91
  headers: { authorization: clientAuth() },
93
92
  body: new URLSearchParams({ token: accessToken }),
94
93
  });
95
- assertEquals(response.status, 200);
94
+ expect(response.status).toStrictEqual(200);
96
95
  return await response.json();
97
96
  }
98
97
  async function ask(accessToken, path, body) {
@@ -107,27 +106,27 @@ export function runTenantContractTests(options) {
107
106
  const text = await response.text();
108
107
  return { status: response.status, body: text ? JSON.parse(text) : {} };
109
108
  }
110
- const sorted = (values) => [...values ?? []].sort();
109
+ const sorted = (values) => [...(values ?? [])].sort();
111
110
  describe("introspection", () => {
112
111
  it("reports tenant-wide permissions and no organization for a sign-in that picked none", async () => {
113
112
  const { access_token } = await tenant.signIn(people.member);
114
113
  const claims = await introspect(access_token);
115
- assertEquals(claims.active, true);
116
- assertEquals(claims.sub, people.member);
117
- assertEquals(sorted(claims.permissions), ["contract:tenant"]);
118
- assertFalse(claims.org_id, "a sign-in with no organization names none");
114
+ expect(claims.active).toStrictEqual(true);
115
+ expect(claims.sub).toStrictEqual(people.member);
116
+ expect(sorted(claims.permissions)).toStrictEqual(["contract:tenant"]);
117
+ expect(claims.org_id, "a sign-in with no organization names none").toBeFalsy();
119
118
  });
120
119
  it("picks a person's only organization when the sign-in names none", async () => {
121
120
  const { access_token } = await tenant.signIn(people.solo);
122
121
  const claims = await introspect(access_token);
123
- assertEquals(claims.org_id, organizations.other);
124
- assertEquals(sorted(claims.permissions), ["contract:other"]);
122
+ expect(claims.org_id).toStrictEqual(organizations.other);
123
+ expect(sorted(claims.permissions)).toStrictEqual(["contract:other"]);
125
124
  });
126
125
  it("adds the picked organization and its permissions, and no other's", async () => {
127
126
  const { access_token } = await tenant.signIn(people.member, organizations.home);
128
127
  const claims = await introspect(access_token);
129
- assertEquals(claims.org_id, organizations.home);
130
- assertEquals(sorted(claims.permissions), [
128
+ expect(claims.org_id).toStrictEqual(organizations.home);
129
+ expect(sorted(claims.permissions)).toStrictEqual([
131
130
  "contract:home",
132
131
  "contract:tenant",
133
132
  ]);
@@ -143,37 +142,37 @@ export function runTenantContractTests(options) {
143
142
  refresh_token: issued.refresh_token,
144
143
  }),
145
144
  });
146
- assertEquals(response.status, 200);
145
+ expect(response.status).toStrictEqual(200);
147
146
  const refreshed = await response.json();
148
147
  const claims = await introspect(refreshed.access_token);
149
- assertEquals(claims.org_id, organizations.other);
148
+ expect(claims.org_id).toStrictEqual(organizations.other);
150
149
  });
151
150
  it("stops answering for an organization once the membership ends", async () => {
152
151
  const { access_token } = await tenant.signIn(people.leaver, organizations.home);
153
152
  await tenant.removeMember(organizations.home, people.leaver);
154
153
  const claims = await introspect(access_token);
155
- assertFalse(claims.org_id);
156
- assertEquals(sorted(claims.permissions), []);
154
+ expect(claims.org_id).toBeFalsy();
155
+ expect(sorted(claims.permissions)).toStrictEqual([]);
157
156
  });
158
157
  });
159
158
  describe("GET /api/memberships", () => {
160
159
  it("lists the organizations the caller belongs to, and no others", async () => {
161
160
  const { access_token } = await tenant.signIn(people.member);
162
161
  const response = await send(new URL("/api/memberships", tenant.issuer), { headers: { authorization: `Bearer ${access_token}` } });
163
- assertEquals(response.status, 200);
164
- const body = await response.json();
165
- assertEquals(body.memberships.map((membership) => membership.org_id).sort(), [organizations.home, organizations.other].sort());
162
+ expect(response.status).toStrictEqual(200);
163
+ const body = (await response.json());
164
+ expect(body.memberships.map((membership) => membership.org_id).sort()).toStrictEqual([organizations.home, organizations.other].sort());
166
165
  for (const membership of body.memberships) {
167
- assertEquals(membership.roles, ["member"]);
166
+ expect(membership.roles).toStrictEqual(["member"]);
168
167
  assert(membership.org_slug, "every membership names its slug");
169
168
  assert(membership.name, "every membership names its organization");
170
169
  }
171
- assertEquals(body.cursor, null);
170
+ expect(body.cursor).toStrictEqual(null);
172
171
  });
173
172
  it("refuses a caller with no bearer token", async () => {
174
173
  const response = await send(new URL("/api/memberships", tenant.issuer));
175
174
  await response.body?.cancel();
176
- assertEquals(response.status, 401);
175
+ expect(response.status).toStrictEqual(401);
177
176
  });
178
177
  });
179
178
  describe("POST /api/check", () => {
@@ -182,8 +181,8 @@ export function runTenantContractTests(options) {
182
181
  const answer = await ask(access_token, "/api/check", {
183
182
  permissions: ["contract:home", "contract:other", "contract:tenant"],
184
183
  });
185
- assertEquals(answer.status, 200);
186
- assertEquals(answer.body, {
184
+ expect(answer.status).toStrictEqual(200);
185
+ expect(answer.body).toStrictEqual({
187
186
  subject: people.member,
188
187
  resource: { type: "organization", id: organizations.home },
189
188
  results: {
@@ -198,7 +197,7 @@ export function runTenantContractTests(options) {
198
197
  const answer = await ask(access_token, "/api/check", {
199
198
  permissions: ["contract:home", "contract:tenant"],
200
199
  });
201
- assertEquals(answer.body, {
200
+ expect(answer.body).toStrictEqual({
202
201
  subject: people.member,
203
202
  resource: null,
204
203
  results: { "contract:home": false, "contract:tenant": true },
@@ -210,8 +209,8 @@ export function runTenantContractTests(options) {
210
209
  permissions: ["contract:other"],
211
210
  resource: { type: "organization", id: organizations.other },
212
211
  });
213
- assertEquals(answer.status, 200);
214
- assertEquals(answer.body.results, { "contract:other": true });
212
+ expect(answer.status).toStrictEqual(200);
213
+ expect(answer.body.results).toStrictEqual({ "contract:other": true });
215
214
  });
216
215
  it("answers an organization the caller does not belong to from tenant-wide permissions alone", async () => {
217
216
  const { access_token } = await tenant.signIn(people.member);
@@ -219,8 +218,8 @@ export function runTenantContractTests(options) {
219
218
  permissions: ["contract:other", "contract:tenant"],
220
219
  resource: { type: "organization", id: organizations.unjoined },
221
220
  });
222
- assertEquals(answer.status, 200);
223
- assertEquals(answer.body.results, {
221
+ expect(answer.status).toStrictEqual(200);
222
+ expect(answer.body.results).toStrictEqual({
224
223
  "contract:other": false,
225
224
  "contract:tenant": true,
226
225
  });
@@ -231,7 +230,7 @@ export function runTenantContractTests(options) {
231
230
  permissions: ["contract:tenant"],
232
231
  resource: { type: "organization", id: crypto.randomUUID() },
233
232
  });
234
- assertEquals(answer.status, 404);
233
+ expect(answer.status).toStrictEqual(404);
235
234
  });
236
235
  it("answers a resource from a grant to the person and from one to their organization", async () => {
237
236
  const { access_token } = await tenant.signIn(people.outsider);
@@ -239,8 +238,8 @@ export function runTenantContractTests(options) {
239
238
  permissions: ["contract:read", "contract:write"],
240
239
  resource: { type: "contract_document", id: "direct" },
241
240
  });
242
- assertEquals(direct.status, 200);
243
- assertEquals(direct.body.results, {
241
+ expect(direct.status).toStrictEqual(200);
242
+ expect(direct.body.results).toStrictEqual({
244
243
  "contract:read": true,
245
244
  "contract:write": false,
246
245
  });
@@ -248,7 +247,7 @@ export function runTenantContractTests(options) {
248
247
  permissions: ["contract:write"],
249
248
  resource: { type: "contract_document", id: "shared" },
250
249
  });
251
- assertEquals(shared.body.results, { "contract:write": true });
250
+ expect(shared.body.results).toStrictEqual({ "contract:write": true });
252
251
  });
253
252
  it("leaves organization permissions out of a resource answer", async () => {
254
253
  const { access_token } = await tenant.signIn(people.member, organizations.home);
@@ -256,7 +255,7 @@ export function runTenantContractTests(options) {
256
255
  permissions: ["contract:home", "contract:tenant"],
257
256
  resource: { type: "contract_document", id: "direct" },
258
257
  });
259
- assertEquals(answer.body.results, {
258
+ expect(answer.body.results).toStrictEqual({
260
259
  "contract:home": false,
261
260
  "contract:tenant": true,
262
261
  });
@@ -267,7 +266,7 @@ export function runTenantContractTests(options) {
267
266
  permissions: ["contract:read"],
268
267
  resource: { type: "contract_unregistered", id: "x" },
269
268
  });
270
- assertEquals(answer.status, 400);
269
+ expect(answer.status).toStrictEqual(400);
271
270
  });
272
271
  it("refuses a caller with no bearer token", async () => {
273
272
  const response = await send(new URL("/api/check", tenant.issuer), {
@@ -276,7 +275,7 @@ export function runTenantContractTests(options) {
276
275
  body: JSON.stringify({ permissions: ["contract:tenant"] }),
277
276
  });
278
277
  await response.body?.cancel();
279
- assertEquals(response.status, 401);
278
+ expect(response.status).toStrictEqual(401);
280
279
  });
281
280
  });
282
281
  describe("POST /api/check/batch", () => {
@@ -286,8 +285,8 @@ export function runTenantContractTests(options) {
286
285
  permissions: ["contract:read"],
287
286
  resource: { type: "contract_document", ids: ["direct", "unseen"] },
288
287
  });
289
- assertEquals(answer.status, 200);
290
- assertEquals(answer.body, {
288
+ expect(answer.status).toStrictEqual(200);
289
+ expect(answer.body).toStrictEqual({
291
290
  subject: people.outsider,
292
291
  resource: { type: "contract_document" },
293
292
  results: {
@@ -336,13 +335,13 @@ export function runTenantContractTests(options) {
336
335
  }
337
336
  async function membershipsOf(accessToken) {
338
337
  const reply = await call(accessToken, "GET", "/api/memberships");
339
- assertEquals(reply.status, 200);
338
+ expect(reply.status).toStrictEqual(200);
340
339
  return reply.body.memberships;
341
340
  }
342
341
  async function createOrganization(accessToken) {
343
342
  const slug = newSlug();
344
343
  const reply = await call(accessToken, "POST", "/api/organizations", { name: `Contract ${slug}`, slug });
345
- assertEquals(reply.status, 201, JSON.stringify(reply.body));
344
+ expect(reply.status, JSON.stringify(reply.body)).toStrictEqual(201);
346
345
  return reply.body;
347
346
  }
348
347
  async function invite(accessToken, organizationId, email, role) {
@@ -350,15 +349,15 @@ export function runTenantContractTests(options) {
350
349
  }
351
350
  async function accept(accessToken, offerId) {
352
351
  const reply = await call(accessToken, "POST", `/api/organizations/offers/${offerId}/accept`);
353
- assertEquals(reply.status, 200, JSON.stringify(reply.body));
352
+ expect(reply.status, JSON.stringify(reply.body)).toStrictEqual(200);
354
353
  return reply.body;
355
354
  }
356
355
  async function seat(managerToken, organizationId, invitee, role) {
357
356
  const offered = await invite(managerToken, organizationId, invitee.email, role);
358
- assertEquals(offered.status, 201, JSON.stringify(offered.body));
357
+ expect(offered.status, JSON.stringify(offered.body)).toStrictEqual(201);
359
358
  assert(offered.body.status === "membership", offered.body.status);
360
359
  const accepted = await accept(invitee.token, offered.body.membership.id);
361
- assertEquals(accepted.status, "accepted");
360
+ expect(accepted.status).toStrictEqual("accepted");
362
361
  }
363
362
  async function membersOf(accessToken, organizationId) {
364
363
  return await call(accessToken, "GET", `/api/organizations/${organizationId}/members`);
@@ -386,114 +385,127 @@ export function runTenantContractTests(options) {
386
385
  });
387
386
  async function invitationIdsOf(accessToken, organizationId) {
388
387
  const listed = await call(accessToken, "GET", `/api/organizations/${organizationId}/invitations`);
389
- assertEquals(listed.status, 200);
388
+ expect(listed.status).toStrictEqual(200);
390
389
  return listed.body.data.map((entry) => entry.id);
391
390
  }
392
391
  it("creates an organization its creator owns", async () => {
393
392
  const creator = await person();
394
393
  const slug = newSlug();
395
394
  const created = await call(creator.token, "POST", "/api/organizations", { name: "Contract Created", slug });
396
- assertEquals(created.status, 201);
397
- assertEquals(created.body.slug, slug);
398
- assertEquals(created.body.name, "Contract Created");
395
+ expect(created.status).toStrictEqual(201);
396
+ expect(created.body.slug).toStrictEqual(slug);
397
+ expect(created.body.name).toStrictEqual("Contract Created");
399
398
  const listed = await call(creator.token, "GET", "/api/organizations");
400
- assertEquals(listed.status, 200);
401
- assertEquals(listed.body.data.map((entry) => entry.id), [created.body.id]);
402
- assertEquals(await membershipsOf(creator.token), [{
399
+ expect(listed.status).toStrictEqual(200);
400
+ expect(listed.body.data.map((entry) => entry.id)).toStrictEqual([
401
+ created.body.id,
402
+ ]);
403
+ expect(await membershipsOf(creator.token)).toStrictEqual([
404
+ {
403
405
  org_id: created.body.id,
404
406
  org_slug: slug,
405
407
  name: "Contract Created",
406
408
  roles: ["owner"],
407
- }]);
409
+ },
410
+ ]);
408
411
  });
409
412
  it("refuses a slug another organization holds", async () => {
410
413
  const reply = await call(seats.outsider.token, "POST", "/api/organizations", { name: "Contract Taken", slug: organization.slug });
411
- assertEquals(reply.status, 409);
414
+ expect(reply.status).toStrictEqual(409);
412
415
  });
413
416
  it("refuses a slug that is not lowercase letters, numbers and single hyphens", async () => {
414
417
  const reply = await call(seats.outsider.token, "POST", "/api/organizations", { name: "Contract Shouting", slug: "Contract--Shouting" });
415
- assertEquals(reply.status, 400);
418
+ expect(reply.status).toStrictEqual(400);
416
419
  });
417
420
  it("lets a member of any tier read the organization and its members, and no cache keep them", async () => {
421
+ const organization = await createOrganization(seats.owner.token);
422
+ await seat(seats.owner.token, organization.id, seats.admin, "admin");
423
+ await seat(seats.owner.token, organization.id, seats.member, "member");
418
424
  const read = await call(seats.member.token, "GET", `/api/organizations/${organization.id}`);
419
- assertEquals(read.status, 200);
420
- assertEquals(read.body.id, organization.id);
421
- assertEquals(read.body.slug, organization.slug);
422
- assertStringIncludes(read.headers.get("cache-control") ?? "", "no-store");
425
+ expect(read.status).toStrictEqual(200);
426
+ expect(read.body.id).toStrictEqual(organization.id);
427
+ expect(read.body.slug).toStrictEqual(organization.slug);
428
+ expect(read.headers.get("cache-control") ?? "").toContain("no-store");
423
429
  const members = await membersOf(seats.member.token, organization.id);
424
- assertEquals(members.status, 200);
425
- assertStringIncludes(members.headers.get("cache-control") ?? "", "no-store");
426
- assertEquals(members.body.data.map((row) => `${row.userId}:${row.role}`).sort(), [
430
+ expect(members.status).toStrictEqual(200);
431
+ expect(members.headers.get("cache-control") ?? "").toContain("no-store");
432
+ expect(members.body.data.map((row) => `${row.userId}:${row.role}`).sort()).toStrictEqual([
427
433
  `${seats.admin.id}:admin`,
428
434
  `${seats.member.id}:member`,
429
435
  `${seats.owner.id}:owner`,
430
436
  ].sort());
431
- assertFalse(members.body.hasMore);
437
+ expect(members.body.hasMore).toBeFalsy();
432
438
  for (const row of members.body.data) {
433
439
  assert(row.id, "every member row names its grant");
434
440
  assert(row.name, "every member row names its person");
435
441
  assert(row.acceptedAt, "a member row is an accepted grant");
436
442
  }
437
- assertEquals(members.body.data.find((row) => row.userId === seats.member.id)
438
- ?.email, seats.member.email);
443
+ expect(members.body.data.find((row) => row.userId === seats.member.id)
444
+ ?.email).toStrictEqual(seats.member.email);
439
445
  });
440
446
  it("answers a non-member, an unknown id and a malformed id with the same 404", async () => {
441
447
  const outsider = await call(seats.outsider.token, "GET", `/api/organizations/${organization.id}`);
442
448
  const unknown = await call(seats.owner.token, "GET", `/api/organizations/${crypto.randomUUID()}`);
443
449
  const malformed = await call(seats.owner.token, "GET", "/api/organizations/not-an-organization");
444
- assertEquals([outsider.status, unknown.status, malformed.status], [404, 404, 404]);
445
- assertEquals(outsider.body.detail, unknown.body.detail);
446
- assertEquals(unknown.body.detail, malformed.body.detail);
450
+ expect([
451
+ outsider.status,
452
+ unknown.status,
453
+ malformed.status,
454
+ ]).toStrictEqual([404, 404, 404]);
455
+ expect(outsider.body.detail).toStrictEqual(unknown.body.detail);
456
+ expect(unknown.body.detail).toStrictEqual(malformed.body.detail);
447
457
  const members = await membersOf(seats.outsider.token, organization.id);
448
- assertEquals(members.status, 404);
458
+ expect(members.status).toStrictEqual(404);
449
459
  });
450
460
  it("lets an owner or admin rename the organization and a plain member not", async () => {
451
461
  const renamed = await call(seats.admin.token, "PATCH", `/api/organizations/${organization.id}`, { name: "Contract Renamed" });
452
- assertEquals(renamed.status, 200);
453
- assertEquals(renamed.body.name, "Contract Renamed");
454
- assertEquals(renamed.body.slug, organization.slug);
462
+ expect(renamed.status).toStrictEqual(200);
463
+ expect(renamed.body.name).toStrictEqual("Contract Renamed");
464
+ expect(renamed.body.slug).toStrictEqual(organization.slug);
455
465
  for (const caller of [seats.member, seats.outsider]) {
456
466
  const refused = await call(caller.token, "PATCH", `/api/organizations/${organization.id}`, { name: "Contract Hijacked" });
457
- assertEquals(refused.status, 404);
467
+ expect(refused.status).toStrictEqual(404);
458
468
  }
459
469
  });
460
470
  it("lists the membership tiers an invitation may offer, to a manager only", async () => {
461
471
  const roles = await call(seats.admin.token, "GET", `/api/organizations/${organization.id}/member-roles`);
462
- assertEquals(roles.status, 200);
463
- assertEquals(roles.body.slice(0, 3), [
472
+ expect(roles.status).toStrictEqual(200);
473
+ expect(roles.body.slice(0, 3)).toStrictEqual([
464
474
  { slug: "owner", name: "Owner" },
465
475
  { slug: "admin", name: "Admin" },
466
476
  { slug: "member", name: "Member" },
467
477
  ]);
468
478
  const refused = await call(seats.member.token, "GET", `/api/organizations/${organization.id}/member-roles`);
469
- assertEquals(refused.status, 404);
479
+ expect(refused.status).toStrictEqual(404);
470
480
  });
471
481
  it("offers an existing person a pending membership that confers nothing until they accept it", async () => {
472
482
  const invitee = await person();
473
483
  const offered = await invite(seats.admin.token, organization.id, invitee.email, "member");
474
- assertEquals(offered.status, 201);
484
+ expect(offered.status).toStrictEqual(201);
475
485
  assert(offered.body.status === "membership", offered.body.status);
476
- assertEquals(offered.body.membership.role, "member");
477
- assertEquals(offered.body.membership.acceptedAt, null);
478
- assertEquals(await membershipsOf(invitee.token), []);
486
+ expect(offered.body.membership.role).toStrictEqual("member");
487
+ expect(offered.body.membership.acceptedAt).toStrictEqual(null);
488
+ expect(await membershipsOf(invitee.token)).toStrictEqual([]);
479
489
  const before = await membersOf(seats.admin.token, organization.id);
480
- assertFalse(before.body.data.some((row) => row.userId === invitee.id), "a pending grant is not a member");
490
+ expect(before.body.data.some((row) => row.userId === invitee.id), "a pending grant is not a member").toBeFalsy();
481
491
  const waiting = await call(invitee.token, "GET", "/api/organizations/offers");
482
- assertEquals(waiting.status, 200);
492
+ expect(waiting.status).toStrictEqual(200);
483
493
  const current = await call(seats.owner.token, "GET", `/api/organizations/${organization.id}`);
484
- assertEquals(waiting.body, {
485
- offers: [{
494
+ expect(waiting.body).toStrictEqual({
495
+ offers: [
496
+ {
486
497
  id: offered.body.membership.id,
487
498
  organizationName: current.body.name,
488
499
  roleName: "Member",
489
- }],
500
+ },
501
+ ],
490
502
  unverifiedEmail: null,
491
503
  });
492
504
  const accepted = await accept(invitee.token, offered.body.membership.id);
493
- assertEquals(accepted.status, "accepted");
494
- assertEquals(accepted.membership?.organizationId, organization.id);
495
- assertEquals(accepted.membership?.role, "member");
496
- assertEquals((await membershipsOf(invitee.token)).map((entry) => entry.roles), [["member"]]);
505
+ expect(accepted.status).toStrictEqual("accepted");
506
+ expect(accepted.membership?.organizationId).toStrictEqual(organization.id);
507
+ expect(accepted.membership?.role).toStrictEqual("member");
508
+ expect((await membershipsOf(invitee.token)).map((entry) => entry.roles)).toStrictEqual([["member"]]);
497
509
  const after = await membersOf(seats.admin.token, organization.id);
498
510
  assert(after.body.data.some((row) => row.userId === invitee.id));
499
511
  });
@@ -502,24 +514,24 @@ export function runTenantContractTests(options) {
502
514
  const offered = await invite(seats.owner.token, organization.id, invitee.email, "member");
503
515
  assert(offered.body.status === "membership", offered.body.status);
504
516
  const offerId = offered.body.membership.id;
505
- assertEquals((await accept(seats.outsider.token, offerId)).status, "invalid");
506
- assertEquals((await accept(seats.outsider.token, "not-an-offer")).status, "invalid");
507
- assertEquals((await accept(invitee.token, offerId)).status, "accepted");
517
+ expect((await accept(seats.outsider.token, offerId)).status).toStrictEqual("invalid");
518
+ expect((await accept(seats.outsider.token, "not-an-offer")).status).toStrictEqual("invalid");
519
+ expect((await accept(invitee.token, offerId)).status).toStrictEqual("accepted");
508
520
  });
509
521
  it("refuses a second offer of a role an address already holds or was offered", async () => {
510
522
  const stranger = newEmail();
511
523
  const first = await invite(seats.admin.token, organization.id, stranger, "member");
512
- assertEquals(first.status, 201);
524
+ expect(first.status).toStrictEqual(201);
513
525
  const again = await invite(seats.admin.token, organization.id, stranger, "member");
514
- assertEquals(again.status, 409);
526
+ expect(again.status).toStrictEqual(409);
515
527
  const held = await invite(seats.admin.token, organization.id, seats.member.email, "member");
516
- assertEquals(held.status, 409);
528
+ expect(held.status).toStrictEqual(409);
517
529
  });
518
530
  it("keeps the owner tier to owners", async () => {
519
531
  const offered = await invite(seats.admin.token, organization.id, newEmail(), "owner");
520
- assertEquals(offered.status, 403);
532
+ expect(offered.status).toStrictEqual(403);
521
533
  const revoked = await call(seats.admin.token, "DELETE", `/api/organizations/${organization.id}/members/${seats.owner.id}/owner`);
522
- assertEquals(revoked.status, 403);
534
+ expect(revoked.status).toStrictEqual(403);
523
535
  });
524
536
  it("refuses a plain member every manager action with the 404 a stranger gets, and changes nothing", async () => {
525
537
  const pending = await invite(seats.admin.token, organization.id, newEmail(), "member");
@@ -534,8 +546,8 @@ export function runTenantContractTests(options) {
534
546
  for (const [method, path] of actions) {
535
547
  const byMember = await call(seats.member.token, method, path);
536
548
  const byStranger = await call(seats.outsider.token, method, path);
537
- assertEquals([byMember.status, byStranger.status], [404, 404], `${method} ${path}`);
538
- assertEquals(byMember.body.detail, byStranger.body.detail);
549
+ expect([byMember.status, byStranger.status], `${method} ${path}`).toStrictEqual([404, 404]);
550
+ expect(byMember.body.detail).toStrictEqual(byStranger.body.detail);
539
551
  }
540
552
  const members = await membersOf(seats.owner.token, organization.id);
541
553
  assert(members.body.data.some((row) => row.userId === seats.admin.id && row.role === "admin"), "a plain member revoked a manager's tier");
@@ -544,10 +556,10 @@ export function runTenantContractTests(options) {
544
556
  it("keeps a manager's actions to the organization the path names", async () => {
545
557
  const base = `/api/organizations/${organization.id}`;
546
558
  const withdrawn = await call(seats.owner.token, "DELETE", `${base}/invitations/${rivalInvitationId}`);
547
- assertEquals(withdrawn.status, 404);
559
+ expect(withdrawn.status).toStrictEqual(404);
548
560
  const revoked = await call(seats.owner.token, "DELETE", `${base}/members/${seats.rivalMember.id}/member`);
549
- assertEquals(revoked.status, 404);
550
- assertFalse((await invitationIdsOf(seats.owner.token, organization.id)).includes(rivalInvitationId), "one organization's invitations listed another's");
561
+ expect(revoked.status).toStrictEqual(404);
562
+ expect((await invitationIdsOf(seats.owner.token, organization.id)).includes(rivalInvitationId), "one organization's invitations listed another's").toBeFalsy();
551
563
  const rivalBase = `/api/organizations/${rival.id}`;
552
564
  for (const [method, path] of [
553
565
  ["DELETE", `${rivalBase}/invitations/${rivalInvitationId}`],
@@ -555,7 +567,7 @@ export function runTenantContractTests(options) {
555
567
  ["GET", `${rivalBase}/invitations`],
556
568
  ]) {
557
569
  const refused = await call(seats.owner.token, method, path);
558
- assertEquals(refused.status, 404, `${method} ${path}`);
570
+ expect(refused.status, `${method} ${path}`).toStrictEqual(404);
559
571
  }
560
572
  assert((await invitationIdsOf(seats.rival.token, rival.id)).includes(rivalInvitationId), "another organization's manager withdrew this invitation");
561
573
  const rivalMembers = await membersOf(seats.rival.token, rival.id);
@@ -563,11 +575,11 @@ export function runTenantContractTests(options) {
563
575
  });
564
576
  it("refuses an invitation from a plain member as it refuses a stranger", async () => {
565
577
  const offered = await invite(seats.member.token, organization.id, newEmail(), "member");
566
- assertEquals(offered.status, 404);
578
+ expect(offered.status).toStrictEqual(404);
567
579
  });
568
580
  it("refuses a role the tenant has not defined", async () => {
569
581
  const offered = await invite(seats.admin.token, organization.id, newEmail(), "contract-undefined-role");
570
- assertEquals(offered.status, 400);
582
+ expect(offered.status).toStrictEqual(400);
571
583
  });
572
584
  it("refuses a return_to on an origin with no registered redirect URI", async () => {
573
585
  const reply = await call(seats.admin.token, "POST", `/api/organizations/${organization.id}/invitations`, {
@@ -575,25 +587,27 @@ export function runTenantContractTests(options) {
575
587
  role: "member",
576
588
  return_to: "https://unregistered.example/welcome",
577
589
  });
578
- assertEquals(reply.status, 400);
590
+ expect(reply.status).toStrictEqual(400);
579
591
  });
580
592
  it("invites an address with no account, lowercased, for its verified holder to accept later", async () => {
581
593
  const address = newEmail();
582
594
  const offered = await invite(seats.admin.token, organization.id, address.toUpperCase(), "member");
583
- assertEquals(offered.status, 201);
595
+ expect(offered.status).toStrictEqual(201);
584
596
  assert(offered.body.status === "invitation", offered.body.status);
585
- assertEquals(offered.body.invitation.email, address);
597
+ expect(offered.body.invitation.email).toStrictEqual(address);
586
598
  const invitationId = offered.body.invitation.id;
587
599
  const listed = await call(seats.admin.token, "GET", `/api/organizations/${organization.id}/invitations?status=outstanding`);
588
- assertEquals(listed.status, 200);
600
+ expect(listed.status).toStrictEqual(200);
589
601
  assert(listed.body.data.some((entry) => entry.id === invitationId));
590
602
  const holder = await person({ email: address });
591
603
  const waiting = await call(holder.token, "GET", "/api/organizations/offers");
592
- assertEquals(waiting.body.offers.map((offer) => offer.id), [invitationId]);
604
+ expect(waiting.body.offers.map((offer) => offer.id)).toStrictEqual([
605
+ invitationId,
606
+ ]);
593
607
  const accepted = await accept(holder.token, invitationId);
594
- assertEquals(accepted.status, "accepted");
595
- assertEquals(accepted.membership?.organizationId, organization.id);
596
- assertEquals((await membershipsOf(holder.token)).map((entry) => entry.org_id), [organization.id]);
608
+ expect(accepted.status).toStrictEqual("accepted");
609
+ expect(accepted.membership?.organizationId).toStrictEqual(organization.id);
610
+ expect((await membershipsOf(holder.token)).map((entry) => entry.org_id)).toStrictEqual([organization.id]);
597
611
  });
598
612
  it("hides an invitation from its address's unverified holder and names the address instead", async () => {
599
613
  const address = newEmail();
@@ -601,14 +615,17 @@ export function runTenantContractTests(options) {
601
615
  assert(offered.body.status === "invitation", offered.body.status);
602
616
  const holder = await person({ email: address, emailVerified: false });
603
617
  const waiting = await call(holder.token, "GET", "/api/organizations/offers");
604
- assertEquals(waiting.body, { offers: [], unverifiedEmail: address });
605
- assertEquals((await accept(holder.token, offered.body.invitation.id)).status, "wrong-account");
618
+ expect(waiting.body).toStrictEqual({
619
+ offers: [],
620
+ unverifiedEmail: address,
621
+ });
622
+ expect((await accept(holder.token, offered.body.invitation.id)).status).toStrictEqual("wrong-account");
606
623
  });
607
624
  it("converts an invitation only for the address it names", async () => {
608
625
  const offered = await invite(seats.admin.token, organization.id, newEmail(), "member");
609
626
  assert(offered.body.status === "invitation", offered.body.status);
610
627
  const other = await person();
611
- assertEquals((await accept(other.token, offered.body.invitation.id)).status, "wrong-account");
628
+ expect((await accept(other.token, offered.body.invitation.id)).status).toStrictEqual("wrong-account");
612
629
  });
613
630
  it("withdraws an invitation so that it can no longer be accepted", async () => {
614
631
  const address = newEmail();
@@ -616,35 +633,35 @@ export function runTenantContractTests(options) {
616
633
  assert(offered.body.status === "invitation", offered.body.status);
617
634
  const invitationId = offered.body.invitation.id;
618
635
  const withdrawn = await call(seats.admin.token, "DELETE", `/api/organizations/${organization.id}/invitations/${invitationId}`);
619
- assertEquals(withdrawn.status, 204);
636
+ expect(withdrawn.status).toStrictEqual(204);
620
637
  const listed = await call(seats.admin.token, "GET", `/api/organizations/${organization.id}/invitations`);
621
- assertFalse(listed.body.data.some((entry) => entry.id === invitationId));
638
+ expect(listed.body.data.some((entry) => entry.id === invitationId)).toBeFalsy();
622
639
  const holder = await person({ email: address });
623
- assertEquals((await accept(holder.token, invitationId)).status, "invalid");
640
+ expect((await accept(holder.token, invitationId)).status).toStrictEqual("invalid");
624
641
  });
625
642
  it("ends a member's access when a manager revokes their tier", async () => {
626
643
  const leaver = await person();
627
644
  await seat(seats.admin.token, organization.id, leaver, "member");
628
645
  const revoked = await call(seats.admin.token, "DELETE", `/api/organizations/${organization.id}/members/${leaver.id}/member`);
629
- assertEquals(revoked.status, 204);
630
- assertEquals(await membershipsOf(leaver.token), []);
646
+ expect(revoked.status).toStrictEqual(204);
647
+ expect(await membershipsOf(leaver.token)).toStrictEqual([]);
631
648
  const read = await call(leaver.token, "GET", `/api/organizations/${organization.id}`);
632
- assertEquals(read.status, 404);
649
+ expect(read.status).toStrictEqual(404);
633
650
  const again = await call(seats.admin.token, "DELETE", `/api/organizations/${organization.id}/members/${leaver.id}/member`);
634
- assertEquals(again.status, 404);
651
+ expect(again.status).toStrictEqual(404);
635
652
  });
636
653
  it("never leaves an organization without an owner", async () => {
637
654
  const reply = await call(seats.owner.token, "DELETE", `/api/organizations/${organization.id}/members/${seats.owner.id}/owner`);
638
- assertEquals(reply.status, 409);
655
+ expect(reply.status).toStrictEqual(409);
639
656
  });
640
657
  it("deletes an organization for its owner alone, and not while it holds a grant", async () => {
641
658
  const doomed = await createOrganization(seats.owner.token);
642
659
  await seat(seats.owner.token, doomed.id, seats.admin, "admin");
643
660
  await seat(seats.owner.token, doomed.id, seats.member, "member");
644
661
  const byAdmin = await call(seats.admin.token, "DELETE", `/api/organizations/${doomed.id}`);
645
- assertEquals(byAdmin.status, 403);
662
+ expect(byAdmin.status).toStrictEqual(403);
646
663
  const byMember = await call(seats.member.token, "DELETE", `/api/organizations/${doomed.id}`);
647
- assertEquals(byMember.status, 404);
664
+ expect(byMember.status).toStrictEqual(404);
648
665
  const holding = await createOrganization(seats.owner.token);
649
666
  await tenant.grant({
650
667
  resource: { type: "contract_document", id: `held-${holding.id}` },
@@ -652,16 +669,16 @@ export function runTenantContractTests(options) {
652
669
  permissions: ["contract:read"],
653
670
  });
654
671
  const blocked = await call(seats.owner.token, "DELETE", `/api/organizations/${holding.id}`);
655
- assertEquals(blocked.status, 409);
672
+ expect(blocked.status).toStrictEqual(409);
656
673
  const deleted = await call(seats.owner.token, "DELETE", `/api/organizations/${doomed.id}`);
657
- assertEquals(deleted.status, 204);
674
+ expect(deleted.status).toStrictEqual(204);
658
675
  const gone = await call(seats.member.token, "GET", `/api/organizations/${doomed.id}`);
659
- assertEquals(gone.status, 404);
660
- assertFalse((await membershipsOf(seats.member.token)).some((entry) => entry.org_id === doomed.id));
676
+ expect(gone.status).toStrictEqual(404);
677
+ expect((await membershipsOf(seats.member.token)).some((entry) => entry.org_id === doomed.id)).toBeFalsy();
661
678
  });
662
679
  it("refuses a caller with no bearer token", async () => {
663
680
  const reply = await call(null, "GET", "/api/organizations");
664
- assertEquals(reply.status, 401);
681
+ expect(reply.status).toStrictEqual(401);
665
682
  });
666
683
  describe("application roles a manager grants a member", () => {
667
684
  const APP_PERMISSION = "contract:app";
@@ -670,7 +687,7 @@ export function runTenantContractTests(options) {
670
687
  beforeAll(async () => {
671
688
  roleId = await tenant.addRole([APP_PERMISSION]);
672
689
  const listed = await call(seats.admin.token, "GET", `/api/organizations/${organization.id}/member-roles`);
673
- assertEquals(listed.status, 200);
690
+ expect(listed.status).toStrictEqual(200);
674
691
  const found = listed.body.find((entry) => entry.id === roleId);
675
692
  assert(found, "member-roles does not list the role by its id");
676
693
  option = found;
@@ -686,77 +703,79 @@ export function runTenantContractTests(options) {
686
703
  }
687
704
  async function heldBy(userId, managerToken = seats.admin.token, organizationId = organization.id) {
688
705
  const reply = await call(managerToken, "GET", rolesPath(userId, organizationId));
689
- assertEquals(reply.status, 200, JSON.stringify(reply.body));
706
+ expect(reply.status, JSON.stringify(reply.body)).toStrictEqual(200);
690
707
  return reply.body;
691
708
  }
692
709
  async function holds(accessToken, organizationId) {
693
710
  const reply = await ask(accessToken, "/api/check", {
694
711
  permissions: [APP_PERMISSION],
695
- ...organizationId
712
+ ...(organizationId
696
713
  ? { resource: { type: "organization", id: organizationId } }
697
- : {},
714
+ : {}),
698
715
  });
699
- assertEquals(reply.status, 200);
716
+ expect(reply.status).toStrictEqual(200);
700
717
  return reply.body.results[APP_PERMISSION];
701
718
  }
702
719
  async function offersOf(accessToken) {
703
720
  const reply = await call(accessToken, "GET", "/api/organizations/offers");
704
- assertEquals(reply.status, 200);
721
+ expect(reply.status).toStrictEqual(200);
705
722
  return reply.body.offers.map((offer) => offer.id).sort();
706
723
  }
707
724
  it("names a tenant-wide role in member-roles by the id a grant takes, and no built-in tier by any", async () => {
708
- assertEquals(option.id, roleId);
725
+ expect(option.id).toStrictEqual(roleId);
709
726
  assert(option.slug, "the role is listed by its slug");
710
727
  assert(option.name, "the role is listed by its name");
711
728
  const listed = await call(seats.owner.token, "GET", `/api/organizations/${organization.id}/member-roles`);
712
729
  for (const slug of ["owner", "admin", "member"]) {
713
730
  const tier = listed.body.find((entry) => entry.slug === slug);
714
731
  assert(tier, `the built-in ${slug} tier is listed`);
715
- assertFalse("id" in tier, `the built-in ${slug} tier has an id`);
732
+ expect("id" in tier, `the built-in ${slug} tier has an id`).toBeFalsy();
716
733
  }
717
734
  });
718
735
  it("grants an accepted member a role that counts inside the organization only", async () => {
719
736
  const member = await seated();
720
737
  await seat(seats.rival.token, rival.id, member, "member");
721
- assertFalse(await holds(member.token, organization.id));
738
+ expect(await holds(member.token, organization.id)).toBeFalsy();
722
739
  const granted = await grantRole(seats.admin.token, member.id);
723
- assertEquals(granted.status, 201, JSON.stringify(granted.body));
724
- assertEquals(granted.body.subjectId, member.id);
725
- assertEquals(granted.body.roleId, roleId);
726
- assertEquals(granted.body.builtInRole, null);
727
- assertEquals(granted.body.scopeType, "organization");
728
- assertEquals(granted.body.scopeId, organization.id);
729
- assertEquals(granted.body.created, true);
740
+ expect(granted.status, JSON.stringify(granted.body)).toStrictEqual(201);
741
+ expect(granted.body.subjectId).toStrictEqual(member.id);
742
+ expect(granted.body.roleId).toStrictEqual(roleId);
743
+ expect(granted.body.builtInRole).toStrictEqual(null);
744
+ expect(granted.body.scopeType).toStrictEqual("organization");
745
+ expect(granted.body.scopeId).toStrictEqual(organization.id);
746
+ expect(granted.body.created).toStrictEqual(true);
730
747
  assert(await holds(member.token, organization.id));
731
- assertFalse(await holds(member.token), "a role held in an organization answered at tenant scope");
732
- assertFalse(await holds(member.token, rival.id), "a role held in one organization answered for another it belongs to");
733
- assertEquals(await heldBy(member.id), [{
748
+ expect(await holds(member.token), "a role held in an organization answered at tenant scope").toBeFalsy();
749
+ expect(await holds(member.token, rival.id), "a role held in one organization answered for another it belongs to").toBeFalsy();
750
+ expect(await heldBy(member.id)).toStrictEqual([
751
+ {
734
752
  id: roleId,
735
753
  builtInRole: null,
736
754
  organizationId: null,
737
755
  slug: option.slug,
738
756
  name: option.name,
739
757
  permissions: [APP_PERMISSION],
740
- }]);
758
+ },
759
+ ]);
741
760
  const again = await grantRole(seats.owner.token, member.id);
742
- assertEquals(again.status, 201);
743
- assertEquals(again.body.id, granted.body.id);
744
- assertEquals(again.body.created, false);
745
- assertEquals((await heldBy(member.id)).length, 1);
761
+ expect(again.status).toStrictEqual(201);
762
+ expect(again.body.id).toStrictEqual(granted.body.id);
763
+ expect(again.body.created).toStrictEqual(false);
764
+ expect((await heldBy(member.id)).length).toStrictEqual(1);
746
765
  });
747
766
  it("revokes a role, after which the permission stops answering and the role is not found", async () => {
748
767
  const member = await seated();
749
- assertEquals((await grantRole(seats.admin.token, member.id)).status, 201);
768
+ expect((await grantRole(seats.admin.token, member.id)).status).toStrictEqual(201);
750
769
  const revoked = await call(seats.admin.token, "DELETE", `${rolesPath(member.id)}/${roleId}`);
751
- assertEquals(revoked.status, 204);
752
- assertFalse(await holds(member.token, organization.id));
753
- assertEquals(await heldBy(member.id), []);
770
+ expect(revoked.status).toStrictEqual(204);
771
+ expect(await holds(member.token, organization.id)).toBeFalsy();
772
+ expect(await heldBy(member.id)).toStrictEqual([]);
754
773
  const again = await call(seats.admin.token, "DELETE", `${rolesPath(member.id)}/${roleId}`);
755
- assertEquals(again.status, 404);
774
+ expect(again.status).toStrictEqual(404);
756
775
  });
757
776
  it("refuses a plain member and a stranger every verb with the 404 an unknown organization gets, and changes nothing", async () => {
758
777
  const member = await seated();
759
- assertEquals((await grantRole(seats.admin.token, member.id)).status, 201);
778
+ expect((await grantRole(seats.admin.token, member.id)).status).toStrictEqual(201);
760
779
  const unknown = crypto.randomUUID();
761
780
  const verbs = [
762
781
  ["GET", (id) => rolesPath(member.id, id), undefined],
@@ -769,11 +788,11 @@ export function runTenantContractTests(options) {
769
788
  ];
770
789
  for (const [method, path, body] of verbs) {
771
790
  const onUnknown = await call(seats.owner.token, method, path(unknown), body);
772
- assertEquals(onUnknown.status, 404, `${method} on an unknown id`);
791
+ expect(onUnknown.status, `${method} on an unknown id`).toStrictEqual(404);
773
792
  for (const caller of [seats.member, seats.outsider]) {
774
793
  const refused = await call(caller.token, method, path(organization.id), body);
775
- assertEquals(refused.status, 404, `${method} below the manager tier`);
776
- assertEquals(refused.body.detail, onUnknown.body.detail);
794
+ expect(refused.status, `${method} below the manager tier`).toStrictEqual(404);
795
+ expect(refused.body.detail).toStrictEqual(onUnknown.body.detail);
777
796
  }
778
797
  }
779
798
  assert(await holds(member.token, organization.id), "a refused revoke took the role away");
@@ -790,31 +809,33 @@ export function runTenantContractTests(options) {
790
809
  ]) {
791
810
  const read = await call(seats.admin.token, "GET", rolesPath(userId));
792
811
  const granted = await grantRole(seats.admin.token, userId);
793
- assertEquals([read.status, granted.status], [404, 404], userId);
812
+ expect([read.status, granted.status], userId).toStrictEqual([
813
+ 404, 404,
814
+ ]);
794
815
  details.add(read.body.detail);
795
816
  details.add(granted.body.detail);
796
817
  }
797
- assertEquals(details.size, 1, [...details].join(" | "));
798
- assertEquals((await accept(pending.token, offered.body.membership.id)).status, "accepted");
799
- assertEquals(await heldBy(pending.id), []);
800
- assertFalse(await holds(pending.token, organization.id));
818
+ expect(details.size, [...details].join(" | ")).toStrictEqual(1);
819
+ expect((await accept(pending.token, offered.body.membership.id)).status).toStrictEqual("accepted");
820
+ expect(await heldBy(pending.id)).toStrictEqual([]);
821
+ expect(await holds(pending.token, organization.id)).toBeFalsy();
801
822
  });
802
823
  it("refuses a role the tenant does not know with 404, and a grant that names no role with 400", async () => {
803
824
  const member = await seated();
804
825
  const unknown = await grantRole(seats.admin.token, member.id, crypto.randomUUID());
805
- assertEquals(unknown.status, 404);
826
+ expect(unknown.status).toStrictEqual(404);
806
827
  const unnamed = await call(seats.admin.token, "POST", rolesPath(member.id), {});
807
- assertEquals(unnamed.status, 400);
808
- assertEquals(await heldBy(member.id), []);
828
+ expect(unnamed.status).toStrictEqual(400);
829
+ expect(await heldBy(member.id)).toStrictEqual([]);
809
830
  });
810
831
  it("refuses a manager revoking a built-in role they do not hold there, and answers any other name with 404", async () => {
811
832
  const member = await seated();
812
833
  const asAdmin = await call(seats.admin.token, "DELETE", `${rolesPath(member.id)}/owner`);
813
- assertEquals(asAdmin.status, 403);
834
+ expect(asAdmin.status).toStrictEqual(403);
814
835
  const asOwner = await call(seats.owner.token, "DELETE", `${rolesPath(member.id)}/member`);
815
- assertEquals(asOwner.status, 403, "an owner membership is not a built-in role held there");
836
+ expect(asOwner.status, "an owner membership is not a built-in role held there").toStrictEqual(403);
816
837
  const unnamed = await call(seats.owner.token, "DELETE", `${rolesPath(member.id)}/not-a-role`);
817
- assertEquals(unnamed.status, 404);
838
+ expect(unnamed.status).toStrictEqual(404);
818
839
  });
819
840
  it("ends the roles and open offers a person's last membership carried, so rejoining restores nothing", async () => {
820
841
  const address = newEmail();
@@ -825,18 +846,17 @@ export function runTenantContractTests(options) {
825
846
  await seat(seats.owner.token, organization.id, leaver, "member");
826
847
  const ownerOffer = await invite(seats.owner.token, organization.id, address, "owner");
827
848
  assert(ownerOffer.body.status === "membership", ownerOffer.body.status);
828
- assertEquals((await grantRole(seats.admin.token, leaver.id)).status, 201);
829
- assertEquals(await offersOf(leaver.token), [invitationId, ownerOffer.body.membership.id].sort());
849
+ expect((await grantRole(seats.admin.token, leaver.id)).status).toStrictEqual(201);
850
+ expect(await offersOf(leaver.token)).toStrictEqual([invitationId, ownerOffer.body.membership.id].sort());
830
851
  const revoked = await call(seats.admin.token, "DELETE", `/api/organizations/${organization.id}/members/${leaver.id}/member`);
831
- assertEquals(revoked.status, 204);
832
- assertEquals(await offersOf(leaver.token), []);
833
- assertFalse((await invitationIdsOf(seats.owner.token, organization.id))
834
- .includes(invitationId), "an invitation to the leaver's address outlived their membership");
852
+ expect(revoked.status).toStrictEqual(204);
853
+ expect(await offersOf(leaver.token)).toStrictEqual([]);
854
+ expect((await invitationIdsOf(seats.owner.token, organization.id)).includes(invitationId), "an invitation to the leaver's address outlived their membership").toBeFalsy();
835
855
  const read = await call(seats.admin.token, "GET", rolesPath(leaver.id));
836
- assertEquals(read.status, 404);
856
+ expect(read.status).toStrictEqual(404);
837
857
  await seat(seats.owner.token, organization.id, leaver, "member");
838
- assertEquals(await heldBy(leaver.id), []);
839
- assertFalse(await holds(leaver.token, organization.id), "rejoining restored a role the last membership carried");
858
+ expect(await heldBy(leaver.id)).toStrictEqual([]);
859
+ expect(await holds(leaver.token, organization.id), "rejoining restored a role the last membership carried").toBeFalsy();
840
860
  });
841
861
  it("keeps a role revoke and a membership's end to the person and organization they name", async () => {
842
862
  const address = newEmail();
@@ -847,19 +867,19 @@ export function runTenantContractTests(options) {
847
867
  const bystander = await seated();
848
868
  await seat(seats.rival.token, rival.id, bystander, "member");
849
869
  for (const userId of [leaver.id, bystander.id]) {
850
- assertEquals((await grantRole(seats.admin.token, userId)).status, 201);
870
+ expect((await grantRole(seats.admin.token, userId)).status).toStrictEqual(201);
851
871
  }
852
- assertEquals((await grantRole(seats.rival.token, bystander.id, roleId, rival.id))
853
- .status, 201);
872
+ expect((await grantRole(seats.rival.token, bystander.id, roleId, rival.id))
873
+ .status).toStrictEqual(201);
854
874
  const revoked = await call(seats.admin.token, "DELETE", `/api/organizations/${organization.id}/members/${leaver.id}/member`);
855
- assertEquals(revoked.status, 204);
856
- assertEquals((await heldBy(bystander.id)).map((role) => role.id), [roleId], "one person's membership ending took another's role");
875
+ expect(revoked.status).toStrictEqual(204);
876
+ expect((await heldBy(bystander.id)).map((role) => role.id), "one person's membership ending took another's role").toStrictEqual([roleId]);
857
877
  assert((await invitationIdsOf(seats.rival.token, rival.id)).includes(elsewhere.body.invitation.id), "a membership ending here withdrew an invitation elsewhere");
858
878
  for (const status of [204, 404]) {
859
879
  const taken = await call(seats.admin.token, "DELETE", `${rolesPath(bystander.id)}/${roleId}`);
860
- assertEquals(taken.status, status);
880
+ expect(taken.status).toStrictEqual(status);
861
881
  }
862
- assertEquals((await heldBy(bystander.id, seats.rival.token, rival.id)).map((role) => role.id), [roleId], "a revoke here took the role held in another organization");
882
+ expect((await heldBy(bystander.id, seats.rival.token, rival.id)).map((role) => role.id), "a revoke here took the role held in another organization").toStrictEqual([roleId]);
863
883
  });
864
884
  it("withdraws only the offer named from someone who is not a member", async () => {
865
885
  const invitee = await person();
@@ -870,27 +890,25 @@ export function runTenantContractTests(options) {
870
890
  offers.push(offered.body.membership.id);
871
891
  }
872
892
  const withdrawn = await call(seats.owner.token, "DELETE", `/api/organizations/${organization.id}/members/${invitee.id}/member`);
873
- assertEquals(withdrawn.status, 204);
874
- assertEquals(await offersOf(invitee.token), [offers[1]]);
893
+ expect(withdrawn.status).toStrictEqual(204);
894
+ expect(await offersOf(invitee.token)).toStrictEqual([offers[1]]);
875
895
  });
876
896
  it("ends nothing while the person keeps another accepted role there", async () => {
877
897
  const stayer = await seated();
878
898
  await seat(seats.owner.token, organization.id, stayer, "admin");
879
899
  const ownerOffer = await invite(seats.owner.token, organization.id, stayer.email, "owner");
880
900
  assert(ownerOffer.body.status === "membership", ownerOffer.body.status);
881
- assertEquals((await grantRole(seats.owner.token, stayer.id)).status, 201);
901
+ expect((await grantRole(seats.owner.token, stayer.id)).status).toStrictEqual(201);
882
902
  const revoked = await call(seats.owner.token, "DELETE", `/api/organizations/${organization.id}/members/${stayer.id}/member`);
883
- assertEquals(revoked.status, 204);
884
- assertEquals((await heldBy(stayer.id)).map((role) => role.id), [
885
- roleId,
886
- ]);
903
+ expect(revoked.status).toStrictEqual(204);
904
+ expect((await heldBy(stayer.id)).map((role) => role.id)).toStrictEqual([roleId]);
887
905
  assert(await holds(stayer.token, organization.id));
888
- assertEquals(await offersOf(stayer.token), [
906
+ expect(await offersOf(stayer.token)).toStrictEqual([
889
907
  ownerOffer.body.membership.id,
890
908
  ]);
891
909
  const withdrawn = await call(seats.owner.token, "DELETE", `/api/organizations/${organization.id}/members/${stayer.id}/owner`);
892
- assertEquals(withdrawn.status, 204);
893
- assertEquals(await offersOf(stayer.token), []);
910
+ expect(withdrawn.status).toStrictEqual(204);
911
+ expect(await offersOf(stayer.token)).toStrictEqual([]);
894
912
  assert(await holds(stayer.token, organization.id), "withdrawing a pending offer ended a role");
895
913
  });
896
914
  });
@@ -899,27 +917,27 @@ export function runTenantContractTests(options) {
899
917
  it("answers a new person's own metadata bucket, empty, and no cache keeps it", async () => {
900
918
  const caller = await person();
901
919
  const reply = await call(caller.token, "GET", "/api/account");
902
- assertEquals(reply.status, 200);
903
- assertEquals(reply.body, { userMetadata: {} });
904
- assertStringIncludes(reply.headers.get("cache-control") ?? "", "no-store");
920
+ expect(reply.status).toStrictEqual(200);
921
+ expect(reply.body).toStrictEqual({ userMetadata: {} });
922
+ expect(reply.headers.get("cache-control") ?? "").toContain("no-store");
905
923
  });
906
924
  it("merges a patch shallowly, deleting keys set to null and keeping keys it does not name", async () => {
907
925
  const caller = await person();
908
926
  const first = await call(caller.token, "PATCH", "/api/account", {
909
927
  userMetadata: { theme: "dark", locale: "en", nested: { a: 1 } },
910
928
  });
911
- assertEquals(first.status, 200);
912
- assertEquals(first.body, {
929
+ expect(first.status).toStrictEqual(200);
930
+ expect(first.body).toStrictEqual({
913
931
  userMetadata: { theme: "dark", locale: "en", nested: { a: 1 } },
914
932
  });
915
933
  const second = await call(caller.token, "PATCH", "/api/account", {
916
934
  userMetadata: { locale: null, nested: { b: 2 } },
917
935
  });
918
- assertEquals(second.body, {
936
+ expect(second.body).toStrictEqual({
919
937
  userMetadata: { theme: "dark", nested: { b: 2 } },
920
938
  });
921
939
  const read = await call(caller.token, "GET", "/api/account");
922
- assertEquals(read.body, second.body);
940
+ expect(read.body).toStrictEqual(second.body);
923
941
  });
924
942
  it("refuses anything but a userMetadata object", async () => {
925
943
  const caller = await person();
@@ -929,7 +947,7 @@ export function runTenantContractTests(options) {
929
947
  { userMetadata: {}, metadata: { role: "admin" } },
930
948
  ]) {
931
949
  const reply = await call(caller.token, "PATCH", "/api/account", body);
932
- assertEquals(reply.status, 400, JSON.stringify(body));
950
+ expect(reply.status, JSON.stringify(body)).toStrictEqual(400);
933
951
  }
934
952
  });
935
953
  it("keeps the bucket to eight levels of nesting", async () => {
@@ -938,11 +956,11 @@ export function runTenantContractTests(options) {
938
956
  const deepest = await call(caller.token, "PATCH", "/api/account", {
939
957
  userMetadata: nested(8),
940
958
  });
941
- assertEquals(deepest.status, 200);
959
+ expect(deepest.status).toStrictEqual(200);
942
960
  const tooDeep = await call(caller.token, "PATCH", "/api/account", {
943
961
  userMetadata: nested(9),
944
962
  });
945
- assertEquals(tooDeep.status, 400);
963
+ expect(tooDeep.status).toStrictEqual(400);
946
964
  });
947
965
  it("measures the metadata limit in UTF-8 bytes and preserves the bucket on refusal", async () => {
948
966
  const caller = await person();
@@ -950,9 +968,9 @@ export function runTenantContractTests(options) {
950
968
  const reply = await call(caller.token, "PATCH", "/api/account", {
951
969
  userMetadata: { notes },
952
970
  });
953
- assertEquals(reply.status, 400);
971
+ expect(reply.status).toStrictEqual(400);
954
972
  const read = await call(caller.token, "GET", "/api/account");
955
- assertEquals(read.body, { userMetadata: {} });
973
+ expect(read.body).toStrictEqual({ userMetadata: {} });
956
974
  });
957
975
  it("keeps the merged bucket to sixteen kilobytes, however small each patch is", async () => {
958
976
  const caller = await person();
@@ -960,23 +978,23 @@ export function runTenantContractTests(options) {
960
978
  const first = await call(caller.token, "PATCH", "/api/account", {
961
979
  userMetadata: { first: half },
962
980
  });
963
- assertEquals(first.status, 200);
981
+ expect(first.status).toStrictEqual(200);
964
982
  const second = await call(caller.token, "PATCH", "/api/account", {
965
983
  userMetadata: { second: half },
966
984
  });
967
- assertEquals(second.status, 400);
985
+ expect(second.status).toStrictEqual(400);
968
986
  const read = await call(caller.token, "GET", "/api/account");
969
- assertEquals(read.body, { userMetadata: { first: half } });
987
+ expect(read.body).toStrictEqual({ userMetadata: { first: half } });
970
988
  });
971
989
  it("refuses a caller with no bearer token", async () => {
972
990
  const reply = await call(null, "GET", "/api/account");
973
- assertEquals(reply.status, 401);
991
+ expect(reply.status).toStrictEqual(401);
974
992
  });
975
993
  });
976
994
  describe("the account sessions API", () => {
977
995
  async function sessionsOf(accessToken) {
978
996
  const reply = await call(accessToken, "GET", "/api/account/sessions");
979
- assertEquals(reply.status, 200);
997
+ expect(reply.status).toStrictEqual(200);
980
998
  return reply.body.sessions;
981
999
  }
982
1000
  const currentOf = (sessions) => sessions.filter((session) => session.current).map(({ id }) => id);
@@ -995,9 +1013,9 @@ export function runTenantContractTests(options) {
995
1013
  const first = await tenant.signIn(id);
996
1014
  const second = await tenant.signIn(id);
997
1015
  const listed = await sessionsOf(second.access_token);
998
- assertEquals(listed.length, 2);
999
- assertEquals(currentOf(listed), [listed[0].id]);
1000
- assertEquals(Object.keys(listed[0]).sort(), [
1016
+ expect(listed.length).toStrictEqual(2);
1017
+ expect(currentOf(listed)).toStrictEqual([listed[0].id]);
1018
+ expect(Object.keys(listed[0]).sort()).toStrictEqual([
1001
1019
  "createdAt",
1002
1020
  "current",
1003
1021
  "id",
@@ -1006,20 +1024,20 @@ export function runTenantContractTests(options) {
1006
1024
  "userAgent",
1007
1025
  ]);
1008
1026
  const fromFirst = await sessionsOf(first.access_token);
1009
- assertEquals(fromFirst.map((session) => session.id).sort(), listed.map((session) => session.id).sort());
1010
- assertEquals(currentOf(fromFirst), [listed[1].id]);
1027
+ expect(fromFirst.map((session) => session.id).sort()).toStrictEqual(listed.map((session) => session.id).sort());
1028
+ expect(currentOf(fromFirst)).toStrictEqual([listed[1].id]);
1011
1029
  });
1012
1030
  it("leaves an earlier sign-in's credential live when another browser signs in", async () => {
1013
1031
  const id = await tenant.addUser([], { email: newEmail() });
1014
1032
  const first = await tenant.signIn(id);
1015
1033
  const second = await tenant.signIn(id);
1016
- assertEquals((await introspect(first.access_token)).active, true);
1017
- assertEquals((await introspect(second.access_token)).active, true);
1034
+ expect((await introspect(first.access_token)).active).toStrictEqual(true);
1035
+ expect((await introspect(second.access_token)).active).toStrictEqual(true);
1018
1036
  const fromFirst = await sessionsOf(first.access_token);
1019
1037
  const fromSecond = await sessionsOf(second.access_token);
1020
- assertEquals(fromFirst.length, 2);
1021
- assertEquals(currentOf(fromFirst).length, 1);
1022
- assertFalse(currentOf(fromFirst)[0] === currentOf(fromSecond)[0], "two browsers' sign-ins shared one login session");
1038
+ expect(fromFirst.length).toStrictEqual(2);
1039
+ expect(currentOf(fromFirst).length).toStrictEqual(1);
1040
+ expect(currentOf(fromFirst)[0] === currentOf(fromSecond)[0], "two browsers' sign-ins shared one login session").toBeFalsy();
1023
1041
  });
1024
1042
  it("keeps a refreshed credential on the session it was issued from", async () => {
1025
1043
  const id = await tenant.addUser([], { email: newEmail() });
@@ -1027,17 +1045,17 @@ export function runTenantContractTests(options) {
1027
1045
  await tenant.signIn(id);
1028
1046
  const issuedFrom = currentOf(await sessionsOf(first.access_token));
1029
1047
  const response = await refresh(first.refresh_token);
1030
- assertEquals(response.status, 200);
1048
+ expect(response.status).toStrictEqual(200);
1031
1049
  const refreshed = await response.json();
1032
- assertEquals(currentOf(await sessionsOf(refreshed.access_token)), issuedFrom);
1050
+ expect(currentOf(await sessionsOf(refreshed.access_token))).toStrictEqual(issuedFrom);
1033
1051
  });
1034
1052
  it("refuses to end the caller's own session", async () => {
1035
1053
  const id = await tenant.addUser([], { email: newEmail() });
1036
1054
  const { access_token } = await tenant.signIn(id);
1037
1055
  const [own] = currentOf(await sessionsOf(access_token));
1038
1056
  const reply = await call(access_token, "DELETE", `/api/account/sessions/${own}`);
1039
- assertEquals(reply.status, 409);
1040
- assertEquals(reply.body.reason, "current_session");
1057
+ expect(reply.status).toStrictEqual(409);
1058
+ expect(reply.body.reason).toStrictEqual("current_session");
1041
1059
  });
1042
1060
  it("ends another of the caller's sessions and every token issued from it", async () => {
1043
1061
  const id = await tenant.addUser([], { email: newEmail() });
@@ -1045,23 +1063,29 @@ export function runTenantContractTests(options) {
1045
1063
  const current = await tenant.signIn(id);
1046
1064
  const [otherSession] = currentOf(await sessionsOf(other.access_token));
1047
1065
  const ended = await call(current.access_token, "DELETE", `/api/account/sessions/${otherSession}`);
1048
- assertEquals(ended.status, 204);
1049
- assertEquals((await introspect(other.access_token)).active, false);
1066
+ expect(ended.status).toStrictEqual(204);
1067
+ expect((await introspect(other.access_token)).active).toStrictEqual(false);
1050
1068
  const refused = await refresh(other.refresh_token);
1051
1069
  await refused.body?.cancel();
1052
- assertEquals(refused.status, 400);
1070
+ expect(refused.status).toStrictEqual(400);
1053
1071
  const remaining = await sessionsOf(current.access_token);
1054
- assertEquals(remaining.map((session) => session.current), [true]);
1072
+ expect(remaining.map((session) => session.current)).toStrictEqual([
1073
+ true,
1074
+ ]);
1055
1075
  });
1056
1076
  it("answers another person's session, an unknown id and a malformed id with 404", async () => {
1057
1077
  const caller = await person();
1058
1078
  const stranger = await person();
1059
1079
  const [theirs] = currentOf(await sessionsOf(stranger.token));
1060
- for (const sessionId of [theirs, crypto.randomUUID(), "not-a-session"]) {
1080
+ for (const sessionId of [
1081
+ theirs,
1082
+ crypto.randomUUID(),
1083
+ "not-a-session",
1084
+ ]) {
1061
1085
  const reply = await call(caller.token, "DELETE", `/api/account/sessions/${sessionId}`);
1062
- assertEquals(reply.status, 404, sessionId);
1086
+ expect(reply.status, sessionId).toStrictEqual(404);
1063
1087
  }
1064
- assertEquals((await sessionsOf(stranger.token)).length, 1);
1088
+ expect((await sessionsOf(stranger.token)).length).toStrictEqual(1);
1065
1089
  });
1066
1090
  it("ends every other session and keeps the caller's", async () => {
1067
1091
  const id = await tenant.addUser([], { email: newEmail() });
@@ -1069,11 +1093,13 @@ export function runTenantContractTests(options) {
1069
1093
  await tenant.signIn(id);
1070
1094
  const kept = await tenant.signIn(id);
1071
1095
  const reply = await call(kept.access_token, "POST", "/api/account/sessions/revoke-others");
1072
- assertEquals(reply.status, 200);
1073
- assertEquals(reply.body, { revoked: 2 });
1096
+ expect(reply.status).toStrictEqual(200);
1097
+ expect(reply.body).toStrictEqual({ revoked: 2 });
1074
1098
  const remaining = await sessionsOf(kept.access_token);
1075
- assertEquals(remaining.map((session) => session.current), [true]);
1076
- assertEquals((await introspect(first.access_token)).active, false);
1099
+ expect(remaining.map((session) => session.current)).toStrictEqual([
1100
+ true,
1101
+ ]);
1102
+ expect((await introspect(first.access_token)).active).toStrictEqual(false);
1077
1103
  });
1078
1104
  it("ends a login session when a token issued from it is revoked", async () => {
1079
1105
  const id = await tenant.addUser([], { email: newEmail() });
@@ -1089,33 +1115,35 @@ export function runTenantContractTests(options) {
1089
1115
  }),
1090
1116
  });
1091
1117
  await response.body?.cancel();
1092
- assertEquals(response.status, 200);
1093
- assertEquals((await introspect(revoked.access_token)).active, false);
1118
+ expect(response.status).toStrictEqual(200);
1119
+ expect((await introspect(revoked.access_token)).active).toStrictEqual(false);
1094
1120
  const remaining = await sessionsOf(kept.access_token);
1095
- assertEquals(remaining.map((session) => session.current), [true]);
1121
+ expect(remaining.map((session) => session.current)).toStrictEqual([
1122
+ true,
1123
+ ]);
1096
1124
  });
1097
1125
  it("revokes a login session's earlier credential when the same browser signs in again", async () => {
1098
1126
  const id = await tenant.addUser([], { email: newEmail() });
1099
1127
  const first = await tenant.signIn(id);
1100
1128
  const again = await tenant.signIn(id, undefined, { sameBrowser: true });
1101
- assertEquals((await introspect(first.access_token)).active, false);
1129
+ expect((await introspect(first.access_token)).active).toStrictEqual(false);
1102
1130
  const refused = await refresh(first.refresh_token);
1103
1131
  await refused.body?.cancel();
1104
- assertEquals(refused.status, 400);
1105
- assertEquals((await introspect(again.access_token)).active, true);
1132
+ expect(refused.status).toStrictEqual(400);
1133
+ expect((await introspect(again.access_token)).active).toStrictEqual(true);
1106
1134
  const listed = await sessionsOf(again.access_token);
1107
- assertEquals(listed.map((session) => session.current), [true]);
1135
+ expect(listed.map((session) => session.current)).toStrictEqual([true]);
1108
1136
  });
1109
1137
  it("refuses a body that names anything", async () => {
1110
1138
  const caller = await person();
1111
1139
  const reply = await call(caller.token, "POST", "/api/account/sessions/revoke-others", { userId: caller.id });
1112
- assertEquals(reply.status, 400);
1140
+ expect(reply.status).toStrictEqual(400);
1113
1141
  });
1114
1142
  });
1115
1143
  describe("the linked accounts API", () => {
1116
1144
  async function linkedOf(accessToken) {
1117
1145
  const reply = await call(accessToken, "GET", "/api/account/linked-accounts");
1118
- assertEquals(reply.status, 200);
1146
+ expect(reply.status).toStrictEqual(200);
1119
1147
  return reply.body;
1120
1148
  }
1121
1149
  it("lists a person's linked accounts and whether they have a password", async () => {
@@ -1126,13 +1154,13 @@ export function runTenantContractTests(options) {
1126
1154
  email,
1127
1155
  });
1128
1156
  const body = await linkedOf(caller.token);
1129
- assertEquals(body.hasPassword, true);
1157
+ expect(body.hasPassword).toStrictEqual(true);
1130
1158
  assert(Array.isArray(body.connectable));
1131
- assertEquals(body.identities.length, 1);
1159
+ expect(body.identities.length).toStrictEqual(1);
1132
1160
  const [identity] = body.identities;
1133
- assertEquals(identity.id, linked);
1134
- assertEquals(identity.provider, "github");
1135
- assertEquals(identity.email, email);
1161
+ expect(identity.id).toStrictEqual(linked);
1162
+ expect(identity.provider).toStrictEqual("github");
1163
+ expect(identity.email).toStrictEqual(email);
1136
1164
  assert(identity.displayName, "a linked account names its provider");
1137
1165
  assert(identity.createdAt, "a linked account says when it was linked");
1138
1166
  });
@@ -1143,8 +1171,8 @@ export function runTenantContractTests(options) {
1143
1171
  email: null,
1144
1172
  });
1145
1173
  const reply = await call(caller.token, "DELETE", `/api/account/linked-accounts/${linked}`);
1146
- assertEquals(reply.status, 204);
1147
- assertEquals((await linkedOf(caller.token)).identities, []);
1174
+ expect(reply.status).toStrictEqual(204);
1175
+ expect((await linkedOf(caller.token)).identities).toStrictEqual([]);
1148
1176
  });
1149
1177
  it("refuses to disconnect a person's last way to sign in", async () => {
1150
1178
  const id = await tenant.addUser([], {
@@ -1156,11 +1184,11 @@ export function runTenantContractTests(options) {
1156
1184
  email: null,
1157
1185
  });
1158
1186
  const { access_token } = await tenant.signIn(id);
1159
- assertEquals((await linkedOf(access_token)).hasPassword, false);
1187
+ expect((await linkedOf(access_token)).hasPassword).toStrictEqual(false);
1160
1188
  const reply = await call(access_token, "DELETE", `/api/account/linked-accounts/${linked}`);
1161
- assertEquals(reply.status, 409);
1162
- assertEquals(reply.body.reason, "last_method");
1163
- assertEquals((await linkedOf(access_token)).identities.map((entry) => entry.id), [linked]);
1189
+ expect(reply.status).toStrictEqual(409);
1190
+ expect(reply.body.reason).toStrictEqual("last_method");
1191
+ expect((await linkedOf(access_token)).identities.map((entry) => entry.id)).toStrictEqual([linked]);
1164
1192
  });
1165
1193
  it("answers another person's linked account, an unknown id and a malformed id with 404", async () => {
1166
1194
  const caller = await person();
@@ -1171,9 +1199,9 @@ export function runTenantContractTests(options) {
1171
1199
  });
1172
1200
  for (const identityId of [theirs, crypto.randomUUID(), "not-an-id"]) {
1173
1201
  const reply = await call(caller.token, "DELETE", `/api/account/linked-accounts/${identityId}`);
1174
- assertEquals(reply.status, 404, identityId);
1202
+ expect(reply.status, identityId).toStrictEqual(404);
1175
1203
  }
1176
- assertEquals((await linkedOf(stranger.token)).identities.map((entry) => entry.id), [theirs]);
1204
+ expect((await linkedOf(stranger.token)).identities.map((entry) => entry.id)).toStrictEqual([theirs]);
1177
1205
  });
1178
1206
  });
1179
1207
  describe("client credentials and the resource-grants API", () => {
@@ -1200,8 +1228,8 @@ export function runTenantContractTests(options) {
1200
1228
  : { authorization: encodeBasicAuth(client.id, client.secret) },
1201
1229
  body: new URLSearchParams({
1202
1230
  grant_type: "client_credentials",
1203
- ...client.secret === undefined ? { client_id: client.id } : {},
1204
- ...scope === undefined ? {} : { scope },
1231
+ ...(client.secret === undefined ? { client_id: client.id } : {}),
1232
+ ...(scope === undefined ? {} : { scope }),
1205
1233
  }),
1206
1234
  });
1207
1235
  const text = await response.text();
@@ -1213,7 +1241,7 @@ export function runTenantContractTests(options) {
1213
1241
  }
1214
1242
  async function issuedTo(client, scope) {
1215
1243
  const issued = await machineToken(client, scope);
1216
- assertEquals(issued.status, 200, JSON.stringify(issued.body));
1244
+ expect(issued.status, JSON.stringify(issued.body)).toStrictEqual(200);
1217
1245
  assert(issued.body.access_token);
1218
1246
  return issued.body.access_token;
1219
1247
  }
@@ -1223,7 +1251,7 @@ export function runTenantContractTests(options) {
1223
1251
  headers: { authorization: encodeBasicAuth(client.id, client.secret) },
1224
1252
  body: new URLSearchParams({ token: accessToken }),
1225
1253
  });
1226
- assertEquals(response.status, 200);
1254
+ expect(response.status).toStrictEqual(200);
1227
1255
  return await response.json();
1228
1256
  }
1229
1257
  function grantsOn(accessToken, query) {
@@ -1252,22 +1280,22 @@ export function runTenantContractTests(options) {
1252
1280
  });
1253
1281
  it("issues a machine token with no refresh token, naming no person and no organization", async () => {
1254
1282
  const issued = await machineToken(reader, ORGANIZATIONS_READ);
1255
- assertEquals(issued.status, 200, JSON.stringify(issued.body));
1256
- assertEquals(issued.body.token_type, "Bearer");
1283
+ expect(issued.status, JSON.stringify(issued.body)).toStrictEqual(200);
1284
+ expect(issued.body.token_type).toStrictEqual("Bearer");
1257
1285
  assert(issued.body.access_token);
1258
- assertFalse("refresh_token" in issued.body, "a machine token has no refresh token");
1286
+ expect("refresh_token" in issued.body, "a machine token has no refresh token").toBeFalsy();
1259
1287
  const claims = await introspectAs(reader, issued.body.access_token);
1260
- assertEquals(claims.active, true);
1261
- assertEquals(claims.client_id, reader.id);
1262
- assertEquals(claims.scope, ORGANIZATIONS_READ);
1263
- assertFalse(claims.sub, "a machine token names no person");
1264
- assertFalse(claims.username, "a machine token names no person");
1265
- assertFalse(claims.org_id, "a machine token belongs to no organization");
1266
- assertEquals(sorted(claims.permissions), []);
1288
+ expect(claims.active).toStrictEqual(true);
1289
+ expect(claims.client_id).toStrictEqual(reader.id);
1290
+ expect(claims.scope).toStrictEqual(ORGANIZATIONS_READ);
1291
+ expect(claims.sub, "a machine token names no person").toBeFalsy();
1292
+ expect(claims.username, "a machine token names no person").toBeFalsy();
1293
+ expect(claims.org_id, "a machine token belongs to no organization").toBeFalsy();
1294
+ expect(sorted(claims.permissions)).toStrictEqual([]);
1267
1295
  });
1268
1296
  it("answers another application introspecting a machine token that it is inactive", async () => {
1269
1297
  const claims = await introspectAs(unassigned, readerToken);
1270
- assertEquals(claims, { active: false });
1298
+ expect(claims).toStrictEqual({ active: false });
1271
1299
  });
1272
1300
  it("refuses the grant to a public application, which has no secret to authenticate with", async () => {
1273
1301
  const open = await tenant.addMachineClient({
@@ -1275,23 +1303,23 @@ export function runTenantContractTests(options) {
1275
1303
  permissions: [],
1276
1304
  confidential: false,
1277
1305
  });
1278
- assertFalse(open.secret, "a public application is issued no secret");
1306
+ expect(open.secret, "a public application is issued no secret").toBeFalsy();
1279
1307
  const refused = await machineToken(open, ORGANIZATIONS_READ);
1280
- assertEquals(refused.status, 401, JSON.stringify(refused.body));
1281
- assertEquals(refused.body.error, "invalid_client");
1282
- assertFalse(refused.body.access_token);
1308
+ expect(refused.status, JSON.stringify(refused.body)).toStrictEqual(401);
1309
+ expect(refused.body.error).toStrictEqual("invalid_client");
1310
+ expect(refused.body.access_token).toBeFalsy();
1283
1311
  });
1284
1312
  it("refuses the grant to an application not registered for it, and refuses a machine token an OIDC scope or one outside its allowlist", async () => {
1285
1313
  const interactive = await machineToken(tenant.client, ORGANIZATIONS_READ);
1286
- assertEquals(interactive.status, 401);
1287
- assertEquals(interactive.body.error, "unauthorized_client");
1314
+ expect(interactive.status).toStrictEqual(401);
1315
+ expect(interactive.body.error).toStrictEqual("unauthorized_client");
1288
1316
  for (const scope of [
1289
1317
  "openid",
1290
1318
  `${ORGANIZATIONS_READ} identity:organizations:write`,
1291
1319
  ]) {
1292
1320
  const refused = await machineToken(reader, scope);
1293
- assertEquals(refused.status, 400, scope);
1294
- assertEquals(refused.body.error, "invalid_scope", scope);
1321
+ expect(refused.status, scope).toStrictEqual(400);
1322
+ expect(refused.body.error, scope).toStrictEqual("invalid_scope");
1295
1323
  }
1296
1324
  });
1297
1325
  it("accepts organization_id on the refresh_token grant alone", async () => {
@@ -1305,45 +1333,45 @@ export function runTenantContractTests(options) {
1305
1333
  }),
1306
1334
  });
1307
1335
  const body = await response.json();
1308
- assertEquals(response.status, 400, JSON.stringify(body));
1309
- assertEquals(body.error, "invalid_request");
1336
+ expect(response.status, JSON.stringify(body)).toStrictEqual(400);
1337
+ expect(body.error).toStrictEqual("invalid_request");
1310
1338
  });
1311
1339
  it("refuses a machine token wherever the tenant answers for a person", async () => {
1312
1340
  const checked = await ask(readerToken, "/api/check", {
1313
1341
  permissions: ["contract:tenant"],
1314
1342
  });
1315
- assertEquals(checked.status, 403);
1316
- assertEquals(checked.body.reason, "machine_token");
1343
+ expect(checked.status).toStrictEqual(403);
1344
+ expect(checked.body.reason).toStrictEqual("machine_token");
1317
1345
  const batch = await ask(readerToken, "/api/check/batch", {
1318
1346
  permissions: ["contract:read"],
1319
1347
  resource: { type: "contract_document", ids: ["direct"] },
1320
1348
  });
1321
- assertEquals(batch.status, 403);
1322
- assertEquals(batch.body.reason, "machine_token");
1349
+ expect(batch.status).toStrictEqual(403);
1350
+ expect(batch.body.reason).toStrictEqual("machine_token");
1323
1351
  for (const path of ["/api/memberships", "/api/account"]) {
1324
1352
  const reply = await call(readerToken, "GET", path);
1325
- assertEquals(reply.status, 403, path);
1326
- assertEquals(reply.body.reason, "machine_token", path);
1353
+ expect(reply.status, path).toStrictEqual(403);
1354
+ expect(reply.body.reason, path).toStrictEqual("machine_token");
1327
1355
  }
1328
1356
  const organizations = await call(readerToken, "GET", "/api/organizations");
1329
- assertEquals(organizations.status, 403);
1357
+ expect(organizations.status).toStrictEqual(403);
1330
1358
  const userinfo = await send(userinfoEndpoint, {
1331
1359
  headers: { authorization: `Bearer ${readerToken}` },
1332
1360
  });
1333
1361
  await userinfo.body?.cancel();
1334
- assertEquals(userinfo.status, 401);
1362
+ expect(userinfo.status).toStrictEqual(401);
1335
1363
  });
1336
1364
  it("lists who holds a grant on a resource, roles named, and an empty list where nobody does", async () => {
1337
1365
  const direct = await grantsOn(readerToken, DIRECT);
1338
- assertEquals(direct.status, 200, JSON.stringify(direct.body));
1339
- assertStringIncludes(direct.headers.get("cache-control") ?? "", "no-store");
1340
- assertEquals(direct.body.resource, {
1366
+ expect(direct.status, JSON.stringify(direct.body)).toStrictEqual(200);
1367
+ expect(direct.headers.get("cache-control") ?? "").toContain("no-store");
1368
+ expect(direct.body.resource).toStrictEqual({
1341
1369
  type: "contract_document",
1342
1370
  id: "direct",
1343
1371
  });
1344
- assertEquals(direct.body.grants.length, 1);
1372
+ expect(direct.body.grants.length).toStrictEqual(1);
1345
1373
  const [held] = direct.body.grants;
1346
- assertEquals(Object.keys(held).sort(), [
1374
+ expect(Object.keys(held).sort()).toStrictEqual([
1347
1375
  "builtInRole",
1348
1376
  "id",
1349
1377
  "roleId",
@@ -1355,27 +1383,30 @@ export function runTenantContractTests(options) {
1355
1383
  "subjectRole",
1356
1384
  "subjectType",
1357
1385
  ]);
1358
- assertEquals(held.subjectType, "user");
1359
- assertEquals(held.subjectId, people.outsider);
1360
- assertEquals(held.subjectRole, null);
1361
- assertEquals(held.subjectActive, true);
1386
+ expect(held.subjectType).toStrictEqual("user");
1387
+ expect(held.subjectId).toStrictEqual(people.outsider);
1388
+ expect(held.subjectRole).toStrictEqual(null);
1389
+ expect(held.subjectActive).toStrictEqual(true);
1362
1390
  assert(held.subjectName, "a grant names its holder");
1363
1391
  assert(held.id, "a grant has an id");
1364
1392
  assert(held.roleId, "a grant names its role");
1365
1393
  assert(held.roleSlug, "a grant names its role's slug");
1366
1394
  assert(held.roleName, "a grant names its role's name");
1367
- assertEquals(held.builtInRole, null);
1395
+ expect(held.builtInRole).toStrictEqual(null);
1368
1396
  const shared = await grantsOn(readerToken, "type=contract_document&id=shared");
1369
- assertEquals(shared.status, 200);
1370
- assertEquals(shared.body.grants.map((grant) => [grant.subjectType, grant.subjectId]), [["organization", organizations.home]]);
1397
+ expect(shared.status).toStrictEqual(200);
1398
+ expect(shared.body.grants.map((grant) => [
1399
+ grant.subjectType,
1400
+ grant.subjectId,
1401
+ ])).toStrictEqual([["organization", organizations.home]]);
1371
1402
  const nobody = await grantsOn(readerToken, `type=contract_document&id=nobody-${crypto.randomUUID()}`);
1372
- assertEquals(nobody.status, 200);
1373
- assertEquals(nobody.body.grants, []);
1403
+ expect(nobody.status).toStrictEqual(200);
1404
+ expect(nobody.body.grants).toStrictEqual([]);
1374
1405
  });
1375
1406
  it("lists the grants on one resource in role-name order", async () => {
1376
1407
  const ranked = await grantsOn(readerToken, RANKED);
1377
- assertEquals(ranked.status, 200, JSON.stringify(ranked.body));
1378
- assertEquals(ranked.body.grants.map((grant) => grant.subjectId).sort(), [
1408
+ expect(ranked.status, JSON.stringify(ranked.body)).toStrictEqual(200);
1409
+ expect(ranked.body.grants.map((grant) => grant.subjectId).sort()).toStrictEqual([
1379
1410
  people.member,
1380
1411
  people.outsider,
1381
1412
  people.leaver,
@@ -1383,47 +1414,47 @@ export function runTenantContractTests(options) {
1383
1414
  organizations.home,
1384
1415
  ].sort());
1385
1416
  const names = ranked.body.grants.map((grant) => grant.roleName);
1386
- assertEquals(new Set(names).size, names.length, "each grant confers a role of its own");
1387
- assertEquals(names, [...names].sort((a, b) => a.localeCompare(b)));
1417
+ expect(new Set(names).size, "each grant confers a role of its own").toStrictEqual(names.length);
1418
+ expect(names).toStrictEqual([...names].sort((a, b) => a.localeCompare(b)));
1388
1419
  });
1389
1420
  it("hides the listing from a machine token without the permission or the scope, and refuses a person's token and no token", async () => {
1390
1421
  const withoutPermission = await issuedTo(unassigned, ORGANIZATIONS_READ);
1391
- assertEquals((await grantsOn(withoutPermission, DIRECT)).status, 404);
1422
+ expect((await grantsOn(withoutPermission, DIRECT)).status).toStrictEqual(404);
1392
1423
  const withoutScope = await issuedTo(reader);
1393
- assertEquals((await grantsOn(withoutScope, DIRECT)).status, 404);
1424
+ expect((await grantsOn(withoutScope, DIRECT)).status).toStrictEqual(404);
1394
1425
  const { access_token } = await tenant.signIn(people.member);
1395
1426
  const person = await call(access_token, "GET", `/api/resource-grants?${DIRECT}`);
1396
- assertEquals(person.status, 403);
1397
- assertEquals(person.body.reason, "user_token");
1398
- assertEquals((await grantsOn(null, DIRECT)).status, 401);
1427
+ expect(person.status).toStrictEqual(403);
1428
+ expect(person.body.reason).toStrictEqual("user_token");
1429
+ expect((await grantsOn(null, DIRECT)).status).toStrictEqual(401);
1399
1430
  });
1400
1431
  function refusedFields(reply) {
1401
- assertEquals(reply.status, 400, JSON.stringify(reply.body));
1432
+ expect(reply.status, JSON.stringify(reply.body)).toStrictEqual(400);
1402
1433
  const { fieldErrors } = reply.body;
1403
1434
  return Object.keys(fieldErrors ?? {});
1404
1435
  }
1405
1436
  it("refuses a resource type the tenant has not registered, and a resource missing its id", async () => {
1406
1437
  const unregistered = await grantsOn(readerToken, "type=contract_unregistered&id=direct");
1407
- assertEquals(refusedFields(unregistered), ["type"]);
1438
+ expect(refusedFields(unregistered)).toStrictEqual(["type"]);
1408
1439
  const missing = await grantsOn(readerToken, "type=contract_document");
1409
- assertEquals(refusedFields(missing), ["resource"]);
1440
+ expect(refusedFields(missing)).toStrictEqual(["resource"]);
1410
1441
  });
1411
1442
  it("bounds the type to 64 characters and the id to 255, refusing ASCII control characters and no other", async () => {
1412
1443
  const long = await grantsOn(readerToken, `type=${"t".repeat(65)}&id=direct`);
1413
- assertEquals(refusedFields(long), ["resource"]);
1444
+ expect(refusedFields(long)).toStrictEqual(["resource"]);
1414
1445
  const longest = await grantsOn(readerToken, `type=${"t".repeat(64)}&id=direct`);
1415
- assertEquals(refusedFields(longest), ["type"]);
1446
+ expect(refusedFields(longest)).toStrictEqual(["type"]);
1416
1447
  const query = (id) => `type=contract_document&id=${encodeURIComponent(id)}`;
1417
1448
  const wide = await grantsOn(readerToken, query("i".repeat(256)));
1418
- assertEquals(refusedFields(wide), ["resource"]);
1449
+ expect(refusedFields(wide)).toStrictEqual(["resource"]);
1419
1450
  const widest = await grantsOn(readerToken, query("i".repeat(255)));
1420
- assertEquals(widest.status, 200, JSON.stringify(widest.body));
1421
- assertEquals(widest.body.grants, []);
1451
+ expect(widest.status, JSON.stringify(widest.body)).toStrictEqual(200);
1452
+ expect(widest.body.grants).toStrictEqual([]);
1422
1453
  const control = await grantsOn(readerToken, query("tab\there"));
1423
- assertEquals(refusedFields(control), ["resource"]);
1454
+ expect(refusedFields(control)).toStrictEqual(["resource"]);
1424
1455
  const unicode = await grantsOn(readerToken, query("next\u0085line"));
1425
- assertEquals(unicode.status, 200, JSON.stringify(unicode.body));
1426
- assertEquals(unicode.body.grants, []);
1456
+ expect(unicode.status, JSON.stringify(unicode.body)).toStrictEqual(200);
1457
+ expect(unicode.body.grants).toStrictEqual([]);
1427
1458
  });
1428
1459
  });
1429
1460
  });