@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,200 +0,0 @@
1
- /**
2
- * `signin` 의 상태 정책 — 실 DB 로 잠금까지 몰아 본다.
3
- *
4
- * 이 경로는 플랫폼에서 사람이 들어오는 **유일한 문**인데 시험이 하나도 없었다. 정책이 코드에
5
- * 흩어져 있고(실패 셈, 잠금, `status` 분기 넷), 순서가 결과를 바꾼다 — 그래서 읽기만으로는
6
- * 「이 계정이 왜 못 들어오나」를 말할 수 없다.
7
- *
8
- * ── 실제로 부르는 모양을 그대로 쓴다 ────────────────────────────────────────
9
- * `signinMiddleware:20` 은 `signin({ username, password })` 를 **context 없이** 부른다.
10
- * 그래서 시험도 context 를 넘기지 않는다 — 넘기면 시험이 화면보다 나은 조건에서 도는 것이 된다.
11
- * 그 차이가 아래 「잠금 해제 메일」 케이스의 주제다.
12
- */
13
-
14
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
15
- import { loadCompiled } from './compiled'
16
-
17
- const { User } = loadCompiled('service/user/user')
18
- const { signin } = loadCompiled('controllers/signin')
19
- const { VerificationToken } = loadCompiled('service/verification-token/verification-token')
20
- /* 오류 코드는 문자열이고 표기가 제각각이다(`'user not found'` · `'password-not-matched'`).
21
- 시험이 그 표기를 베껴 쓰면 코드를 고칠 때 시험만 조용히 틀린다 — 상수를 그대로 든다. */
22
- const CODES = loadCompiled('constants/error-code')
23
-
24
- const PASSWORD = 'Passw0rd!'
25
- const WRONG = 'Wr0ngPass!'
26
-
27
- let domain: any
28
-
29
- async function seedUser(overrides: any = {}) {
30
- const salt = User.generateSalt()
31
-
32
- return await getRepository(User).save({
33
- username: 'kim',
34
- email: 'kim@example.com',
35
- name: 'Kim',
36
- salt,
37
- password: User.encode(PASSWORD, salt),
38
- status: 'activated',
39
- domains: [domain],
40
- ...overrides
41
- })
42
- }
43
-
44
- /** 실 경로와 같게 — context 없이 부른다. */
45
- const attempt = (password: string, username = 'kim') => signin({ username, password })
46
-
47
- async function reload(id: string) {
48
- return await getRepository(User).findOneBy({ id })
49
- }
50
-
51
- beforeAll(async () => {
52
- await openAuthDatabase()
53
- })
54
-
55
- beforeEach(async () => {
56
- await resetAuthDatabase()
57
- domain = await getRepository(Domain).save({ name: 'plant-a', subdomain: 'plant-a' })
58
- })
59
-
60
- afterAll(async () => {
61
- await closeAuthDatabase()
62
- })
63
-
64
- describe('들어가기', () => {
65
- it('맞는 비밀번호로 토큰과 소속 도메인을 받는다', async () => {
66
- const user = await seedUser()
67
-
68
- const result = await attempt(PASSWORD)
69
-
70
- expect(result.token).toBeTruthy()
71
- expect(result.user.id).toBe(user.id)
72
- expect(result.domains.map((d: any) => d.subdomain)).toEqual(['plant-a'])
73
- })
74
-
75
- it('username 이 아니라 email 로도 들어간다', async () => {
76
- await seedUser()
77
-
78
- const result = await attempt(PASSWORD, 'kim@example.com')
79
-
80
- expect(result.token).toBeTruthy()
81
- })
82
- })
83
-
84
- describe('실패 셈과 잠금', () => {
85
- it('틀릴 때마다 셈이 하나 오른다', async () => {
86
- const user = await seedUser()
87
-
88
- for (const expected of [1, 2, 3, 4]) {
89
- await expect(attempt(WRONG)).rejects.toThrow()
90
- expect((await reload(user.id)).failCount).toBe(expected)
91
- }
92
-
93
- /* 4회까지는 아직 열려 있다. */
94
- expect((await reload(user.id)).status).toBe('activated')
95
- })
96
-
97
- it('다섯 번째에 잠긴다', async () => {
98
- const user = await seedUser()
99
-
100
- for (let i = 0; i < 5; i++) {
101
- await expect(attempt(WRONG)).rejects.toThrow()
102
- }
103
-
104
- const locked = await reload(user.id)
105
- expect(locked.status).toBe('locked')
106
- expect(locked.failCount).toBe(5)
107
- })
108
-
109
- it('잠긴 뒤에는 **맞는 비밀번호도** 거절한다', async () => {
110
- /* 잠금 검사가 비밀번호 대조보다 먼저 온다(signin.ts:39). 그래서 남이 남의 계정을
111
- 잠글 수 있고, 잠긴 뒤 본인도 못 들어온다 — 지금 정책이 그렇다. */
112
- const user = await seedUser({ status: 'locked' })
113
-
114
- await expect(attempt(PASSWORD)).rejects.toThrow()
115
- expect((await reload(user.id)).status).toBe('locked')
116
- })
117
-
118
- it('성공하면 셈이 0 으로 돌아간다', async () => {
119
- const user = await seedUser()
120
-
121
- await expect(attempt(WRONG)).rejects.toThrow()
122
- await expect(attempt(WRONG)).rejects.toThrow()
123
- expect((await reload(user.id)).failCount).toBe(2)
124
-
125
- await attempt(PASSWORD)
126
- expect((await reload(user.id)).failCount).toBe(0)
127
- })
128
- })
129
-
130
- describe('상태 분기', () => {
131
- it('삭제된 계정은 비밀번호를 보기도 전에 거절한다', async () => {
132
- const user = await seedUser({ status: 'deleted' })
133
-
134
- await expect(attempt(PASSWORD)).rejects.toThrow()
135
- /* 셈이 오르지 않는다 — 비밀번호 대조 앞에서 끊긴다. */
136
- expect((await reload(user.id)).failCount).toBe(0)
137
- })
138
-
139
- it('미활성 계정은 비밀번호를 **맞춘 뒤에** 거절한다', async () => {
140
- /* 순서가 뜻을 만든다: 틀리면 「비밀번호가 틀렸다」, 맞히면 「활성화되지 않았다」.
141
- 즉 미활성 계정에 대해 비밀번호 맞음/틀림이 응답으로 갈린다. */
142
- const user = await seedUser({ status: 'inactive' })
143
-
144
- await expect(attempt(WRONG)).rejects.toMatchObject({ errorCode: CODES.PASSWORD_NOT_MATCHED })
145
- await expect(attempt(PASSWORD)).rejects.toMatchObject({ errorCode: CODES.USER_NOT_ACTIVATED })
146
- })
147
-
148
- it('없는 계정과 틀린 비밀번호가 다른 오류 코드로 갈린다', async () => {
149
- await seedUser()
150
-
151
- await expect(attempt(PASSWORD, 'nobody')).rejects.toMatchObject({ errorCode: CODES.USER_NOT_FOUND })
152
- await expect(attempt(WRONG)).rejects.toMatchObject({ errorCode: CODES.PASSWORD_NOT_MATCHED })
153
- })
154
- })
155
-
156
- describe('실패 응답에 실려 나가는 것', () => {
157
- it('인증되지 않은 실패가 email 과 남은 시도 횟수를 함께 싣는다', async () => {
158
- /* `detail` 이 화면까지 가는 경로가 있다. 아무 자격 없이 세 가지를 알 수 있다 —
159
- 그 계정이 있다는 것, 그 계정의 email, 그리고 잠기기까지 몇 번 남았는지. */
160
- await seedUser()
161
-
162
- await expect(attempt(WRONG)).rejects.toMatchObject({
163
- detail: { username: 'kim', email: 'kim@example.com', failCount: 1 }
164
- })
165
- })
166
- })
167
-
168
- describe('잠금 해제 메일', () => {
169
- it('잠기면 해제 토큰이 만들어진다', async () => {
170
- /*
171
- * ⚠ **부르는 쪽이 `await` 하지 않는다** — `signin.ts:40,58` 이 `sendUnlockUserEmail(...)` 을
172
- * 띄워 놓고 곧바로 던진다. 그래서 시험이 바로 읽으면 **0건이 나온다.** 그 0건은 제품의
173
- * 사실이 아니라 아직 도착하지 않은 쓰기다(처음에 그것을 「메일이 안 간다」로 읽었다).
174
- *
175
- * 여기서는 그 성질 자체를 고정한다: 즉시 읽으면 없고, 기다리면 있다. 언젠가 그 호출에
176
- * `await` 가 붙으면 앞의 기대가 깨져서 알려 줄 것이다.
177
- *
178
- * 메일 발송 자체는 이 하네스로 확인하지 않는다 — `sendEmail` 이 `config.get('email')` 을
179
- * 읽는데 시험 환경에는 그 설정이 없다(`emailInfo.sender` 에서 던진다). 발송은 운영 설정의
180
- * 일이고, 실제로 도착하고 있음을 사용자가 확인했다.
181
- */
182
- const user = await seedUser()
183
-
184
- for (let i = 0; i < 5; i++) {
185
- await expect(attempt(WRONG)).rejects.toThrow()
186
- }
187
-
188
- expect((await reload(user.id)).status).toBe('locked')
189
-
190
- /* 던진 직후에는 아직 없다 — 부르는 쪽이 기다리지 않기 때문이다. */
191
- expect(await getRepository(VerificationToken).find()).toHaveLength(0)
192
-
193
- await new Promise(resolve => setTimeout(resolve, 300))
194
-
195
- const tokens = await getRepository(VerificationToken).find()
196
- expect(tokens).toHaveLength(1)
197
- expect(tokens[0].type).toBe('unlock')
198
- expect(tokens[0].userId).toBe(user.id)
199
- })
200
- })
@@ -1,68 +0,0 @@
1
- /**
2
- * Signing up refuses a username already taken, and a username that is another account's email.
3
- *
4
- * The email lookup was assigned to `user` instead of `duplicated`, so an email clash was never refused and a second
5
- * account with the same address was made. And a username clash on the profile was named an email clash: the two
6
- * codes shared one value.
7
- */
8
-
9
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
10
- import { loadCompiled } from './compiled'
11
-
12
- const { User } = loadCompiled('service/user/user')
13
- const { signup } = loadCompiled('controllers/signup')
14
- const ERROR_CODES = loadCompiled('constants/error-code')
15
-
16
- const PASSWORD = 'N3w!Passw0rd'
17
-
18
- let here: any
19
-
20
- function signUp(username: string) {
21
- return signup({ name: 'Lee', username, email: username, password: PASSWORD, domain: here, context: { lng: 'en' } })
22
- }
23
-
24
- beforeAll(async () => {
25
- await openAuthDatabase()
26
- })
27
-
28
- afterAll(async () => {
29
- await closeAuthDatabase()
30
- })
31
-
32
- beforeEach(async () => {
33
- await resetAuthDatabase()
34
- here = await getRepository(Domain).save({ name: 'Plant A', subdomain: 'plant-a' })
35
- const salt = User.generateSalt()
36
- await getRepository(User).save({
37
- username: 'kim',
38
- email: 'kim@example.com',
39
- name: 'Kim',
40
- salt,
41
- password: User.encode(PASSWORD, salt),
42
- status: 'activated',
43
- domains: [here]
44
- })
45
- })
46
-
47
- describe('signing up', () => {
48
- it('★ refuses a username that is another account email, in any case', async () => {
49
- await expect(signUp('KIM@example.com')).rejects.toMatchObject({ errorCode: ERROR_CODES.USER_DUPLICATED })
50
- expect(await getRepository(User).countBy({ name: 'Lee' })).toBe(0)
51
- })
52
-
53
- it('refuses a username already taken', async () => {
54
- await expect(signUp('kim')).rejects.toMatchObject({ errorCode: ERROR_CODES.USER_DUPLICATED })
55
- })
56
-
57
- it('makes an account for a username nobody has', async () => {
58
- await signUp('lee@example.com')
59
- expect(await getRepository(User).countBy({ username: 'lee@example.com' })).toBe(1)
60
- })
61
- })
62
-
63
- describe('a username clash', () => {
64
- it('★ is named a username clash, not an email clash', () => {
65
- expect(ERROR_CODES.USERNAME_ALREADY_EXISTS).toBe('username already exists')
66
- expect(ERROR_CODES.USERNAME_ALREADY_EXISTS).not.toBe(ERROR_CODES.EMAIL_ALREADY_EXISTS)
67
- })
68
- })
@@ -1,244 +0,0 @@
1
- /**
2
- * 토큰 발급 시험 — **우리 토큰이 무엇을 말하는가.**
3
- *
4
- * 사람·애플리케이션·어플라이언스 세 종류의 주체가 모두 JWT 로 들어온다. 저장소의 시험 316개
5
- * 중 발급을 부르는 것이 없었다. 여기서 고정하는 것은 「무엇이 실려 있나」와 「무엇이 실려
6
- * 있지 않나」다. **없는 것 쪽이 더 중요하다** (`docs/design/auth-rebuild.md` §4):
7
- *
8
- * · `jti` 가 없다 → 발급한 토큰을 **가리킬 수 없다** = 폐기 수단이 없다.
9
- * 로그아웃은 쿠키만 지우고, 복사된 토큰은 만료까지 살아 있다.
10
- * · `kid` 가 없다 → 두 키를 동시에 신뢰할 수 없다 = 서명 키를 **교체할 수 없다**.
11
- * · 사람 토큰에 도메인이 없다 → 도메인은 `x-things-factory-domain` 헤더가 정한다.
12
- *
13
- * 폐기·회전을 넣을 때 이 시험들이 먼저 깨져야 한다. 「`jti` 가 없다」가 깨지는 것이 곧
14
- * 대장이 생겼다는 증거다.
15
- */
16
-
17
- import jwt from 'jsonwebtoken'
18
-
19
- import { loadCompiled } from './compiled'
20
-
21
- const { User } = loadCompiled('service/user/user')
22
- const { Appliance } = loadCompiled('service/appliance/appliance')
23
- const { Application } = loadCompiled('service/application/application')
24
- const { SECRET } = loadCompiled('utils/get-secret')
25
-
26
- const DAY = 24 * 60 * 60
27
- const YEAR = 365.25 * DAY /* jsonwebtoken 의 `ms` 는 '1y' 를 365.25일로 읽는다 */
28
-
29
- const DOMAIN = { id: 'domain-1', subdomain: 'plant-a' }
30
- const APPLIANCE = { id: 'appliance-1', name: 'gate-scanner' }
31
-
32
- function decode(token: string) {
33
- return jwt.decode(token, { complete: true }) as any
34
- }
35
-
36
- function lifetime(payload: any) {
37
- return payload.exp - payload.iat
38
- }
39
-
40
- function personToken(overrides: any = {}) {
41
- const user = new User()
42
- Object.assign(user, { id: 'user-1', username: 'kim', email: 'kim@example.com', ...overrides })
43
- return user.sign()
44
- }
45
-
46
- describe('사람 토큰 (User.sign)', () => {
47
- it('실려 있는 것은 username · email 과 표준 클레임뿐이다', async () => {
48
- const { payload } = decode(await personToken())
49
-
50
- expect(Object.keys(payload).sort()).toEqual(['email', 'exp', 'iat', 'iss', 'sub', 'username'])
51
- expect(payload).toMatchObject({
52
- username: 'kim',
53
- email: 'kim@example.com',
54
- iss: 'hatiolab.com',
55
- sub: 'user'
56
- })
57
- })
58
-
59
- it('username 이 없으면 email 을 username 자리에 넣는다', async () => {
60
- const { payload } = decode(await personToken({ username: undefined }))
61
-
62
- expect(payload.username).toBe('kim@example.com')
63
- })
64
-
65
- it('사용자 id 를 싣지 않는다 — 토큰은 username 으로 사람을 가리킨다', async () => {
66
- const { payload } = decode(await personToken())
67
-
68
- expect(payload.id).toBeUndefined()
69
- })
70
-
71
- it('기본 수명은 7일이다', async () => {
72
- const { payload } = decode(await personToken())
73
-
74
- expect(lifetime(payload)).toBe(7 * DAY)
75
- })
76
-
77
- it('넘긴 도메인은 버려진다 — 토큰에 도메인이 없다', async () => {
78
- /* `signin` 은 `user.sign({ subdomain: domain?.subdomain })` 로 부르는데(signin.ts:95)
79
- `sign` 은 `expiresIn` 만 꺼내 쓴다(user.ts:186). 넘긴 값이 조용히 사라진다. */
80
- const user = new User()
81
- Object.assign(user, { id: 'user-1', username: 'kim', email: 'kim@example.com' })
82
-
83
- const { payload } = decode(await user.sign({ subdomain: 'plant-a' }))
84
-
85
- expect(payload.subdomain).toBeUndefined()
86
- expect(payload.domain).toBeUndefined()
87
- })
88
- })
89
-
90
- describe('어플라이언스 토큰 (Appliance.generateAccessToken)', () => {
91
- it('주체 종류와 어플라이언스 id, 도메인을 싣는다', () => {
92
- const { payload } = decode(Appliance.generateAccessToken(DOMAIN, { id: 'appuser-1' }, APPLIANCE))
93
-
94
- expect(payload).toMatchObject({
95
- id: 'appuser-1',
96
- userType: 'appliance',
97
- appliance: { id: 'appliance-1' },
98
- status: 'activated',
99
- domain: { subdomain: 'plant-a' },
100
- sub: 'access-token'
101
- })
102
- })
103
-
104
- it('기본 수명이 1년이다', () => {
105
- /* 설정 `applianceJwtExpiresIn` 이 없을 때의 값(appliance.ts:131).
106
- 이 토큰은 DB 에 평문으로도 남는다(appliance-mutation.ts:64,70). */
107
- const { payload } = decode(Appliance.generateAccessToken(DOMAIN, { id: 'appuser-1' }, APPLIANCE))
108
-
109
- expect(lifetime(payload)).toBe(YEAR)
110
- })
111
- })
112
-
113
- describe('애플리케이션 토큰 (Application)', () => {
114
- const SCOPES = ['reader', 'approver']
115
-
116
- /* 서명이 받는 것은 이름을 가진 객체다. 맨 문자열을 받던 시절에는 같은 자리에 appSecret 을
117
- 넣어도 아무 표시가 없었다 — `Application.sign` 의 주석. */
118
- const CLIENT = { appKey: 'APPKEY' }
119
-
120
- it('access token 은 30일, refresh token 은 1년이다', () => {
121
- const access = decode(Application.generateAccessToken(DOMAIN, { id: 'appuser-1' }, CLIENT, SCOPES))
122
- const refresh = decode(Application.generateRefreshToken(DOMAIN, { id: 'appuser-1' }, CLIENT, SCOPES))
123
-
124
- expect(lifetime(access.payload)).toBe(30 * DAY)
125
- expect(lifetime(refresh.payload)).toBe(YEAR)
126
- expect(access.payload.sub).toBe('access-token')
127
- expect(refresh.payload.sub).toBe('refresh-token')
128
- })
129
-
130
- it('appKey 와 scope 를 싣는다 — scope 는 그 도메인의 역할 이름이다', () => {
131
- /* `oauth2-server.ts:131` 이 `roles: scopes` 로 저장하고 `:240` 이 `name: In(scopes)` 로
132
- 역할을 찾는다. 즉 scope 어휘가 따로 없고 역할 이름이 곧 scope 다 — 역할 이름을 바꾸면
133
- 이미 발급된 토큰의 scope 가 조용히 뜻을 잃는다. */
134
- const { payload } = decode(Application.generateAccessToken(DOMAIN, { id: 'appuser-1' }, CLIENT, SCOPES))
135
-
136
- expect(payload).toMatchObject({
137
- userType: 'application',
138
- application: { appKey: 'APPKEY' },
139
- scope: SCOPES
140
- })
141
- })
142
-
143
- it('auth code 는 1분 살고 왕복한다', () => {
144
- const code = Application.generateAuthCode('kim@example.com', 'APPKEY', 'plant-a', ['reader'], 'state-1')
145
- const { payload } = decode(code)
146
-
147
- expect(lifetime(payload)).toBe(60)
148
- expect(Application.verifyAuthCode(code)).toMatchObject({
149
- email: 'kim@example.com',
150
- appKey: 'APPKEY',
151
- subdomain: 'plant-a',
152
- scopes: ['reader'],
153
- state: 'state-1'
154
- })
155
- })
156
-
157
- it('appKey 와 appSecret 은 16바이트 hex 다', () => {
158
- expect(Application.generateAppKey()).toMatch(/^[0-9a-f]{32}$/)
159
- expect(Application.generateAppSecret()).toMatch(/^[0-9a-f]{32}$/)
160
- expect(Application.generateAppKey()).not.toBe(Application.generateAppKey())
161
- })
162
- })
163
-
164
- describe('`iat` 은 초로 절삭된다 — 밀리초 시각과 견주면 새 토큰이 옛것으로 보인다', () => {
165
- /*
166
- * 폐기를 「무효 목록 없이」 하려는 설계가 반드시 밟는 자리다. 사람 행에는
167
- * `passwordUpdatedAt`(밀리초를 가진 `Date`)이 있고, 토큰에는 `iat`(초)이 있다. 그래서
168
- * `decoded.iat * 1000 < user.passwordUpdatedAt` 로 「그 시각 앞에 발급된 토큰을 끊는다」를
169
- * 만들면, **비밀번호를 바꾼 그 사람의 새 토큰까지 끊긴다** — `change-pwd.ts` 가 같은 초에
170
- * 칸을 쓰고(:81) 토큰을 발급하기(:99) 때문이다.
171
- *
172
- * 대장(발급 기록)을 두면 안 생기는 문제이므로, 이 시험은 고칠 대상이 아니라
173
- * **설계가 이 사실을 알고 있어야 한다는 표시**다.
174
- */
175
- /*
176
- * ⚠ 이 시험은 **시계에 기대고 있었고 6번에 1번쯤 빨갛게 났다** (2026-09-10).
177
- *
178
- * 30번을 7ms 간격으로 돌면서 「전부 옛것으로 보인다」를 단정했는데, 그 단정이 틀리는 순간이
179
- * 둘 있었다. 둘 다 결함이 아니라 **관측이 성립하지 않은 회차**다.
180
- *
181
- * 밀리초가 정확히 000 절삭이 아무것도 안 깎는다 — 그 한 순간만 비교가 우연히 안전하다
182
- * 두 줄 사이에 초가 넘어감 `passwordUpdatedAt` 과 토큰이 **다른 초**다 — 전제가 「같은 초」다
183
- *
184
- * 그래서 회차를 세는 대신 **관측이 성립한 회차만** 보고, 그런 회차가 하나도 없으면 조용히
185
- * 초록이 되지 않게 따로 단정한다. 시계를 목으로 바꾸지 않는 이유는 재는 대상이 **발급기가
186
- * 실제로 넣는 `iat`** 이기 때문이다.
187
- */
188
- it('같은 초에 찍은 밀리초 시각보다 새 토큰의 `iat` 이 앞선다', async () => {
189
- const ROUNDS = 30
190
- let observed = 0
191
- let looksOlder = 0
192
-
193
- for (let round = 0; round < ROUNDS; round++) {
194
- /* change-pwd.ts 의 순서: 먼저 `passwordUpdatedAt` 을 쓰고, 그 다음 발급한다. */
195
- const passwordUpdatedAt = new Date()
196
- const { payload } = decode(await personToken())
197
-
198
- const sameSecond = Math.floor(passwordUpdatedAt.getTime() / 1000) === payload.iat
199
- const truncates = passwordUpdatedAt.getMilliseconds() !== 0
200
-
201
- if (sameSecond && truncates) {
202
- observed++
203
- if (payload.iat * 1000 < passwordUpdatedAt.getTime()) looksOlder++
204
- }
205
-
206
- await new Promise(resolve => setTimeout(resolve, 7))
207
- }
208
-
209
- /* 성립한 회차가 없으면 위 단정이 「0 === 0」으로 통과한다 — 그것은 초록이 아니다. */
210
- expect(observed).toBeGreaterThan(0)
211
- expect(looksOlder).toBe(observed)
212
- })
213
- })
214
-
215
- describe('세 종류의 토큰이 공유하는 것 — 그리고 없는 것', () => {
216
- /** 사람·어플라이언스·애플리케이션·auth code 를 한 자리에 모아 같은 질문을 던진다. */
217
- async function everyToken(): Promise<[string, string][]> {
218
- return [
219
- ['사람', await personToken()],
220
- ['어플라이언스', Appliance.generateAccessToken(DOMAIN, { id: 'appuser-1' }, APPLIANCE)],
221
- ['애플리케이션', Application.generateAccessToken(DOMAIN, { id: 'appuser-1' }, 'APPKEY', ['reader'])],
222
- ['auth code', Application.generateAuthCode('kim@example.com', 'APPKEY', 'plant-a', ['reader'], 's')]
223
- ]
224
- }
225
-
226
- it('어느 토큰에도 `jti` 가 없다 — 발급한 것을 가리킬 수 없으니 폐기할 수 없다', async () => {
227
- for (const [kind, token] of await everyToken()) {
228
- expect({ kind, jti: decode(token).payload.jti }).toEqual({ kind, jti: undefined })
229
- }
230
- })
231
-
232
- it('어느 토큰에도 `kid` 가 없다 — 서명 키를 교체할 수 없다', async () => {
233
- for (const [kind, token] of await everyToken()) {
234
- expect({ kind, kid: decode(token).header.kid }).toEqual({ kind, kid: undefined })
235
- }
236
- })
237
-
238
- it('모든 토큰이 같은 대칭키로 HS256 서명된다 — 검증하는 곳은 서명도 할 수 있다', async () => {
239
- for (const [kind, token] of await everyToken()) {
240
- expect({ kind, alg: decode(token).header.alg }).toEqual({ kind, alg: 'HS256' })
241
- expect(() => jwt.verify(token, SECRET)).not.toThrow()
242
- }
243
- })
244
- })
@@ -1,134 +0,0 @@
1
- /**
2
- * `User.roles` — 사용자 목록이 한 사람마다 드는 역할 필드.
3
- *
4
- * `userRoles` · `myRoles` 와 같은 결함(`role-query.ts` 의 ③)을 갖고 있던 세 번째 자리다.
5
- * 사용자를 `relations: ['roles']` 로 읽어 **가진 역할 전부**를 돌려줬다. 도메인 A 와 B 에
6
- * 함께 속한 사람을 A 의 `users { items { roles { name } } }` 로 읽으면 B 의 역할 이름이 나왔다.
7
- *
8
- * 범위는 이 도메인이 부여할 수 있는 것이다 — 내 도메인, 부모 1단, 파트너가 이 도메인에 grant 한
9
- * 역할. 셋째는 사용자 상세가 체크 상태를 이 필드로 채우기 때문에 빠지면 안 된다: 빠지면 저장할
10
- * 때 파트너 역할이 떼어진다.
11
- */
12
-
13
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
14
- import { loadCompiled } from './compiled'
15
-
16
- const { Role } = loadCompiled('service/role/role')
17
- const { GrantedRole } = loadCompiled('service/granted-role/granted-role')
18
- const { User } = loadCompiled('service/user/user')
19
- const { UserQuery } = loadCompiled('service/user/user-query')
20
-
21
- const query = new UserQuery()
22
-
23
- let parent: any
24
- let child: any
25
- let stranger: any
26
-
27
- /** `parentId` 는 `@RelationId` 라 저장소로 읽은 도메인에만 실린다. */
28
- async function loadDomain(id: string) {
29
- return await getRepository(Domain).findOneBy({ id })
30
- }
31
-
32
- /** 필드 리졸버 본문을 직접 부른다 — 목록 질의가 `context.state.domain` 을 채운 상태다. */
33
- async function rolesOf(user: any, domain: any) {
34
- const roles = await query.roles(user, { state: { domain } } as any)
35
- return roles.map((role: any) => role.name).sort()
36
- }
37
-
38
- async function makeUser(domains: any[], roles: any[]) {
39
- const tag = Math.random().toString(36).slice(2, 8)
40
- return await getRepository(User).save({
41
- username: `u-${tag}`,
42
- email: `u-${tag}@example.com`,
43
- name: 'Kim',
44
- salt: User.generateSalt(),
45
- status: 'activated',
46
- domains,
47
- roles
48
- })
49
- }
50
-
51
- beforeAll(async () => {
52
- await openAuthDatabase()
53
- })
54
-
55
- afterAll(async () => {
56
- await closeAuthDatabase()
57
- })
58
-
59
- beforeEach(async () => {
60
- await resetAuthDatabase()
61
-
62
- const domainRepository = getRepository(Domain)
63
-
64
- const savedParent = await domainRepository.save({ name: 'HQ', subdomain: 'hq' })
65
- const savedChild = await domainRepository.save({ name: 'Plant A', subdomain: 'plant-a', parent: savedParent })
66
- const savedStranger = await domainRepository.save({ name: 'Other', subdomain: 'other' })
67
-
68
- parent = await loadDomain(savedParent.id)
69
- child = await loadDomain(savedChild.id)
70
- stranger = await loadDomain(savedStranger.id)
71
- })
72
-
73
- describe('두 도메인에 속한 사람', () => {
74
- it('A 에서 읽으면 A 의 역할만 나온다 — B 의 역할 이름이 새지 않는다', async () => {
75
- const mine = await getRepository(Role).save({ name: 'operator', domain: child })
76
- const theirs = await getRepository(Role).save({ name: 'auditor', domain: stranger })
77
- const user = await makeUser([child, stranger], [mine, theirs])
78
-
79
- expect(await rolesOf(user, child)).toEqual(['operator'])
80
- })
81
-
82
- it('B 에서 읽으면 B 의 역할만 나온다', async () => {
83
- const mine = await getRepository(Role).save({ name: 'operator', domain: child })
84
- const theirs = await getRepository(Role).save({ name: 'auditor', domain: stranger })
85
- const user = await makeUser([child, stranger], [mine, theirs])
86
-
87
- expect(await rolesOf(user, stranger)).toEqual(['auditor'])
88
- })
89
- })
90
-
91
- describe('상속 범위', () => {
92
- it('부모 도메인의 역할은 나온다 — 자식 도메인에서 부여할 수 있는 범위다', async () => {
93
- const shared = await getRepository(Role).save({ name: 'shared', domain: parent })
94
- const local = await getRepository(Role).save({ name: 'local', domain: child })
95
- const user = await makeUser([child], [shared, local])
96
-
97
- expect(await rolesOf(user, child)).toEqual(['local', 'shared'])
98
- })
99
-
100
- it('부모에서 읽으면 자식 도메인의 역할은 나오지 않는다 — 상속은 위로 한 단이다', async () => {
101
- const shared = await getRepository(Role).save({ name: 'shared', domain: parent })
102
- const local = await getRepository(Role).save({ name: 'local', domain: child })
103
- const user = await makeUser([parent, child], [shared, local])
104
-
105
- expect(await rolesOf(user, parent)).toEqual(['shared'])
106
- })
107
- })
108
-
109
- describe('파트너가 grant 한 역할', () => {
110
- it('이 도메인에 grant 된 파트너 역할은 나온다 — 빠지면 사용자 상세가 저장할 때 뗀다', async () => {
111
- const partnerRole = await getRepository(Role).save({ name: 'partner-viewer', domain: stranger })
112
- await getRepository(GrantedRole).save({ role: partnerRole, domain: child })
113
- const user = await makeUser([child], [partnerRole])
114
-
115
- expect(await rolesOf(user, child)).toEqual(['partner-viewer'])
116
- })
117
-
118
- it('다른 도메인에 grant 된 것은 나오지 않는다 — grant 는 받은 도메인에서만 보인다', async () => {
119
- const partnerRole = await getRepository(Role).save({ name: 'partner-viewer', domain: stranger })
120
- await getRepository(GrantedRole).save({ role: partnerRole, domain: child })
121
- const user = await makeUser([child, parent], [partnerRole])
122
-
123
- expect(await rolesOf(user, parent)).toEqual([])
124
- })
125
- })
126
-
127
- describe('역할이 없는 사람', () => {
128
- it('빈 목록을 준다', async () => {
129
- await getRepository(Role).save({ name: 'operator', domain: child })
130
- const user = await makeUser([child], [])
131
-
132
- expect(await rolesOf(user, child)).toEqual([])
133
- })
134
- })