@things-factory/auth-base 10.1.278 → 10.1.280

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 (284) hide show
  1. package/package.json +24 -7
  2. package/CHANGELOG.md +0 -55
  3. package/dist-client/actions/auth.js.map +0 -1
  4. package/dist-client/apply-domain-theme.js.map +0 -1
  5. package/dist-client/auth.js.map +0 -1
  6. package/dist-client/bootstrap.js.map +0 -1
  7. package/dist-client/directive/privileged.js.map +0 -1
  8. package/dist-client/domain-required.js.map +0 -1
  9. package/dist-client/index.js.map +0 -1
  10. package/dist-client/profiled.js.map +0 -1
  11. package/dist-client/reducers/auth.js.map +0 -1
  12. package/dist-client/tsconfig.tsbuildinfo +0 -1
  13. package/dist-client/verify-webauthn.js.map +0 -1
  14. package/dist-server/ai-entry/access-assistant.js.map +0 -1
  15. package/dist-server/ai-entry/access-entry-targets.js.map +0 -1
  16. package/dist-server/ai-entry/entry-target-shape.js.map +0 -1
  17. package/dist-server/constants/error-code.js.map +0 -1
  18. package/dist-server/constants/error-message.js.map +0 -1
  19. package/dist-server/constants/max-age.js.map +0 -1
  20. package/dist-server/controllers/access-request.js.map +0 -1
  21. package/dist-server/controllers/account-mail.js.map +0 -1
  22. package/dist-server/controllers/auth.js.map +0 -1
  23. package/dist-server/controllers/change-pwd.js.map +0 -1
  24. package/dist-server/controllers/confirm-identity.js.map +0 -1
  25. package/dist-server/controllers/delete-user.js.map +0 -1
  26. package/dist-server/controllers/passkey.js.map +0 -1
  27. package/dist-server/controllers/password-setup.js.map +0 -1
  28. package/dist-server/controllers/profile.js.map +0 -1
  29. package/dist-server/controllers/reset-password.js.map +0 -1
  30. package/dist-server/controllers/signin.js.map +0 -1
  31. package/dist-server/controllers/signup.js.map +0 -1
  32. package/dist-server/controllers/unlock-user.js.map +0 -1
  33. package/dist-server/controllers/utils/find-verification-token.js.map +0 -1
  34. package/dist-server/controllers/utils/make-verification-token.js.map +0 -1
  35. package/dist-server/controllers/utils/password-rule.js.map +0 -1
  36. package/dist-server/controllers/utils/save-verification-token.js.map +0 -1
  37. package/dist-server/controllers/verification.js.map +0 -1
  38. package/dist-server/errors/auth-error.js.map +0 -1
  39. package/dist-server/errors/index.js.map +0 -1
  40. package/dist-server/errors/user-domain-not-match-error.js.map +0 -1
  41. package/dist-server/index.js.map +0 -1
  42. package/dist-server/middlewares/audit-scope-middleware.js.map +0 -1
  43. package/dist-server/middlewares/authenticate-401-middleware.js.map +0 -1
  44. package/dist-server/middlewares/bypass-signin-middleware.js.map +0 -1
  45. package/dist-server/middlewares/domain-authenticate-middleware.js.map +0 -1
  46. package/dist-server/middlewares/graphql-authenticate-middleware.js.map +0 -1
  47. package/dist-server/middlewares/index.js.map +0 -1
  48. package/dist-server/middlewares/jwt-authenticate-middleware.js.map +0 -1
  49. package/dist-server/middlewares/signin-middleware.js.map +0 -1
  50. package/dist-server/middlewares/verify-recaptcha-middleware.js.map +0 -1
  51. package/dist-server/migrations/1548206416130-SeedUser.js.map +0 -1
  52. package/dist-server/migrations/1566805283882-SeedPrivilege.js.map +0 -1
  53. package/dist-server/migrations/1745000000000-MigrateDomainOwnersToTable.js.map +0 -1
  54. package/dist-server/migrations/index.js.map +0 -1
  55. package/dist-server/role-tasks.js.map +0 -1
  56. package/dist-server/router/auth-checkin-router.js.map +0 -1
  57. package/dist-server/router/auth-private-process-router.js.map +0 -1
  58. package/dist-server/router/auth-public-process-router.js.map +0 -1
  59. package/dist-server/router/auth-signin-router.js.map +0 -1
  60. package/dist-server/router/auth-signup-router.js.map +0 -1
  61. package/dist-server/router/index.js.map +0 -1
  62. package/dist-server/router/oauth2/index.js.map +0 -1
  63. package/dist-server/router/oauth2/oauth2-authorize-router.js.map +0 -1
  64. package/dist-server/router/oauth2/oauth2-router.js.map +0 -1
  65. package/dist-server/router/oauth2/oauth2-server.js.map +0 -1
  66. package/dist-server/router/oauth2/passport-oauth2-client-password.js.map +0 -1
  67. package/dist-server/router/oauth2/passport-refresh-token.js.map +0 -1
  68. package/dist-server/router/path-base-domain-router.js.map +0 -1
  69. package/dist-server/router/site-root-router.js.map +0 -1
  70. package/dist-server/router/webauthn-router.js.map +0 -1
  71. package/dist-server/routes.js.map +0 -1
  72. package/dist-server/service/access-request/access-request-mutation.js.map +0 -1
  73. package/dist-server/service/access-request/access-request-query.js.map +0 -1
  74. package/dist-server/service/access-request/access-request-types.js.map +0 -1
  75. package/dist-server/service/access-request/access-request.js.map +0 -1
  76. package/dist-server/service/access-request/index.js.map +0 -1
  77. package/dist-server/service/app-binding/app-binding-mutation.js.map +0 -1
  78. package/dist-server/service/app-binding/app-binding-query.js.map +0 -1
  79. package/dist-server/service/app-binding/app-binding-types.js.map +0 -1
  80. package/dist-server/service/app-binding/app-binding.js.map +0 -1
  81. package/dist-server/service/app-binding/index.js.map +0 -1
  82. package/dist-server/service/appliance/appliance-mutation.js.map +0 -1
  83. package/dist-server/service/appliance/appliance-query.js.map +0 -1
  84. package/dist-server/service/appliance/appliance-types.js.map +0 -1
  85. package/dist-server/service/appliance/appliance.js.map +0 -1
  86. package/dist-server/service/appliance/index.js.map +0 -1
  87. package/dist-server/service/application/application-mutation.js.map +0 -1
  88. package/dist-server/service/application/application-query.js.map +0 -1
  89. package/dist-server/service/application/application-types.js.map +0 -1
  90. package/dist-server/service/application/application.js.map +0 -1
  91. package/dist-server/service/application/index.js.map +0 -1
  92. package/dist-server/service/audit/audit-action.js.map +0 -1
  93. package/dist-server/service/audit/audit-kinds.js.map +0 -1
  94. package/dist-server/service/audit/audit-query.js.map +0 -1
  95. package/dist-server/service/audit/audit-recorder.js.map +0 -1
  96. package/dist-server/service/audit/audit-request.js.map +0 -1
  97. package/dist-server/service/audit/audit-retention.js.map +0 -1
  98. package/dist-server/service/audit/audit-subscriber.js.map +0 -1
  99. package/dist-server/service/audit/audit-target.js.map +0 -1
  100. package/dist-server/service/audit/audit-types.js.map +0 -1
  101. package/dist-server/service/audit/audit-work.js.map +0 -1
  102. package/dist-server/service/audit/index.js.map +0 -1
  103. package/dist-server/service/auth-provider/auth-provider-mutation.js.map +0 -1
  104. package/dist-server/service/auth-provider/auth-provider-parameter-spec.js.map +0 -1
  105. package/dist-server/service/auth-provider/auth-provider-query.js.map +0 -1
  106. package/dist-server/service/auth-provider/auth-provider-type.js.map +0 -1
  107. package/dist-server/service/auth-provider/auth-provider.js.map +0 -1
  108. package/dist-server/service/auth-provider/index.js.map +0 -1
  109. package/dist-server/service/domain-generator/domain-generator-mutation.js.map +0 -1
  110. package/dist-server/service/domain-generator/domain-generator-types.js.map +0 -1
  111. package/dist-server/service/domain-generator/index.js.map +0 -1
  112. package/dist-server/service/domain-link/domain-link-mutation.js.map +0 -1
  113. package/dist-server/service/domain-link/domain-link-query.js.map +0 -1
  114. package/dist-server/service/domain-link/domain-link-types.js.map +0 -1
  115. package/dist-server/service/domain-link/domain-link.js.map +0 -1
  116. package/dist-server/service/domain-link/index.js.map +0 -1
  117. package/dist-server/service/domain-owner/domain-owner-mutation.js.map +0 -1
  118. package/dist-server/service/domain-owner/domain-owner-query.js.map +0 -1
  119. package/dist-server/service/domain-owner/domain-owner.js.map +0 -1
  120. package/dist-server/service/domain-owner/index.js.map +0 -1
  121. package/dist-server/service/granted-role/granted-role-mutation.js.map +0 -1
  122. package/dist-server/service/granted-role/granted-role-query.js.map +0 -1
  123. package/dist-server/service/granted-role/granted-role.js.map +0 -1
  124. package/dist-server/service/granted-role/index.js.map +0 -1
  125. package/dist-server/service/identity-confirmation/identity-confirmation.js.map +0 -1
  126. package/dist-server/service/index.js.map +0 -1
  127. package/dist-server/service/open-fields.js.map +0 -1
  128. package/dist-server/service/partner/index.js.map +0 -1
  129. package/dist-server/service/partner/partner-mutation.js.map +0 -1
  130. package/dist-server/service/partner/partner-query.js.map +0 -1
  131. package/dist-server/service/partner/partner-types.js.map +0 -1
  132. package/dist-server/service/partner/partner.js.map +0 -1
  133. package/dist-server/service/password-history/index.js.map +0 -1
  134. package/dist-server/service/password-history/password-history.js.map +0 -1
  135. package/dist-server/service/privilege/index.js.map +0 -1
  136. package/dist-server/service/privilege/open-field-rule.js.map +0 -1
  137. package/dist-server/service/privilege/privilege-axis.js.map +0 -1
  138. package/dist-server/service/privilege/privilege-deprecation.js.map +0 -1
  139. package/dist-server/service/privilege/privilege-directive.js.map +0 -1
  140. package/dist-server/service/privilege/privilege-mutation.js.map +0 -1
  141. package/dist-server/service/privilege/privilege-query.js.map +0 -1
  142. package/dist-server/service/privilege/privilege-rejection.js.map +0 -1
  143. package/dist-server/service/privilege/privilege-sync.js.map +0 -1
  144. package/dist-server/service/privilege/privilege-types.js.map +0 -1
  145. package/dist-server/service/privilege/privilege.js.map +0 -1
  146. package/dist-server/service/role/index.js.map +0 -1
  147. package/dist-server/service/role/role-mutation.js.map +0 -1
  148. package/dist-server/service/role/role-privilege-query.js.map +0 -1
  149. package/dist-server/service/role/role-query.js.map +0 -1
  150. package/dist-server/service/role/role-types.js.map +0 -1
  151. package/dist-server/service/role/role.js.map +0 -1
  152. package/dist-server/service/role-template/index.js.map +0 -1
  153. package/dist-server/service/role-template/role-task.js.map +0 -1
  154. package/dist-server/service/role-template/role-template-gap-resolver.js.map +0 -1
  155. package/dist-server/service/role-template/role-template-mutation.js.map +0 -1
  156. package/dist-server/service/role-template/role-template-query.js.map +0 -1
  157. package/dist-server/service/role-template/role-template-types.js.map +0 -1
  158. package/dist-server/service/role-template/role-template.js.map +0 -1
  159. package/dist-server/service/user/domain-query.js.map +0 -1
  160. package/dist-server/service/user/index.js.map +0 -1
  161. package/dist-server/service/user/user-mutation.js.map +0 -1
  162. package/dist-server/service/user/user-query.js.map +0 -1
  163. package/dist-server/service/user/user-types.js.map +0 -1
  164. package/dist-server/service/user/user.js.map +0 -1
  165. package/dist-server/service/users-auth-providers/index.js.map +0 -1
  166. package/dist-server/service/users-auth-providers/users-auth-providers.js.map +0 -1
  167. package/dist-server/service/verification-token/index.js.map +0 -1
  168. package/dist-server/service/verification-token/verification-token.js.map +0 -1
  169. package/dist-server/service/web-auth-credential/index.js.map +0 -1
  170. package/dist-server/service/web-auth-credential/web-auth-credential-mutation.js.map +0 -1
  171. package/dist-server/service/web-auth-credential/web-auth-credential-query.js.map +0 -1
  172. package/dist-server/service/web-auth-credential/web-auth-credential.js.map +0 -1
  173. package/dist-server/tsconfig.tsbuildinfo +0 -1
  174. package/dist-server/types.js.map +0 -1
  175. package/dist-server/utils/accepts.js.map +0 -1
  176. package/dist-server/utils/access-request-review.js.map +0 -1
  177. package/dist-server/utils/access-token-cookie.js.map +0 -1
  178. package/dist-server/utils/account-lock-rule.js.map +0 -1
  179. package/dist-server/utils/assignable-roles.js.map +0 -1
  180. package/dist-server/utils/build-auth-url.js.map +0 -1
  181. package/dist-server/utils/can-user-checkin-domain.js.map +0 -1
  182. package/dist-server/utils/check-permission.js.map +0 -1
  183. package/dist-server/utils/check-user-belongs-domain.js.map +0 -1
  184. package/dist-server/utils/check-user-has-role.js.map +0 -1
  185. package/dist-server/utils/checkin-domain-facts.js.map +0 -1
  186. package/dist-server/utils/checkin-domain-rule.js.map +0 -1
  187. package/dist-server/utils/credential-at-rest.js.map +0 -1
  188. package/dist-server/utils/credential-features.js.map +0 -1
  189. package/dist-server/utils/credential-serial-rule.js.map +0 -1
  190. package/dist-server/utils/domain-ownership.js.map +0 -1
  191. package/dist-server/utils/encrypt-state.js.map +0 -1
  192. package/dist-server/utils/get-aes-256-key.js.map +0 -1
  193. package/dist-server/utils/get-domain-users.js.map +0 -1
  194. package/dist-server/utils/get-secret.js.map +0 -1
  195. package/dist-server/utils/get-user-domains.js.map +0 -1
  196. package/dist-server/utils/lock-duration.js.map +0 -1
  197. package/dist-server/utils/password-reset-rule.js.map +0 -1
  198. package/dist-server/utils/profile-user.js.map +0 -1
  199. package/dist-server/utils/refuse-role-name.js.map +0 -1
  200. package/dist-server/utils/role-name-standing.js.map +0 -1
  201. package/dist-server/utils/same-origin-redirect.js.map +0 -1
  202. package/dist-server/utils/verification-token-rule.js.map +0 -1
  203. package/spec/unit/checkin-domain.spec.ts +0 -307
  204. package/spec/unit/checkin-privilege.spec.ts +0 -205
  205. package/spec/unit/domain-owner-entity.spec.ts +0 -179
  206. package/spec/unit/domain-owner-granted.spec.ts +0 -215
  207. package/spec/unit/domain-owner-migration.spec.ts +0 -236
  208. package/spec/unit/domain-owner-mutation.spec.ts +0 -337
  209. package/spec/unit/domain-ownership.spec.ts +0 -188
  210. package/tests/access-assistant-db.test.ts +0 -155
  211. package/tests/access-entry-targets-db.test.ts +0 -176
  212. package/tests/access-request-db.test.ts +0 -394
  213. package/tests/access-request-pg.test.ts +0 -83
  214. package/tests/access-request-review.test.ts +0 -193
  215. package/tests/account-lock-db.test.ts +0 -216
  216. package/tests/account-lock-rule.test.ts +0 -141
  217. package/tests/account-mail.test.ts +0 -69
  218. package/tests/activate-page-case.test.ts +0 -53
  219. package/tests/add-user-gates.test.ts +0 -34
  220. package/tests/app-binding-db.test.ts +0 -210
  221. package/tests/app-binding-delete-db.test.ts +0 -188
  222. package/tests/appliance-credential-state-db.test.ts +0 -207
  223. package/tests/appliance-delete-db.test.ts +0 -119
  224. package/tests/appliance-revoke-db.test.ts +0 -294
  225. package/tests/appliance-schema.test.ts +0 -129
  226. package/tests/application-delete-db.test.ts +0 -190
  227. package/tests/application-token-payload-db.test.ts +0 -185
  228. package/tests/assignable-roles-db.test.ts +0 -219
  229. package/tests/audit-db.test.ts +0 -410
  230. package/tests/audit-work.test.ts +0 -58
  231. package/tests/auth-base-role-tasks.test.ts +0 -58
  232. package/tests/checkin-domain-rule.test.ts +0 -128
  233. package/tests/checkin-privilege-seam-db.test.ts +0 -158
  234. package/tests/compiled.ts +0 -41
  235. package/tests/confirm-identity-db.test.ts +0 -85
  236. package/tests/credential-at-rest-db.test.ts +0 -246
  237. package/tests/credential-features-off-db.test.ts +0 -189
  238. package/tests/credential-serial-rule.test.ts +0 -84
  239. package/tests/declare-privileges.test.ts +0 -29
  240. package/tests/domain-assignees-db.test.ts +0 -95
  241. package/tests/domain-inheritance-sentinel.test.ts +0 -93
  242. package/tests/domain-required.test.ts +0 -127
  243. package/tests/has-privilege-db.test.ts +0 -198
  244. package/tests/identity-confirmation.test.ts +0 -146
  245. package/tests/list-inheritance-db.test.ts +0 -127
  246. package/tests/lock-recovery.test.ts +0 -206
  247. package/tests/mail-request-answers-db.test.ts +0 -142
  248. package/tests/open-doors.test.ts +0 -121
  249. package/tests/open-fields-enforcement.test.ts +0 -215
  250. package/tests/open-fields-framework.test.ts +0 -212
  251. package/tests/open-redirect-db.test.ts +0 -143
  252. package/tests/passkey-authenticator.ts +0 -104
  253. package/tests/passkey-db.test.ts +0 -295
  254. package/tests/passkey-signin-route-db.test.ts +0 -139
  255. package/tests/password-hashing.test.ts +0 -131
  256. package/tests/password-persistence.test.ts +0 -147
  257. package/tests/password-reset-rule-db.test.ts +0 -159
  258. package/tests/password-setup-db.test.ts +0 -215
  259. package/tests/permission-gate.test.ts +0 -228
  260. package/tests/picture-reducer.test.ts +0 -25
  261. package/tests/privilege-axis.test.ts +0 -157
  262. package/tests/privilege-deprecation.test.ts +0 -65
  263. package/tests/privilege-directive.test.ts +0 -332
  264. package/tests/privilege-rejection.test.ts +0 -104
  265. package/tests/privilege-sync-db.test.ts +0 -58
  266. package/tests/profile-user.test.ts +0 -24
  267. package/tests/refusal-cause.test.ts +0 -80
  268. package/tests/refusals-have-korean.test.ts +0 -24
  269. package/tests/request-holds.test.ts +0 -53
  270. package/tests/role-inheritance-db.test.ts +0 -286
  271. package/tests/role-mutation-db.test.ts +0 -293
  272. package/tests/role-name-lookup-sentinel.test.ts +0 -79
  273. package/tests/role-name-standing.test.ts +0 -83
  274. package/tests/role-privileges-db.test.ts +0 -252
  275. package/tests/role-task.test.ts +0 -168
  276. package/tests/role-template-gap-db.test.ts +0 -268
  277. package/tests/role-template-seed-db.test.ts +0 -180
  278. package/tests/role-template.test.ts +0 -162
  279. package/tests/role-users-gate.test.ts +0 -22
  280. package/tests/signin-policy.test.ts +0 -200
  281. package/tests/signup-duplicate-db.test.ts +0 -68
  282. package/tests/token-issuance.test.ts +0 -244
  283. package/tests/user-roles-field-db.test.ts +0 -134
  284. package/tests/verification-token-db.test.ts +0 -285
