@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,193 +0,0 @@
1
- /**
2
- * The access decision review - pure (ADR-0103, docs/design/access-request.md §6-1).
3
- *
4
- * The list screen, the AI 입력 draft card and the decision door all read this one function, so what it says
5
- * is what every one of them says. Each code is held here on its own.
6
- */
7
-
8
- import {
9
- effectiveState,
10
- expiryFrom,
11
- isBlocked,
12
- reviewAccessDecision,
13
- smallestRole,
14
- type DecisionFacts
15
- } from '../server/utils/access-request-review.js'
16
-
17
- const NOW = new Date('2026-10-02T09:00:00Z')
18
- const ASKED = { category: 'warehouse', privilege: 'mutation' }
19
- const SEE = { category: 'warehouse', privilege: 'query' }
20
- const GRANT_ROLES = { category: 'user', privilege: 'mutation' }
21
-
22
- function facts(over: Partial<DecisionFacts> = {}): DecisionFacts {
23
- return {
24
- request: { state: 'PENDING', requesterId: 'kim', ...ASKED, expiresAt: expiryFrom(NOW) },
25
- deciderId: 'park',
26
- decision: 'GRANT',
27
- reason: null,
28
- role: { id: 'r-inbound', name: 'inbound', privileges: [ASKED] },
29
- requester: { active: true, inDomain: true },
30
- requesterHoldsIt: false,
31
- candidates: [{ id: 'r-inbound', name: 'inbound', privilegeCount: 1 }],
32
- deciderPrivileges: [ASKED, SEE, GRANT_ROLES],
33
- deciderIsOwner: false,
34
- deniedBefore: null,
35
- now: NOW,
36
- ...over
37
- }
38
- }
39
-
40
- const codes = (f: DecisionFacts) => reviewAccessDecision(f).map(issue => `${issue.blocking ? '⛔' : '⚠'}${issue.code}`)
41
-
42
- describe('a clean grant', () => {
43
- it('★ the smallest role that holds exactly what was asked passes with nothing to say', () => {
44
- expect(reviewAccessDecision(facts())).toEqual([])
45
- })
46
- })
47
-
48
- describe('what keeps a decision from being made', () => {
49
- it('★ no such request', () => {
50
- expect(codes(facts({ request: null }))).toEqual(['⛔access-request-not-found'])
51
- })
52
-
53
- it('★ decided, withdrawn or expired already - another person may have decided a moment ago', () => {
54
- for (const state of ['GRANTED', 'DENIED', 'WITHDRAWN', 'EXPIRED'] as const) {
55
- expect(codes(facts({ request: { ...facts().request!, state } }))).toContain('⛔request-not-pending')
56
- }
57
- })
58
-
59
- it('★ a pending request past its time is expired whatever the column says', () => {
60
- const late = facts({ request: { ...facts().request!, expiresAt: new Date(NOW.getTime() - 1) } })
61
- expect(codes(late)).toContain('⛔request-not-pending')
62
- })
63
-
64
- it('★ the decider is the requester', () => {
65
- expect(codes(facts({ deciderId: 'kim' }))).toContain('⛔own-request')
66
- })
67
-
68
- it('★ the requester is locked, inactive or gone from the domain', () => {
69
- expect(codes(facts({ requester: { active: false, inDomain: true } }))).toContain('⛔requester-unavailable')
70
- expect(codes(facts({ requester: { active: true, inDomain: false } }))).toContain('⛔requester-unavailable')
71
- expect(codes(facts({ requester: null }))).toContain('⛔requester-unavailable')
72
- })
73
-
74
- it('★ a denial without a reason', () => {
75
- expect(codes(facts({ decision: 'DENY', reason: ' ', role: null }))).toEqual(['⛔deny-needs-reason'])
76
- expect(codes(facts({ decision: 'DENY', reason: 'IT only', role: null }))).toEqual([])
77
- })
78
-
79
- it('★ the requester holds it already', () => {
80
- expect(codes(facts({ requesterHoldsIt: true }))).toContain('⛔already-holds')
81
- })
82
-
83
- it('★ no role chosen, or one this domain cannot assign', () => {
84
- expect(codes(facts({ role: null }))).toEqual(['⛔role-required'])
85
- expect(codes(facts({ role: null, roleNamedButNotAssignable: true }))).toEqual(['⛔role-not-assignable'])
86
- })
87
-
88
- it('★ the role does not hold the asked privilege', () => {
89
- expect(codes(facts({ role: { id: 'r-viewer', name: 'viewer', privileges: [SEE] } }))).toEqual(['⛔role-does-not-grant'])
90
- })
91
- })
92
-
93
- describe('what the person deciding is told', () => {
94
- const manager = { id: 'r-manager', name: 'manager', privileges: [ASKED, SEE, GRANT_ROLES] }
95
- const candidates = [
96
- { id: 'r-inbound', name: 'inbound', privilegeCount: 1 },
97
- { id: 'r-manager', name: 'manager', privilegeCount: 3 }
98
- ]
99
-
100
- it('★ the role gives more than was asked - named', () => {
101
- const issues = reviewAccessDecision(facts({ role: manager, candidates }))
102
- expect(issues.find(i => i.code === 'role-grants-more')).toMatchObject({
103
- blocking: false,
104
- params: { count: 2, privileges: 'warehouse:query, user:mutation' }
105
- })
106
- })
107
-
108
- it('★ the role hands on the power to give roles', () => {
109
- expect(codes(facts({ role: manager, candidates }))).toContain('⚠role-grants-admin')
110
- })
111
-
112
- it('asking for the role-giving power itself is not warned as a side effect', () => {
113
- const asked = facts({
114
- request: { ...facts().request!, ...GRANT_ROLES },
115
- role: { id: 'r-admin', name: 'admin', privileges: [GRANT_ROLES] },
116
- candidates: [{ id: 'r-admin', name: 'admin', privilegeCount: 1 }]
117
- })
118
- expect(codes(asked)).not.toContain('⚠role-grants-admin')
119
- })
120
-
121
- it('★ a smaller role grants it too - its name', () => {
122
- const issues = reviewAccessDecision(facts({ role: manager, candidates }))
123
- expect(issues.find(i => i.code === 'smaller-role-exists')).toMatchObject({ params: { role: 'inbound', count: 1 } })
124
- })
125
-
126
- it('★ the role gives what the decider does not have - told, not refused (Q6)', () => {
127
- const issues = reviewAccessDecision(facts({ role: manager, candidates, deciderPrivileges: [ASKED] }))
128
- expect(issues.find(i => i.code === 'decider-lacks-it')).toMatchObject({ blocking: false, params: { privileges: 'warehouse:query, user:mutation' } })
129
- expect(isBlocked(issues)).toBe(false)
130
- })
131
-
132
- it('an owner is not told they lack anything', () => {
133
- expect(codes(facts({ role: manager, candidates, deciderPrivileges: [], deciderIsOwner: true }))).not.toContain('⚠decider-lacks-it')
134
- })
135
-
136
- it('★ denied before - by whom, when, why', () => {
137
- const issues = reviewAccessDecision(facts({ deniedBefore: { by: 'Lee', at: '2026-09-01T00:00:00Z', reason: 'IT only' } }))
138
- expect(issues).toEqual([
139
- { code: 'denied-before', blocking: false, field: 'request', params: { by: 'Lee', at: '2026-09-01T00:00:00.000Z', reason: 'IT only' } }
140
- ])
141
- })
142
- })
143
-
144
- describe('helpers', () => {
145
- it('the smallest role, ties by name - the same answer every time', () => {
146
- expect(smallestRole([{ name: 'b', privilegeCount: 2 }, { name: 'a', privilegeCount: 2 }, { name: 'c', privilegeCount: 5 }])?.name).toBe('a')
147
- expect(smallestRole([])).toBeUndefined()
148
- })
149
-
150
- it('a request waits fourteen days', () => {
151
- expect(expiryFrom(NOW).toISOString()).toBe('2026-10-16T09:00:00.000Z')
152
- expect(effectiveState('PENDING', null, NOW)).toBe('PENDING')
153
- expect(effectiveState('GRANTED', new Date(0), NOW)).toBe('GRANTED')
154
- })
155
- })
156
-
157
- describe('the parent domain\'s role (ADR-0103 decision 8)', () => {
158
- it('★ blocks when the decider does not decide in the parent, passes when they do', () => {
159
- expect(codes(facts({ roleIsParents: true, deciderMayChangeParentRoles: false }))).toContain('⛔role-belongs-to-parent')
160
- expect(codes(facts({ roleIsParents: true, deciderMayChangeParentRoles: true }))).not.toContain('⛔role-belongs-to-parent')
161
- expect(codes(facts({ roleIsParents: false }))).not.toContain('⛔role-belongs-to-parent')
162
- })
163
- })
164
-
165
- describe('the task asked for (ADR-0103 decision 9)', () => {
166
- const task = { id: 'warehouse.keeping', privileges: [ASKED, SEE] }
167
-
168
- it('★ a role short of the task asked is told which privileges it lacks - not refused', () => {
169
- const issues = reviewAccessDecision(facts({ task }))
170
- expect(issues.find(issue => issue.code === 'role-lacks-task')).toEqual({
171
- code: 'role-lacks-task',
172
- blocking: false,
173
- field: 'role',
174
- params: { role: 'inbound', task: 'warehouse.keeping', privileges: 'warehouse:query' }
175
- })
176
- expect(isBlocked(issues)).toBe(false)
177
- })
178
-
179
- it('★ says nothing when no task was asked or the task is gone', () => {
180
- expect(codes(facts({ task: null }))).not.toContain('⚠role-lacks-task')
181
- expect(codes(facts())).not.toContain('⚠role-lacks-task')
182
- })
183
-
184
- it('★ a smaller role that drops the task is not proposed over a role holding it', () => {
185
- const keeper = { id: 'r-keeper', name: 'keeper', privileges: [ASKED, SEE, GRANT_ROLES] }
186
- const candidates = [
187
- { id: 'r-keeper', name: 'keeper', privilegeCount: 3, holdsTask: true },
188
- { id: 'r-inbound', name: 'inbound', privilegeCount: 1, holdsTask: false }
189
- ]
190
- expect(codes(facts({ task, role: keeper, candidates }))).not.toContain('⚠smaller-role-exists')
191
- expect(smallestRole(candidates)?.name).toBe('keeper')
192
- })
193
- })
@@ -1,216 +0,0 @@
1
- /**
2
- * 잠금이 스스로 풀리는 것 — 실 경로(`signin` · `checkAuth`)를 태워 본다.
3
- *
4
- * `account-lock-rule.test.ts` 는 규칙만 본다. 규칙이 옳아도 **부르는 자리**가 틀리면 계정은
5
- * 영구히 잠긴 채로 남는다. 그래서 여기서는 실제 컨트롤러를 태우고 **행을 다시 읽는다.**
6
- *
7
- * 시계는 넘길 수 없으므로(컨트롤러가 안에서 `new Date()` 를 읽는다) **`lockedAt` 을 과거로
8
- * 옮겨** 시간이 지난 상태를 만든다.
9
- */
10
-
11
- import { config } from '@things-factory/env'
12
-
13
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
14
- import { loadCompiled } from './compiled'
15
-
16
- const { User } = loadCompiled('service/user/user')
17
- const { signin } = loadCompiled('controllers/signin')
18
-
19
- const PASSWORD = 'Passw0rd!'
20
- const WRONG = 'Wr0ngPass!'
21
-
22
- /** 기본값은 10분이다 — 설정이 없을 때의 값(`utils/lock-duration.ts`). */
23
- const DEFAULT_MINUTES = 10
24
-
25
- let domain: any
26
-
27
- async function seedUser(overrides: any = {}) {
28
- const salt = User.generateSalt()
29
-
30
- return await getRepository(User).save({
31
- username: 'kim',
32
- email: 'kim@example.com',
33
- name: 'Kim',
34
- salt,
35
- password: User.encode(PASSWORD, salt),
36
- status: 'activated',
37
- domains: [domain],
38
- ...overrides
39
- })
40
- }
41
-
42
- const attempt = (password: string) => signin({ username: 'kim', password })
43
-
44
- const reload = (id: string) => getRepository(User).findOneBy({ id })
45
-
46
- /** 잠긴 시각을 `minutes` 분 전으로 옮긴다 — 시간이 지난 상태를 만드는 방법. */
47
- async function ageLock(id: string, minutes: number) {
48
- await getRepository(User).update({ id }, { lockedAt: new Date(Date.now() - minutes * 60 * 1000) })
49
- }
50
-
51
- async function lockOut() {
52
- const user = await seedUser()
53
- for (let i = 0; i < 5; i++) {
54
- await expect(attempt(WRONG)).rejects.toThrow()
55
- }
56
- return user
57
- }
58
-
59
- beforeAll(async () => {
60
- await openAuthDatabase()
61
- })
62
-
63
- beforeEach(async () => {
64
- await resetAuthDatabase()
65
- domain = await getRepository(Domain).save({ name: 'plant-a', subdomain: 'plant-a' })
66
- })
67
-
68
- afterAll(async () => {
69
- await closeAuthDatabase()
70
- })
71
-
72
- describe('잠글 때 시각을 남긴다', () => {
73
- it('다섯 번째 실패에 `lockedAt` 이 써진다', async () => {
74
- const before = Date.now()
75
- const user = await lockOut()
76
- const after = Date.now()
77
-
78
- const locked = await reload(user.id)
79
-
80
- expect(locked.status).toBe('locked')
81
- expect(locked.lockedAt).toBeTruthy()
82
-
83
- const at = new Date(locked.lockedAt).getTime()
84
- expect(at).toBeGreaterThanOrEqual(before - 1000)
85
- expect(at).toBeLessThanOrEqual(after + 1000)
86
- })
87
-
88
- it('네 번째까지는 시각이 없다', async () => {
89
- const user = await seedUser()
90
-
91
- for (let i = 0; i < 4; i++) {
92
- await expect(attempt(WRONG)).rejects.toThrow()
93
- }
94
-
95
- const still = await reload(user.id)
96
- expect(still.status).toBe('activated')
97
- expect(still.lockedAt).toBeFalsy()
98
- })
99
- })
100
-
101
- describe('기본 10분 안에는 잠겨 있다', () => {
102
- it('맞는 비밀번호도 거절한다', async () => {
103
- const user = await lockOut()
104
- await ageLock(user.id, DEFAULT_MINUTES - 1)
105
-
106
- await expect(attempt(PASSWORD)).rejects.toThrow()
107
- expect((await reload(user.id)).status).toBe('locked')
108
- })
109
- })
110
-
111
- describe('시간이 지나면 스스로 풀린다', () => {
112
- it('맞는 비밀번호로 들어가진다', async () => {
113
- const user = await lockOut()
114
- await ageLock(user.id, DEFAULT_MINUTES + 1)
115
-
116
- const result = await attempt(PASSWORD)
117
-
118
- expect(result.token).toBeTruthy()
119
- })
120
-
121
- it('행이 되돌아간다 — 상태·셈·시각 셋 다', async () => {
122
- const user = await lockOut()
123
- await ageLock(user.id, DEFAULT_MINUTES + 1)
124
-
125
- await attempt(PASSWORD)
126
-
127
- const released = await reload(user.id)
128
- expect(released.status).toBe('activated')
129
- expect(released.failCount).toBe(0)
130
- expect(released.lockedAt).toBeFalsy()
131
- })
132
-
133
- it('풀린 뒤 틀려도 셈이 1 부터 다시 센다 — 잠금이 곧 창이다', async () => {
134
- const user = await lockOut()
135
- await ageLock(user.id, DEFAULT_MINUTES + 1)
136
-
137
- /* 틀린 비밀번호로도 잠금이 풀린다 — 판정이 대조보다 먼저다. */
138
- await expect(attempt(WRONG)).rejects.toThrow()
139
-
140
- const after = await reload(user.id)
141
- expect(after.status).toBe('activated')
142
- expect(after.failCount).toBe(1)
143
- expect(after.lockedAt).toBeFalsy()
144
- })
145
-
146
- it('다시 잠기려면 다섯 번을 새로 틀려야 한다', async () => {
147
- const user = await lockOut()
148
- await ageLock(user.id, DEFAULT_MINUTES + 1)
149
-
150
- for (let i = 0; i < 4; i++) {
151
- await expect(attempt(WRONG)).rejects.toThrow()
152
- }
153
- expect((await reload(user.id)).status).toBe('activated')
154
-
155
- await expect(attempt(WRONG)).rejects.toThrow()
156
- expect((await reload(user.id)).status).toBe('locked')
157
- })
158
- })
159
-
160
- describe('`lockedAt` 이 없는 잠긴 행 — 이 변경 전에 잠긴 것', () => {
161
- it('풀리지 않는다', async () => {
162
- /* 조용히 열리는 쪽으로 기울면 잠긴 계정 전체가 한 번에 열린다. 관리자(`activateUser`)와
163
- 잠금 해제 메일이 푸는 길로 남긴다. */
164
- const user = await seedUser({ status: 'locked', failCount: 5, lockedAt: null })
165
-
166
- await expect(attempt(PASSWORD)).rejects.toThrow()
167
- expect((await reload(user.id)).status).toBe('locked')
168
- })
169
- })
170
-
171
- describe('요청 경로(`checkAuth`)도 같은 판정을 쓴다', () => {
172
- it('잠긴 동안에는 토큰을 거절한다', async () => {
173
- const user = await lockOut()
174
- await ageLock(user.id, DEFAULT_MINUTES - 1)
175
-
176
- await expect(User.checkAuth({ username: 'kim' })).rejects.toThrow()
177
- })
178
-
179
- it('시간이 지나면 통과시킨다 — 그리고 **행을 고치지 않는다**', async () => {
180
- /* 매 요청 지나는 경로라 쓰기를 얹지 않는다. 행 정리는 다음 `signin` 이 한다. */
181
- const user = await lockOut()
182
- await ageLock(user.id, DEFAULT_MINUTES + 1)
183
-
184
- const authed = await User.checkAuth({ username: 'kim' })
185
- expect(authed.id).toBe(user.id)
186
-
187
- const untouched = await reload(user.id)
188
- expect(untouched.status).toBe('locked')
189
- expect(untouched.lockedAt).toBeTruthy()
190
- })
191
- })
192
-
193
- describe('설정으로 자동해제를 끌 수 있다', () => {
194
- afterEach(() => {
195
- config.set('password', undefined)
196
- })
197
-
198
- it('0 이면 시간이 지나도 풀리지 않는다', async () => {
199
- config.set('password', { lockDurationMinutes: 0 })
200
-
201
- const user = await lockOut()
202
- await ageLock(user.id, 60 * 24 * 365)
203
-
204
- await expect(attempt(PASSWORD)).rejects.toThrow()
205
- expect((await reload(user.id)).status).toBe('locked')
206
- })
207
-
208
- it('설정한 분이 지나면 풀린다', async () => {
209
- config.set('password', { lockDurationMinutes: 1 })
210
-
211
- const user = await lockOut()
212
- await ageLock(user.id, 2)
213
-
214
- expect((await attempt(PASSWORD)).token).toBeTruthy()
215
- })
216
- })
@@ -1,141 +0,0 @@
1
- /**
2
- * 계정 잠금 판정 규칙 시험 — 순수 함수라 엔티티에 닿지 않는다.
3
- *
4
- * 이 규칙이 하는 일은 하나다: 「이 잠금이 아직 유효한가」. 그 답을 두 곳이 쓴다 —
5
- * `signin`(들어오려는 사람)과 `User.checkAuth`(요청마다). 두 곳이 각자 판단하면 로그인은
6
- * 되는데 요청은 거절되는 상태가 생기므로 규칙을 한 곳에 두었고, 여기서 그 규칙만 본다.
7
- *
8
- * 시계를 인자로 받으므로 「10분 뒤」를 기다리지 않고 시험한다.
9
- */
10
-
11
- import {
12
- AccountLockFacts,
13
- LOCKED_STATUS,
14
- LOCK_FOREVER,
15
- accountLockExpiresAt,
16
- isAccountLocked,
17
- isAccountLockExpired
18
- } from '../server/utils/account-lock-rule.js'
19
- import { loadCompiled } from './compiled'
20
-
21
- const MINUTES = 10
22
- const AT = new Date('2026-09-10T09:00:00.000Z')
23
- const minutesAfter = (n: number) => new Date(AT.getTime() + n * 60 * 1000)
24
-
25
- const locked = (overrides: Partial<AccountLockFacts> = {}): AccountLockFacts => ({
26
- status: LOCKED_STATUS,
27
- lockedAt: AT,
28
- ...overrides
29
- })
30
-
31
- describe('엔티티와 값이 어긋나지 않는다', () => {
32
- it('`LOCKED_STATUS` 가 `UserStatus.LOCKED` 와 같다', () => {
33
- /* 이 규칙은 엔티티를 import 하지 않는다(그러면 순수 시험이 닿지 못한다). 그래서 두 값이
34
- 어긋나는 것을 막는 것은 이 시험뿐이다. */
35
- const { UserStatus } = loadCompiled('service/user/user')
36
-
37
- expect(LOCKED_STATUS).toBe(UserStatus.LOCKED)
38
- })
39
- })
40
-
41
- describe('잠금이 아닌 상태', () => {
42
- it('활성 계정은 잠금이 아니다', () => {
43
- for (const status of ['activated', 'inactive', 'deleted', 'banned', 'password_reset_required']) {
44
- expect(isAccountLocked({ status, lockedAt: AT }, AT, MINUTES)).toBe(false)
45
- expect(isAccountLockExpired({ status, lockedAt: AT }, AT, MINUTES)).toBe(false)
46
- expect(accountLockExpiresAt({ status, lockedAt: AT }, MINUTES)).toBeNull()
47
- }
48
- })
49
-
50
- it('사실이 비어 있어도 던지지 않는다', () => {
51
- for (const facts of [undefined, null, {}, { status: null }] as any[]) {
52
- expect(isAccountLocked(facts, AT, MINUTES)).toBe(false)
53
- }
54
- })
55
- })
56
-
57
- describe('시간이 지나면 풀린다', () => {
58
- it('설정한 시간 안에는 잠겨 있다', () => {
59
- expect(isAccountLocked(locked(), AT, MINUTES)).toBe(true)
60
- expect(isAccountLocked(locked(), minutesAfter(9), MINUTES)).toBe(true)
61
- })
62
-
63
- it('설정한 시간이 지나면 풀린다', () => {
64
- expect(isAccountLocked(locked(), minutesAfter(10), MINUTES)).toBe(false)
65
- expect(isAccountLocked(locked(), minutesAfter(60), MINUTES)).toBe(false)
66
- })
67
-
68
- it('경계는 만료 시각을 포함하지 않는다 — 정확히 10분이면 풀린 것이다', () => {
69
- const facts = locked()
70
-
71
- expect(isAccountLocked(facts, minutesAfter(10 - 1 / 60000), MINUTES)).toBe(true)
72
- expect(isAccountLocked(facts, minutesAfter(10), MINUTES)).toBe(false)
73
- })
74
-
75
- it('풀린 것을 따로 물을 수 있다 — 행을 되돌려야 하는지 판단하는 자리', () => {
76
- expect(isAccountLockExpired(locked(), minutesAfter(5), MINUTES)).toBe(false)
77
- expect(isAccountLockExpired(locked(), minutesAfter(15), MINUTES)).toBe(true)
78
- })
79
-
80
- it('만료 시각을 계산해 준다', () => {
81
- expect(accountLockExpiresAt(locked(), MINUTES)).toEqual(minutesAfter(10))
82
- })
83
-
84
- it('드라이버가 문자열로 준 시각도 읽는다', () => {
85
- /* sqlite·mssql 은 `Date` 대신 문자열을 주기도 한다. */
86
- const facts = locked({ lockedAt: AT.toISOString() })
87
-
88
- expect(isAccountLocked(facts, minutesAfter(5), MINUTES)).toBe(true)
89
- expect(isAccountLocked(facts, minutesAfter(15), MINUTES)).toBe(false)
90
- })
91
- })
92
-
93
- describe('문은 `status` 가 지킨다 — `lockedAt` 이 아니다', () => {
94
- it('한 번도 잠긴 적 없는 계정은 `lockedAt` 이 없어도 열려 있다', () => {
95
- /*
96
- * 이 한 줄이 배포 사고 하나를 막는다. 판정이 `lockedAt` 을 먼저 보면 칸이 비어 있는
97
- * **모든** 계정(= 한 번도 잠긴 적 없는 계정 전부)이 잠김이 되어 아무도 못 들어온다.
98
- * 그래서 `status` 를 먼저 보고, 잠금이 아니면 `lockedAt` 을 아예 보지 않는다.
99
- */
100
- const never = { status: 'activated' } as AccountLockFacts
101
-
102
- expect(isAccountLocked(never, AT, MINUTES)).toBe(false)
103
- expect(isAccountLockExpired(never, AT, MINUTES)).toBe(false)
104
- expect(accountLockExpiresAt(never, MINUTES)).toBeNull()
105
- })
106
-
107
- it('자동해제를 끈 설정에서도 잠기지 않은 계정은 열려 있다', () => {
108
- /* 「자동해제 없음」이 「전원 잠금」이 되지 않는다. */
109
- expect(isAccountLocked({ status: 'activated' }, AT, LOCK_FOREVER)).toBe(false)
110
- expect(isAccountLocked({ status: 'activated', lockedAt: null }, AT, LOCK_FOREVER)).toBe(false)
111
- })
112
- })
113
-
114
- describe('잠긴 계정에서 `lockedAt` 이 없을 때', () => {
115
- it('「잠겼다」가 아니라 **「자동 해제가 없다」**가 된다', () => {
116
- /*
117
- * 이 변경 전에 잠긴 행에는 칸이 비어 있다. 언제 풀릴지 계산할 수 없으니 자동으로는 안
118
- * 풀린다 — 풀어 주는 쪽으로 기울면 **잠긴 계정 전체가 조용히 열린다.** 관리자
119
- * (`activateUser`)와 잠금 해제 메일이 푸는 길로 남는다.
120
- *
121
- * 이것은 위 절과 짝이다: 잠금 여부는 `status` 가 정하고, 여기서 `lockedAt` 이 정하는 것은
122
- * **해제 시각이 있나**뿐이다.
123
- */
124
- for (const lockedAt of [undefined, null, '', 'not-a-date'] as any[]) {
125
- const facts = locked({ lockedAt })
126
-
127
- expect(isAccountLocked(facts, minutesAfter(600), MINUTES)).toBe(true)
128
- expect(isAccountLockExpired(facts, minutesAfter(600), MINUTES)).toBe(false)
129
- expect(accountLockExpiresAt(facts, MINUTES)).toBeNull()
130
- }
131
- })
132
- })
133
-
134
- describe('자동해제를 끄는 설정', () => {
135
- it('0 이하면 풀리지 않는다', () => {
136
- for (const duration of [LOCK_FOREVER, 0, -1, undefined, null, NaN] as any[]) {
137
- expect(isAccountLocked(locked(), minutesAfter(60 * 24 * 365), duration)).toBe(true)
138
- expect(accountLockExpiresAt(locked(), duration)).toBeNull()
139
- }
140
- })
141
- })
@@ -1,69 +0,0 @@
1
- /**
2
- * The four account mails: each in the person's language, with its own subject, in the one mail every module sends -
3
- * the application's brand at the head, never the old frame with a logo fetched from hatiolab.com.
4
- */
5
- import fs from 'fs'
6
- import path from 'path'
7
-
8
- import { DEFAULT_BRAND } from '@things-factory/email-base'
9
- import { i18next, NAMESPACE, translatorFor } from '@things-factory/i18n-base/translator'
10
-
11
- import { ACCOUNT_MAIL_KINDS, accountMail, accountMailWords } from '../server/controllers/account-mail'
12
-
13
- const LANGUAGES = ['ko', 'en', 'ja', 'zh', 'ms', 'es']
14
- const LINK = 'https://office.example.com/auth/reset-password#token'
15
-
16
- /* A test runs outside an application, so the translator has not gathered this module's words; they are added here. */
17
- beforeAll(() => {
18
- for (const lng of LANGUAGES) {
19
- const words = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../translations', `${lng}.json`), 'utf8'))
20
- i18next.addResourceBundle(lng, NAMESPACE, words, true, true)
21
- }
22
- })
23
-
24
- describe('the words of an account mail', () => {
25
- test('every mail has its subject, body and button in every language - never a key', () => {
26
- for (const lng of LANGUAGES) {
27
- for (const kind of ACCOUNT_MAIL_KINDS) {
28
- const words = accountMailWords(translatorFor(lng), kind, 'Brand X')
29
-
30
- for (const [part, text] of Object.entries(words)) {
31
- expect([lng, kind, part, text]).not.toEqual([lng, kind, part, expect.stringMatching(/^(title|text|button)\.account-mail-/)])
32
- expect([lng, kind, part, text.trim()]).not.toEqual([lng, kind, part, ''])
33
- }
34
- expect(words.body).toContain('Brand X')
35
- }
36
- }
37
- })
38
-
39
- test('the four subjects differ in every language - a reset is not mistaken for an unlock', () => {
40
- for (const lng of LANGUAGES) {
41
- const subjects = ACCOUNT_MAIL_KINDS.map(kind => accountMailWords(translatorFor(lng), kind, 'Brand X').subject)
42
- expect(new Set(subjects).size).toBe(ACCOUNT_MAIL_KINDS.length)
43
- }
44
- })
45
- })
46
-
47
- describe('an account mail', () => {
48
- const context = (lng?: string) => ({ lng, i18next })
49
-
50
- test('is written in the person’s language, else the language of the request', () => {
51
- expect(accountMail({ kind: 'unlock-account', user: { locale: 'ko-KR' }, link: LINK, context: context('en-US') }).subject).toBe('계정 잠김')
52
- expect(accountMail({ kind: 'unlock-account', user: { locale: null }, link: LINK, context: context('ja') }).subject).toBe(
53
- translatorFor('ja')('title.account-mail-unlock-account')
54
- )
55
- })
56
-
57
- test('is the one mail: the brand at the head, the subject in the band, the link as the button, no fetched image', () => {
58
- const { subject, content } = accountMail({ kind: 'reset-password', user: { locale: 'ko' }, link: LINK, context: context() })
59
-
60
- expect(subject).toBe('비밀번호 재설정')
61
- expect(content).toContain('<html lang="ko">')
62
- expect(content).toContain(`>${DEFAULT_BRAND.name}<`)
63
- expect(content).toContain(`href="${LINK}"`)
64
- expect(content).toContain('>비밀번호 재설정</font></a')
65
- expect(content).toContain('Copyright © HatioLab Inc. All rights reserved.')
66
- expect(content).not.toContain('<img')
67
- expect(content).not.toContain('Hatio, Lab.')
68
- })
69
- })
@@ -1,53 +0,0 @@
1
- import { loadCompiled } from './compiled'
2
-
3
- const { authenticate401Middleware } = loadCompiled('middlewares/authenticate-401-middleware')
4
- const { AuthError } = loadCompiled('errors/auth-error')
5
- const CODES = loadCompiled('constants/error-code')
6
-
7
- /**
8
- * One page, `auth-activate`, answers a locked account and one not yet confirmed. It said "your
9
- * account is not activated" to both: the reason was in `message`, and the page never read it. A
10
- * locked person was not told why, nor that the unlock mail had already gone.
11
- *
12
- * The page tells the two apart by `errorCode`, so that is what this holds - for both cases.
13
- */
14
-
15
- async function pageFor(errorCode: string) {
16
- let rendered: any
17
-
18
- const context: any = {
19
- t: (key: string) => key,
20
- method: 'GET',
21
- header: { accept: 'text/html' },
22
- path: '/',
23
- originalUrl: '/',
24
- query: {},
25
- request: { body: {} },
26
- render: async (view: string, options: any) => {
27
- rendered = { view, ...options }
28
- }
29
- }
30
-
31
- await authenticate401Middleware(context, async () => {
32
- throw new AuthError({ errorCode, detail: { username: 'kim', email: 'kim@example.com' } })
33
- })
34
-
35
- return rendered
36
- }
37
-
38
- describe.each([CODES.USER_LOCKED, CODES.USER_NOT_ACTIVATED])('%s', errorCode => {
39
- it('goes to the activate page with its code', async () => {
40
- const page = await pageFor(errorCode)
41
-
42
- expect(page.pageElement).toBe('auth-activate')
43
- expect(page.data.errorCode).toBe(errorCode)
44
- })
45
- })
46
-
47
- it('the page reads the locked code by the spelling the server sends', () => {
48
- const { readFileSync } = require('fs')
49
- const { join } = require('path')
50
- const page = readFileSync(join(__dirname, '..', '..', 'auth-ui', 'client', 'entries', 'auth', 'activate.ts'), 'utf-8')
51
-
52
- expect(page).toContain(`this.data?.errorCode === '${CODES.USER_LOCKED}'`)
53
- })