@@ -1,131 +0,0 @@
1
- /**
2
- * 비밀번호 저장 방식 시험.
3
- *
4
- * `signin` 은 `User.verify(user.password, 입력, user.salt)` 하나로 사람을 들여보낸다
5
- * (`controllers/signin.ts:53`). 저장소의 시험 316개 중 이 함수를 부르는 것이 없었다.
6
- *
7
- * 아래는 **지금 그렇다**를 고정하는 시험이고, 그중 셋은 고쳐야 할 사실이다
8
- * (`docs/design/auth-rebuild.md` §5-1):
9
- *
10
- * ① 해시가 **KDF 가 아니다** — HMAC-SHA256 **1회**다. 대입 저항이 사실상 없다
11
- * ② `verify` 가 **SHA-1 도 받는다** — 옛 형식 하위호환
12
- * ③ salt 가 없으면 **`SECRET` 을 salt 로** 쓴다 — 그 경우 전 사용자가 같은 salt 다
13
- *
14
- * KDF 로 옮길 때는 이 시험을 먼저 바꾼다. ①의 「1회 HMAC 과 바이트가 같다」가 깨지는 것이
15
- * 곧 옮겨졌다는 증거다.
16
- *
17
- * (여기서 다루지 않는 것: 비교가 `==` 문자열 비교라 상수 시간이 아니다. 타이밍은 시험으로
18
- * 붙잡을 수 있는 성질이 아니므로 문서에만 남긴다.)
19
- */
20
-
21
- import crypto from 'crypto'
22
-
23
- import { validatePasswordByRule } from '../server/controllers/utils/password-rule.js'
24
- import { loadCompiled } from './compiled'
25
-
26
- const { User } = loadCompiled('service/user/user')
27
- const { SECRET } = loadCompiled('utils/get-secret')
28
-
29
- const PASSWORD = 'Passw0rd!'
30
- const SALT = 'a1b2c3d4e5f6'
31
-
32
- describe('해시 만들기 (User.encode)', () => {
33
- it('HMAC-SHA256 을 한 번 돌린 값과 바이트가 같다 — KDF 가 아니다', () => {
34
- const oneRound = crypto.createHmac('sha256', SALT).update(PASSWORD).digest('base64')
35
-
36
- expect(User.encode(PASSWORD, SALT)).toBe(oneRound)
37
- })
38
-
39
- it('같은 입력에 같은 값을 준다 — 호출마다 무작위가 섞이지 않는다', () => {
40
- expect(User.encode(PASSWORD, SALT)).toBe(User.encode(PASSWORD, SALT))
41
- })
42
-
43
- it('salt 가 다르면 값이 다르다', () => {
44
- expect(User.encode(PASSWORD, SALT)).not.toBe(User.encode(PASSWORD, 'other-salt'))
45
- })
46
-
47
- it('salt 가 없으면 `SECRET` 을 salt 로 쓴다 — 그 사용자들은 salt 를 공유한다', () => {
48
- const withSecretAsSalt = crypto.createHmac('sha256', SECRET).update(PASSWORD).digest('base64')
49
-
50
- expect(User.encode(PASSWORD, undefined)).toBe(withSecretAsSalt)
51
- expect(User.encode(PASSWORD, null)).toBe(withSecretAsSalt)
52
- expect(User.encode(PASSWORD, '')).toBe(withSecretAsSalt)
53
- })
54
-
55
- it('salt 는 16바이트 hex 다', () => {
56
- const salt = User.generateSalt()
57
-
58
- expect(salt).toMatch(/^[0-9a-f]{32}$/)
59
- expect(salt).not.toBe(User.generateSalt())
60
- })
61
- })
62
-
63
- describe('해시 맞추기 (User.verify)', () => {
64
- it('SHA-256 으로 저장된 값을 맞춘다', () => {
65
- expect(User.verify(User.encode(PASSWORD, SALT), PASSWORD, SALT)).toBe(true)
66
- })
67
-
68
- it('틀린 비밀번호를 거절한다', () => {
69
- expect(User.verify(User.encode(PASSWORD, SALT), 'Passw0rd?', SALT)).toBe(false)
70
- })
71
-
72
- it('salt 가 다르면 거절한다', () => {
73
- expect(User.verify(User.encode(PASSWORD, SALT), PASSWORD, 'other-salt')).toBe(false)
74
- })
75
-
76
- it('SHA-1 으로 저장된 값도 받는다 — 옛 형식 하위호환', () => {
77
- const sha1 = crypto.createHmac('sha1', SALT).update(PASSWORD).digest('base64')
78
-
79
- expect(User.verify(sha1, PASSWORD, SALT)).toBe(true)
80
- })
81
-
82
- it('저장된 해시가 없으면 거절한다', () => {
83
- /* 기계 주체의 그림자 사용자는 `password` 칸에 **토큰 원문**이 들어간다
84
- (appliance-mutation.ts:64 · oauth2-server.ts:152). 그 값은 해시가 아니므로
85
- 비밀번호 로그인으로는 통하지 않는다 — 노출 문제는 저장 쪽이고, 로그인 쪽은 막혀 있다. */
86
- expect(User.verify(undefined, PASSWORD, SALT)).toBe(false)
87
- expect(User.verify(null, PASSWORD, SALT)).toBe(false)
88
- expect(User.verify('some-jwt-looking-string', PASSWORD, SALT)).toBe(false)
89
- })
90
- })
91
-
92
- describe('서명 키 (SECRET)', () => {
93
- it('설정이 없으면 저장소에 박힌 상수를 쓴다', () => {
94
- /* production 에서는 던진다(get-secret.ts:7). 그 밖의 환경 — staging 포함 — 은
95
- **공개된 상수**로 토큰을 서명한다. 이 값이 바뀌면 이 시험이 깨진다. */
96
- expect(SECRET).toBe('0xD58F835B69D207A76CC5F84a70a1D0d4C79dAC95')
97
- })
98
- })
99
-
100
- describe('비밀번호 규칙 (기본 설정)', () => {
101
- /** 기본 규칙: 8자 이상 · 소문자 · 대문자 · 숫자 · 특수문자 · 같은 문자 3연속 금지. */
102
- const rejects = (password: string) => expect(() => validatePasswordByRule(password, 'en-US')).toThrow()
103
- const accepts = (password: string) => expect(() => validatePasswordByRule(password, 'en-US')).not.toThrow()
104
-
105
- it('규칙을 모두 만족하면 통과한다', () => {
106
- accepts('Passw0rd!')
107
- })
108
-
109
- it('짧으면 거절한다', () => {
110
- rejects('Pw0rd!')
111
- })
112
-
113
- it('종류가 빠지면 거절한다', () => {
114
- rejects('passw0rd!') // 대문자 없음
115
- rejects('PASSW0RD!') // 소문자 없음
116
- rejects('Password!') // 숫자 없음
117
- rejects('Passw0rdd') // 특수문자 없음
118
- })
119
-
120
- it('같은 문자 3연속을 거절한다', () => {
121
- rejects('Paaassw0rd!')
122
- })
123
-
124
- it('특수문자는 `!@#$%^&*()` 안에서만 인정한다', () => {
125
- /* 규칙 정규식이 그 열 자만 본다(password-rule.ts:36). 그 밖의 기호(`-`·`_`·`~` 등)만
126
- 쓴 비밀번호는 특수문자가 없는 것으로 취급되어 거절된다 — 사용자에게는
127
- 「특수문자를 넣었는데 거절당했다」로 보인다. */
128
- rejects('Passw0rd-')
129
- accepts('Passw0rd#')
130
- })
131
- })
@@ -1,147 +0,0 @@
1
- /**
2
- * 비밀번호 변경이 **DB 에 무엇을 남기나** — 실 엔티티로 저장하고 읽어서 확인한다.
3
- *
4
- * ── 왜 이것이 첫 DB 시험인가 ────────────────────────────────────────────────
5
- * 「비밀번호를 바꾸면 다른 기기의 세션이 끊겨야 한다」를 세우려면 그 판단의 근거가 필요하고,
6
- * 후보는 `User.passwordUpdatedAt` 이다. 그런데 그 칸을 쓰는 코드가 이 모양이다:
7
- *
8
- * await repository.save({ ...user, passwordUpdatedAt: new Date() }) // change-pwd.ts:79
9
- *
10
- * 이 저장소는 **읽어 온 엔티티를 펼쳐 `save` 하면 칸이 조용히 안 써지는** 함정을 이미 한 번
11
- * 겪었다(도메인 시각대 — 로그는 「두었습니다」, `select` 는 `None`). 그러니 그 칸을 근거로
12
- * 삼기 전에 **읽어서 확인해야 한다.** 코드도 로그도 근거가 아니다.
13
- *
14
- * 여기서는 실제 컨트롤러(`changePwd`)를 그대로 태운다 — 칸을 직접 써 보는 것이 아니라,
15
- * 화면이 부르는 그 경로가 무엇을 남기는지를 본다.
16
- */
17
-
18
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
19
- import { loadCompiled } from './compiled'
20
-
21
- const { User } = loadCompiled('service/user/user')
22
- const { changePwd } = loadCompiled('controllers/change-pwd')
23
-
24
- const CURRENT = 'Passw0rd!'
25
- const NEXT = 'N3xtPass!'
26
-
27
- /** `changePwd` 가 컨텍스트에서 실제로 읽는 것만 담는다 — 도메인과 언어뿐이다. */
28
- function contextFor(domain: any) {
29
- return { state: { domain }, lng: 'en-US' }
30
- }
31
-
32
- async function seedUser(domain: any) {
33
- const salt = User.generateSalt()
34
-
35
- return await getRepository(User).save({
36
- username: 'kim',
37
- email: 'kim@example.com',
38
- name: 'Kim',
39
- salt,
40
- password: User.encode(CURRENT, salt),
41
- status: 'activated',
42
- domains: [domain]
43
- })
44
- }
45
-
46
- let domain: any
47
-
48
- beforeAll(async () => {
49
- await openAuthDatabase()
50
- })
51
-
52
- beforeEach(async () => {
53
- await resetAuthDatabase()
54
- domain = await getRepository(Domain).save({ name: 'plant-a', subdomain: 'plant-a' })
55
- })
56
-
57
- afterAll(async () => {
58
- await closeAuthDatabase()
59
- })
60
-
61
- describe('changePwd 가 DB 에 남기는 것', () => {
62
- it('새 비밀번호로 로그인할 수 있게 된다', async () => {
63
- const user = await seedUser(domain)
64
-
65
- await changePwd({ id: user.id }, CURRENT, NEXT, NEXT, contextFor(domain))
66
-
67
- const stored = await getRepository(User).findOneBy({ id: user.id })
68
- expect(User.verify(stored.password, NEXT, stored.salt)).toBe(true)
69
- expect(User.verify(stored.password, CURRENT, stored.salt)).toBe(false)
70
- })
71
-
72
- it('`passwordUpdatedAt` 이 실제로 써진다 — 펼쳐 넣은 칸이 살아남는가', async () => {
73
- /*
74
- * 이 시험이 이 파일의 이유다. 세션 무효화가 이 칸을 근거로 삼을 수 있는지가 여기서 갈린다.
75
- * 반환값이나 로그가 아니라 **다시 읽은 행**을 본다.
76
- */
77
- const user = await seedUser(domain)
78
- expect(user.passwordUpdatedAt).toBeFalsy() /* 새로 만든 행은 `null` 이다 */
79
-
80
- const before = Date.now()
81
- await changePwd({ id: user.id }, CURRENT, NEXT, NEXT, contextFor(domain))
82
- const after = Date.now()
83
-
84
- const stored = await getRepository(User).findOneBy({ id: user.id })
85
-
86
- expect(stored.passwordUpdatedAt).toBeTruthy()
87
-
88
- const written = new Date(stored.passwordUpdatedAt).getTime()
89
- expect(written).toBeGreaterThanOrEqual(before - 1000)
90
- expect(written).toBeLessThanOrEqual(after + 1000)
91
- })
92
-
93
- it('토큰을 돌려준다 — 그리고 그 토큰의 `iat` 이 칸보다 앞선다', async () => {
94
- /*
95
- * `token-issuance.test.ts` 가 초 절삭을 단위로 보였고, 여기서는 **실제 경로**에서 같은 일이
96
- * 일어나는 것을 본다: `changePwd` 는 칸을 쓴 뒤(:79) 토큰을 발급한다(:99). 그래서
97
- * `iat * 1000 < passwordUpdatedAt` 로 옛 세션을 끊으면 **방금 받은 이 토큰도 끊긴다.**
98
- */
99
- const jwt = require('jsonwebtoken')
100
- const user = await seedUser(domain)
101
-
102
- const token = await changePwd({ id: user.id }, CURRENT, NEXT, NEXT, contextFor(domain))
103
- const stored = await getRepository(User).findOneBy({ id: user.id })
104
-
105
- const { iat } = jwt.decode(token) as any
106
- expect(iat * 1000).toBeLessThan(new Date(stored.passwordUpdatedAt).getTime())
107
- })
108
-
109
- it('현재 비밀번호가 틀리면 아무것도 바꾸지 않는다', async () => {
110
- const user = await seedUser(domain)
111
-
112
- await expect(changePwd({ id: user.id }, 'WrongPass!', NEXT, NEXT, contextFor(domain))).rejects.toThrow()
113
-
114
- const stored = await getRepository(User).findOneBy({ id: user.id })
115
- expect(User.verify(stored.password, CURRENT, stored.salt)).toBe(true)
116
- expect(stored.passwordUpdatedAt).toBeFalsy()
117
- })
118
-
119
- it('확인 비밀번호가 다르면 거절한다', async () => {
120
- const user = await seedUser(domain)
121
-
122
- await expect(changePwd({ id: user.id }, CURRENT, NEXT, 'Different1!', contextFor(domain))).rejects.toThrow()
123
-
124
- const stored = await getRepository(User).findOneBy({ id: user.id })
125
- expect(User.verify(stored.password, CURRENT, stored.salt)).toBe(true)
126
- })
127
-
128
- it('새 비밀번호가 규칙에 안 맞으면 거절한다', async () => {
129
- const user = await seedUser(domain)
130
-
131
- await expect(changePwd({ id: user.id }, CURRENT, 'short', 'short', contextFor(domain))).rejects.toThrow()
132
-
133
- const stored = await getRepository(User).findOneBy({ id: user.id })
134
- expect(User.verify(stored.password, CURRENT, stored.salt)).toBe(true)
135
- })
136
-
137
- it('salt 는 그대로 두고 해시만 바꾼다', async () => {
138
- /* salt 를 함께 갈면 비밀번호 이력 대조(`User.verify(h.password, …, h.salt)`)가 옛 salt 를
139
- 들고 있어야 성립한다. 지금 어느 쪽인지를 고정해 둔다. */
140
- const user = await seedUser(domain)
141
-
142
- await changePwd({ id: user.id }, CURRENT, NEXT, NEXT, contextFor(domain))
143
-
144
- const stored = await getRepository(User).findOneBy({ id: user.id })
145
- expect(stored.salt).toBe(user.salt)
146
- })
147
- })
@@ -1,159 +0,0 @@
1
- /**
2
- * Whose password a domain may take back - by issuing a password setup link (ADR-0161).
3
- *
4
- * The reset asked only that the person belong to the asking domain. A person has one password for every domain, so a
5
- * user manager of any domain could take the password of someone who also works elsewhere, of another domain's owner,
6
- * and of the super user (the system domain's owner) the moment that person was a member here. It was the default
7
- * password then; it is a setup link now, and the same people are refused.
8
- */
9
-
10
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
11
- import { loadCompiled } from './compiled'
12
-
13
- const { User } = loadCompiled('service/user/user')
14
- const { UserMutation } = loadCompiled('service/user/user-mutation')
15
- const { UserQuery } = loadCompiled('service/user/user-query')
16
- const { DomainOwner } = loadCompiled('service/domain-owner/domain-owner')
17
-
18
- const OWN_PASSWORD = 'Mine!5678'
19
-
20
- let here: any
21
- let other: any
22
- let system: any
23
-
24
- async function makeUser(domains: any[], extra: any = {}) {
25
- const tag = Math.random().toString(36).slice(2, 8)
26
- const salt = User.generateSalt()
27
- return await getRepository(User).save({
28
- username: `u-${tag}`,
29
- email: `u-${tag}@example.com`,
30
- name: 'Kim',
31
- salt,
32
- password: User.encode(OWN_PASSWORD, salt),
33
- status: 'activated',
34
- domains,
35
- ...extra
36
- })
37
- }
38
-
39
- function context(domain: any) {
40
- return {
41
- state: { domain, tx: undefined },
42
- t: (key: string) => key,
43
- headers: {},
44
- request: { protocol: 'https', host: 'acme.example.com' }
45
- } as any
46
- }
47
-
48
- function reset(user: any, domain: any) {
49
- return new UserMutation().issuePasswordSetupLink(user.username, false, context(domain))
50
- }
51
-
52
- function mayReset(user: any, domain: any) {
53
- return new UserQuery().mayIssuePasswordSetupLink(user.id, context(domain))
54
- }
55
-
56
- async function stillHasOwnPassword(user: any) {
57
- const read = await getRepository(User).findOneBy({ id: user.id })
58
- return User.verify(read.password, OWN_PASSWORD, read.salt)
59
- }
60
-
61
- beforeAll(async () => {
62
- await openAuthDatabase()
63
- })
64
-
65
- afterAll(async () => {
66
- await closeAuthDatabase()
67
- })
68
-
69
- beforeEach(async () => {
70
- await resetAuthDatabase()
71
-
72
- const domainRepository = getRepository(Domain)
73
- here = await domainRepository.save({ name: 'Plant A', subdomain: 'plant-a' })
74
- other = await domainRepository.save({ name: 'Plant B', subdomain: 'plant-b' })
75
- system = await domainRepository.save({ name: 'System', subdomain: 'system' })
76
- })
77
-
78
- describe('a person wholly of this domain', () => {
79
- it('is issued a link and waits for a password, and the screen offers it', async () => {
80
- const user = await makeUser([here])
81
-
82
- expect(await mayReset(user, here)).toBe(true)
83
- await expect(reset(user, here)).resolves.toMatchObject({ link: expect.stringContaining('/auth/reset-password#') })
84
-
85
- const read = await getRepository(User).findOneBy({ id: user.id })
86
- expect(read.status).toBe('password_reset_required')
87
- expect(await stillHasOwnPassword(user)).toBe(false)
88
- })
89
- })
90
-
91
- describe('refused, and the password stays', () => {
92
- it('★ someone who also works in another domain', async () => {
93
- const user = await makeUser([here, other])
94
-
95
- expect(await mayReset(user, here)).toBe(false)
96
- await expect(reset(user, here)).rejects.toMatchObject({
97
- code: 'password-reset-reaches-other-domains',
98
- params: { domains: 'Plant B' }
99
- })
100
- expect(await stillHasOwnPassword(user)).toBe(true)
101
- })
102
-
103
- it("★ another domain's owner, through the owner table, though a member only here", async () => {
104
- const user = await makeUser([here])
105
- await getRepository(DomainOwner).save({ domain: other, user })
106
-
107
- expect(await mayReset(user, here)).toBe(false)
108
- await expect(reset(user, here)).rejects.toMatchObject({ code: 'password-reset-owner-account' })
109
- expect(await stillHasOwnPassword(user)).toBe(true)
110
- })
111
-
112
- it("★ another domain's primary owner (the column only)", async () => {
113
- const user = await makeUser([here])
114
- await getRepository(Domain).update({ id: other.id }, { owner: user.id })
115
-
116
- expect(await mayReset(user, here)).toBe(false)
117
- await expect(reset(user, here)).rejects.toMatchObject({ code: 'password-reset-owner-account' })
118
- expect(await stillHasOwnPassword(user)).toBe(true)
119
- })
120
-
121
- it('★ the super user - asked from the system domain itself', async () => {
122
- const superUser = await makeUser([system])
123
- await getRepository(Domain).update({ id: system.id }, { owner: superUser.id })
124
-
125
- expect(await mayReset(superUser, system)).toBe(false)
126
- await expect(reset(superUser, system)).rejects.toMatchObject({ code: 'password-reset-owner-account' })
127
- expect(await stillHasOwnPassword(superUser)).toBe(true)
128
- })
129
-
130
- it('★ the super user - asked from a domain they are also a member of', async () => {
131
- const superUser = await makeUser([system, here])
132
- await getRepository(Domain).update({ id: system.id }, { owner: superUser.id })
133
-
134
- expect(await mayReset(superUser, here)).toBe(false)
135
- await expect(reset(superUser, here)).rejects.toMatchObject({ code: 'password-reset-owner-account' })
136
- expect(await stillHasOwnPassword(superUser)).toBe(true)
137
- })
138
-
139
- it("this domain's own owner", async () => {
140
- const owner = await makeUser([here])
141
- await getRepository(DomainOwner).save({ domain: here, user: owner })
142
-
143
- expect(await mayReset(owner, here)).toBe(false)
144
- await expect(reset(owner, here)).rejects.toMatchObject({ code: 'password-reset-owner-account' })
145
- })
146
-
147
- it('an admin account, as inactivateUser refuses it', async () => {
148
- const admin = await makeUser([here], { userType: 'admin' })
149
-
150
- expect(await mayReset(admin, here)).toBe(false)
151
- await expect(reset(admin, here)).rejects.toMatchObject({ code: 'password-reset-owner-account' })
152
- })
153
-
154
- it('the screen is told no for someone who is not a member here', async () => {
155
- const stranger = await makeUser([other])
156
-
157
- expect(await mayReset(stranger, here)).toBe(false)
158
- })
159
- })
@@ -1,215 +0,0 @@
1
- /**
2
- * An administrator never sets anybody's password (ADR-0161).
3
- *
4
- * It used to be the installation's default password - one value everyone handed one knew, with no end, for every
5
- * account an administrator made or reset. Now such an account waits for its password, and the person sets it through
6
- * a one-time link that is mailed or handed over: 48 hours, hashed at rest, asking for the address it was for, and the
7
- * only live one. Issuing it ends the sessions opened before.
8
- */
9
-
10
- import { authDataSource, closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
11
- import { loadCompiled } from './compiled'
12
- import { i18next } from '@things-factory/i18n-base/translator'
13
-
14
- const jwt = require('jsonwebtoken')
15
-
16
- const { User } = loadCompiled('service/user/user')
17
- const { UserMutation } = loadCompiled('service/user/user-mutation')
18
- const { VerificationToken } = loadCompiled('service/verification-token/verification-token')
19
- const { signin } = loadCompiled('controllers/signin')
20
- const { resetPassword } = loadCompiled('controllers/reset-password')
21
- const { hashVerificationToken } = loadCompiled('utils/verification-token-rule')
22
-
23
- const OWN_PASSWORD = 'Mine!5678'
24
- const NEW_PASSWORD = 'N3w!Passw0rd'
25
- const HOUR = 60 * 60 * 1000
26
-
27
- let here: any
28
-
29
- function context(tx?: any) {
30
- return {
31
- state: { domain: here, tx },
32
- t: (key: string) => key,
33
- i18next,
34
- headers: {},
35
- request: { protocol: 'https', host: 'acme.example.com' }
36
- } as any
37
- }
38
-
39
- async function makeActive(email = 'kim@example.com') {
40
- const salt = User.generateSalt()
41
- return await getRepository(User).save({
42
- username: email,
43
- email,
44
- name: 'Kim',
45
- salt,
46
- password: User.encode(OWN_PASSWORD, salt),
47
- status: 'activated',
48
- domains: [here]
49
- })
50
- }
51
-
52
- function create(email = 'lee@example.com') {
53
- return new UserMutation().createUser({ username: email, email, name: 'Lee', userType: 'user' }, context())
54
- }
55
-
56
- function issue(user: any, mail = false, tx?: any) {
57
- return new UserMutation().issuePasswordSetupLink(user.username, mail, context(tx))
58
- }
59
-
60
- const tokenOf = (link: string) => link.split('#')[1]
61
- const reload = (id: string) => getRepository(User).findOneBy({ id })
62
- const setPassword = (token: string, email: string, password = NEW_PASSWORD) =>
63
- resetPassword(token, password, email, { t: (key: string) => key })
64
-
65
- beforeAll(async () => {
66
- await openAuthDatabase()
67
- })
68
-
69
- afterAll(async () => {
70
- await closeAuthDatabase()
71
- })
72
-
73
- beforeEach(async () => {
74
- await resetAuthDatabase()
75
- here = await getRepository(Domain).save({ name: 'Plant A', subdomain: 'plant-a' })
76
- })
77
-
78
- describe('an account an administrator makes', () => {
79
- it('★ waits for its password, which nobody was given', async () => {
80
- const made = await create()
81
- const read = await reload(made.id)
82
-
83
- expect(read.status).toBe('password_reset_required')
84
- expect(read.password).toBeTruthy()
85
- })
86
-
87
- it('★ does not sign in, and a guess against it is not counted', async () => {
88
- const made = await create()
89
-
90
- await expect(signin({ username: made.username, password: 'anything' })).rejects.toMatchObject({
91
- errorCode: 'password-setup-pending'
92
- })
93
- expect((await reload(made.id)).failCount ?? 0).toBe(0)
94
- })
95
-
96
- it('★ is refused on every request too', async () => {
97
- const made = await create()
98
-
99
- await expect(User.checkAuth({ username: made.username, email: made.email })).rejects.toMatchObject({
100
- errorCode: 'password-setup-pending'
101
- })
102
- })
103
-
104
- it('is not opened by activating it', async () => {
105
- const made = await create()
106
-
107
- await expect(new UserMutation().activateUser(made.username, context())).rejects.toMatchObject({
108
- code: 'password-setup-pending'
109
- })
110
- })
111
- })
112
-
113
- describe('the setup link', () => {
114
- it('★ is handed back once, kept only as a hash, and lives 48 hours', async () => {
115
- const made = await create()
116
- const before = Date.now()
117
-
118
- const issued = await issue(made)
119
-
120
- expect(issued.link).toMatch(/^https?:\/\/acme\.example\.com\/auth\/reset-password#[0-9a-f]{32}$/)
121
- const stored = await getRepository(VerificationToken).findOneBy({ userId: made.id })
122
- expect(stored.type).toBe('password-setup')
123
- expect(stored.token).toBe(hashVerificationToken(tokenOf(issued.link)))
124
- expect(stored.token).not.toBe(tokenOf(issued.link))
125
- expect(new Date(stored.expiresAt).getTime() - before).toBeGreaterThanOrEqual(48 * HOUR - 1000)
126
- expect(new Date(stored.expiresAt).getTime() - before).toBeLessThanOrEqual(48 * HOUR + 5000)
127
- })
128
-
129
- it('★ opens the account with the address it was for, and then signs in', async () => {
130
- const made = await create()
131
- const { link } = await issue(made)
132
-
133
- await setPassword(tokenOf(link), 'LEE@example.com ')
134
-
135
- expect((await reload(made.id)).status).toBe('activated')
136
- await expect(signin({ username: made.username, password: NEW_PASSWORD })).resolves.toMatchObject({
137
- token: expect.any(String)
138
- })
139
- })
140
-
141
- it('★ is not enough without the address it was for', async () => {
142
- const made = await create()
143
- const { link } = await issue(made)
144
-
145
- await expect(setPassword(tokenOf(link), 'someone@else.example')).rejects.toThrow()
146
- await expect(setPassword(tokenOf(link), '')).rejects.toThrow()
147
- expect((await reload(made.id)).status).toBe('password_reset_required')
148
- })
149
-
150
- it('works once', async () => {
151
- const made = await create()
152
- const { link } = await issue(made)
153
-
154
- await setPassword(tokenOf(link), made.email)
155
- await expect(setPassword(tokenOf(link), made.email, 'An0ther!Pass')).rejects.toThrow()
156
- })
157
-
158
- it('★ issued again, retires the last one', async () => {
159
- const made = await create()
160
- const first = await issue(made)
161
- const second = await issue(made)
162
-
163
- await expect(setPassword(tokenOf(first.link), made.email)).rejects.toThrow()
164
- await setPassword(tokenOf(second.link), made.email)
165
- expect((await reload(made.id)).status).toBe('activated')
166
- })
167
- })
168
-
169
- describe('taking back the password of an account in use', () => {
170
- it('★ stops the old password and ends the sessions opened before', async () => {
171
- const kim = await makeActive()
172
- const { token: before } = await signin({ username: kim.username, password: OWN_PASSWORD })
173
- await expect(User.checkAuth(jwt.decode(before))).resolves.toBeTruthy()
174
-
175
- const { link } = await issue(kim)
176
-
177
- await expect(signin({ username: kim.username, password: OWN_PASSWORD })).rejects.toMatchObject({
178
- errorCode: 'password-setup-pending'
179
- })
180
-
181
- await setPassword(tokenOf(link), kim.email)
182
-
183
- /* Open again - but the session from before stays ended: its generation has passed. */
184
- await expect(User.checkAuth(jwt.decode(before))).rejects.toThrow()
185
- const { token: after } = await signin({ username: kim.username, password: NEW_PASSWORD })
186
- await expect(User.checkAuth(jwt.decode(after))).resolves.toBeTruthy()
187
- })
188
-
189
- it('★ by mail - a mail that does not go out changes nothing', async () => {
190
- const kim = await makeActive()
191
-
192
- await expect(
193
- authDataSource().transaction(async (tx: any) => await issue(kim, true, tx))
194
- ).rejects.toMatchObject({ code: 'password-setup-mail-not-sent' })
195
-
196
- const read = await reload(kim.id)
197
- expect(read.status).toBe('activated')
198
- expect(User.verify(read.password, OWN_PASSWORD, read.salt)).toBe(true)
199
- expect(await getRepository(VerificationToken).findOneBy({ userId: kim.id })).toBeNull()
200
- })
201
- })
202
-
203
- describe('a forgotten password', () => {
204
- it('asks for the address as well, through the same page', async () => {
205
- const kim = await makeActive()
206
- const { makeVerificationToken } = loadCompiled('controllers/utils/make-verification-token')
207
- const { saveVerificationToken } = loadCompiled('controllers/utils/save-verification-token')
208
- const token = makeVerificationToken()
209
- await saveVerificationToken(kim.id, token, 'password-reset')
210
-
211
- await expect(setPassword(token, 'someone@else.example')).rejects.toThrow()
212
- await setPassword(token, kim.email)
213
- await expect(signin({ username: kim.username, password: NEW_PASSWORD })).resolves.toBeTruthy()
214
- })
215
- })