@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,79 +0,0 @@
1
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase } from './db'
2
- import { loadCompiled } from './compiled'
3
-
4
- const { Role } = loadCompiled('service/role/role')
5
- const { standingRoleQuery } = loadCompiled('utils/refuse-role-name')
6
-
7
- /**
8
- * The read behind the inherited-name check asks **one domain at a time**.
9
- *
10
- * ── Why this reads what we send ─────────────────────────────────────────────
11
- * The check has to ask this domain first and the parent only if it has nothing. One read over
12
- * both with `In([own, parent])` has no `ORDER BY`, so when the name stands in both, the answer
13
- * is whichever row the driver hands back — and if that is the parent's, the refusal is the
14
- * inherited one, which the operator's "stand it separately" is allowed to pass. A second row of
15
- * the name then stands in a domain that already has it: the state the whole rule exists to
16
- * prevent, reached through the rule.
17
- *
18
- * ⚠ **This harness cannot fail on that.** sqlite returned this domain's row for the combined
19
- * query, so the outcome was right by accident and every case in
20
- * `role-inheritance-db.test.ts` stayed green with the defect in place — measured 2026-09-20 by
21
- * putting the old shape back: 448 tests, none red.
22
- *
23
- * So this reads the query **before it is sent**, the way `domain-inheritance-sentinel.test.ts`
24
- * does for the same reason. What we build is the same on every driver, and that is the only
25
- * place this class of defect is reachable from here.
26
- */
27
-
28
- const OWN = '00000000-0000-4000-8000-000000000002'
29
- const PARENT = '00000000-0000-4000-8000-000000000001'
30
-
31
- beforeAll(async () => {
32
- await openAuthDatabase()
33
- })
34
-
35
- afterAll(async () => {
36
- await closeAuthDatabase()
37
- })
38
-
39
- function sent(domainId: string): { sql: string; parameters: any[] } {
40
- const [sql, parameters] = standingRoleQuery(getRepository(Role), 'approver', domainId).getQueryAndParameters()
41
-
42
- return { sql, parameters }
43
- }
44
-
45
- describe('the query the check sends', () => {
46
- it('binds the one domain it was asked about', () => {
47
- expect(sent(OWN).parameters).toContain(OWN)
48
- })
49
-
50
- it('never binds a second domain', () => {
51
- /*
52
- * ⚠ The assertion that holds the fix. `In([own, parent])` would put both ids in here, and
53
- * from that moment which one answers is the driver's choice.
54
- */
55
- const { parameters } = sent(OWN)
56
-
57
- expect(parameters.filter(parameter => parameter === PARENT)).toHaveLength(0)
58
- expect(parameters.filter(parameter => typeof parameter === 'string' && parameter.startsWith('0000'))).toEqual([OWN])
59
- })
60
-
61
- it('compares the domain with one value, not a set', () => {
62
- /* `IN (...)` in the text is the shape of asking about more than one domain at once. */
63
- expect(sent(OWN).sql).not.toMatch(/ROLE_DOMAIN.*\bIN\b/i)
64
- })
65
-
66
- it('asks by name as well, bound rather than written in', () => {
67
- /* A name is a value: written into the text it would be an injection and a cache miss. */
68
- const { sql, parameters } = sent(OWN)
69
-
70
- expect(parameters).toContain('approver')
71
- expect(sql).not.toContain('approver')
72
- })
73
-
74
- it('is the same query for the parent, with the parent id', () => {
75
- /* The fallback is the same read, not a second shape that could drift from this one. */
76
- expect(sent(PARENT).parameters).toContain(PARENT)
77
- expect(sent(PARENT).parameters.filter(parameter => parameter === OWN)).toHaveLength(0)
78
- })
79
- })
@@ -1,83 +0,0 @@
1
- import { nameStands, refusesName } from '../server/utils/role-name-standing'
2
-
3
- /**
4
- * Whether a role may take a name that is already standing (ADR-0062 decision 3).
5
- *
6
- * The two directions cost different things. Letting an inherited name through silently leaves
7
- * two rows answering to one name, which is what put an approval line in front of a role nobody
8
- * looks at. Refusing too widely closes the migration path of decision 2 — stand the role up
9
- * here, move the people to the role **of the same name**, cut the parent last — and then a
10
- * domain cannot get off its inherited structure at all.
11
- *
12
- * So most of these are about the inherited case and about what the operator is allowed to say.
13
- */
14
-
15
- const ROOT = { id: 'domain-root' }
16
- const CHILD = { id: 'domain-child', parentId: 'domain-root' }
17
-
18
- describe('where the name stands', () => {
19
- it('nowhere, when nothing was found', () => {
20
- expect(nameStands(null, CHILD)).toEqual('nowhere')
21
- expect(nameStands(undefined, CHILD)).toEqual('nowhere')
22
- expect(nameStands({}, CHILD)).toEqual('nowhere')
23
- })
24
-
25
- it('here, when this domain has it', () => {
26
- expect(nameStands({ domainId: 'domain-child' }, CHILD)).toEqual('here')
27
- })
28
-
29
- it('inherited, when the parent has it', () => {
30
- expect(nameStands({ domainId: 'domain-root' }, CHILD)).toEqual('inherited')
31
- })
32
-
33
- it('nowhere, when the row belongs to some third domain', () => {
34
- /*
35
- * ⚠ Another tenant's role is not this domain's business, and reading it as a collision would
36
- * let one tenant's naming block another's.
37
- */
38
- expect(nameStands({ domainId: 'domain-other' }, CHILD)).toEqual('nowhere')
39
- })
40
-
41
- it('never says inherited for a domain with no parent', () => {
42
- /* A root domain inherits from nobody, so `here` is the only collision it can have. */
43
- expect(nameStands({ domainId: 'domain-root' }, ROOT)).toEqual('here')
44
- expect(nameStands({ domainId: 'domain-child' }, ROOT)).toEqual('nowhere')
45
- })
46
-
47
- it('does not match an absent parent against an absent domain', () => {
48
- /* ⚠ Two missing values are equal to each other, which would make every row inherited. */
49
- expect(nameStands({ domainId: null }, { id: 'domain-child', parentId: null })).toEqual('nowhere')
50
- expect(nameStands({ domainId: undefined }, CHILD)).toEqual('nowhere')
51
- })
52
- })
53
-
54
- describe('whether it refuses', () => {
55
- it('always, for a name this domain already has', () => {
56
- /*
57
- * ⚠ Not something to opt into. Two rows in one domain with one name would be
58
- * indistinguishable in every respect — there is nothing for a screen to show and nothing for
59
- * a person to choose between.
60
- */
61
- expect(refusesName('here')).toBe(true)
62
- expect(refusesName('here', true)).toBe(true)
63
- })
64
-
65
- it('for an inherited name, unless the operator said this domain stands its own', () => {
66
- expect(refusesName('inherited')).toBe(true)
67
- expect(refusesName('inherited', false)).toBe(true)
68
- expect(refusesName('inherited', true)).toBe(false)
69
- })
70
-
71
- it('not at all when the name is free', () => {
72
- expect(refusesName('nowhere')).toBe(false)
73
- expect(refusesName('nowhere', true)).toBe(false)
74
- })
75
-
76
- it('treats a missing answer as not said', () => {
77
- /*
78
- * ⚠ The default has to be refuse. Standing a second role of an inherited name is the
79
- * deliberate act; a caller that says nothing has not made it.
80
- */
81
- expect(refusesName('inherited', undefined)).toBe(true)
82
- })
83
- })
@@ -1,252 +0,0 @@
1
- /**
2
- * `rolePrivileges` — 권한 편집 화면이 드는 **한 번의 질의**를 실 DB 로 태운다.
3
- *
4
- * 이 질의는 두 가지를 함께 답한다: 「무엇이 있나」와 「이 역할이 무엇을 갖나」. 화면이 그것을
5
- * 질의 둘로 받으면 짝짓기를 화면이 해야 하는데, 옛 화면은 그 짝을 역할 **이름**으로 맞췄고
6
- * 부모 도메인에 같은 이름의 역할이 있으면 남의 권한을 그렸다.
7
- *
8
- * 그리고 이 질의는 얼마 전까지 **원시 SQL** 이었다. 셋을 동시에 잃고 있었다 —
9
- * `roleId` 문자열 이어 붙이기, `THEN true`(MSSQL 에 없는 리터럴), 도메인 무시. 아래 시험이
10
- * 그 셋 중 되돌아올 수 있는 둘을 붙잡는다.
11
- *
12
- * ⚠ sqlite 로는 방언 문제를 **못 본다**(`tests/db.ts`). 그것은 원시 SQL 을 안 쓰는 것으로만
13
- * 막힌다. 그래서 이 시험은 「관계로 읽는다」는 결과를 지키고, 방언은 코드에서 지운다.
14
- */
15
-
16
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
17
- import { loadCompiled } from './compiled'
18
-
19
- const { Role } = loadCompiled('service/role/role')
20
- const { Privilege } = loadCompiled('service/privilege/privilege')
21
- const { User } = loadCompiled('service/user/user')
22
- const { RoleQuery } = loadCompiled('service/role/role-query')
23
-
24
- const query = new RoleQuery()
25
-
26
- let parent: any
27
- let child: any
28
- let stranger: any
29
- let boardQuery: any
30
- let boardMutation: any
31
-
32
- /** `parentId` 는 `@RelationId` 라 저장소로 읽은 도메인에만 실린다. */
33
- async function loadDomain(id: string) {
34
- return await getRepository(Domain).findOneBy({ id })
35
- }
36
-
37
- /** 지시자가 없으므로 리졸버 본문을 직접 부른다 — 게이트는 `privilege-directive.test.ts` 가 본다. */
38
- function ask(roleId: string, domain: any) {
39
- return query.rolePrivileges(roleId, { state: { domain } } as any)
40
- }
41
-
42
- function askUserRoles(userId: string, domain: any) {
43
- return query.userRoles(userId, { state: { domain } } as any)
44
- }
45
-
46
- beforeAll(async () => {
47
- await openAuthDatabase()
48
- })
49
-
50
- afterAll(async () => {
51
- await closeAuthDatabase()
52
- })
53
-
54
- beforeEach(async () => {
55
- await resetAuthDatabase()
56
-
57
- const domainRepository = getRepository(Domain)
58
-
59
- const savedParent = await domainRepository.save({ name: 'HQ', subdomain: 'hq' })
60
- const savedChild = await domainRepository.save({ name: 'Plant A', subdomain: 'plant-a', parent: savedParent })
61
- const savedStranger = await domainRepository.save({ name: 'Other', subdomain: 'other' })
62
-
63
- parent = await loadDomain(savedParent.id)
64
- child = await loadDomain(savedChild.id)
65
- stranger = await loadDomain(savedStranger.id)
66
-
67
- const privilegeRepository = getRepository(Privilege)
68
- boardQuery = await privilegeRepository.save({ name: 'query', category: 'board' })
69
- boardMutation = await privilegeRepository.save({ name: 'mutation', category: 'board' })
70
- })
71
-
72
- describe('가진 것과 안 가진 것', () => {
73
- it('전체 권한을 돌려주고, 가진 것에만 표시한다', async () => {
74
- const role = await getRepository(Role).save({ name: 'viewer', domain: child, privileges: [boardQuery] })
75
-
76
- const rows = await ask(role.id, child)
77
-
78
- expect(rows).toHaveLength(2)
79
- expect(rows.find(row => row.id === boardQuery.id).assigned).toBe(true)
80
- expect(rows.find(row => row.id === boardMutation.id).assigned).toBe(false)
81
- })
82
-
83
- it('아무것도 없는 역할에도 목록을 준다 — 편집을 시작할 수 있어야 한다', async () => {
84
- const role = await getRepository(Role).save({ name: 'blank', domain: child, privileges: [] })
85
-
86
- const rows = await ask(role.id, child)
87
-
88
- expect(rows).toHaveLength(2)
89
- expect(rows.every(row => row.assigned === false)).toBe(true)
90
- })
91
- })
92
-
93
- describe('도메인 범위', () => {
94
- it('부모 도메인의 역할은 볼 수 있다 — 상속으로 목록에 나오는 것과 같은 범위다', async () => {
95
- const role = await getRepository(Role).save({ name: 'shared', domain: parent, privileges: [boardMutation] })
96
-
97
- const rows = await ask(role.id, child)
98
-
99
- expect(rows.find(row => row.id === boardMutation.id).assigned).toBe(true)
100
- })
101
-
102
- it('남의 도메인 역할은 못 본다 — id 를 알아도 답하지 않는다', async () => {
103
- const role = await getRepository(Role).save({ name: 'theirs', domain: stranger, privileges: [boardMutation] })
104
-
105
- expect(await ask(role.id, child)).toEqual([])
106
- })
107
-
108
- it('자식 도메인의 역할도 못 본다 — 상속은 위로 한 단이고 아래로는 없다', async () => {
109
- const role = await getRepository(Role).save({ name: 'local', domain: child, privileges: [boardQuery] })
110
-
111
- expect(await ask(role.id, parent)).toEqual([])
112
- })
113
- })
114
-
115
- describe('없는 역할', () => {
116
- it('빈 목록을 준다 — 「전체 권한을 아무것도 안 가진 역할」로 보이지 않는다', async () => {
117
- /*
118
- * 이 구별이 화면에서 중요하다. 전체 목록을 주면서 `assigned` 를 전부 거짓으로 두면
119
- * 화면은 「권한이 하나도 없는 역할」을 그리고, 관리자가 저장하면 **실제로** 그렇게 된다.
120
- */
121
- expect(await ask('00000000-0000-0000-0000-000000000000', child)).toEqual([])
122
- })
123
- })
124
-
125
- describe('부러지지 않는다', () => {
126
- it('id 자리에 SQL 을 넣어도 던지지 않고 빈 목록을 준다', async () => {
127
- /*
128
- * 옛 구현은 이 문자열을 SQL 에 그대로 이어 붙였다. `user:query` 를 가진 사람이 닿는
129
- * 자리이므로 그것으로 충분히 위험했다. 지금은 값으로만 쓰인다.
130
- */
131
- expect(await ask("' OR 1=1 --", child)).toEqual([])
132
- })
133
- })
134
-
135
- /**
136
- * 목록이 쓰는 **수**.
137
- *
138
- * 목록이 「이 역할이 무엇을 여나」를 말하려고 권한 **행**을 받고 있었다 — 역할 20개에
139
- * 5,731 bytes · 69ms 대 518 bytes · 7ms(UX 레인 실측). 행을 안 받고 수를 받는다.
140
- */
141
- describe('권한 수', () => {
142
- it('가진 것만 센다', async () => {
143
- const role = await getRepository(Role).save({ name: 'viewer', domain: child, privileges: [boardQuery] })
144
-
145
- expect(await query.privilegeCount(role)).toBe(1)
146
- })
147
-
148
- it('하나도 없으면 0 이다 — 「모른다」가 아니라 0 이다', async () => {
149
- const role = await getRepository(Role).save({ name: 'blank', domain: child, privileges: [] })
150
-
151
- expect(await query.privilegeCount(role)).toBe(0)
152
- })
153
-
154
- it('다른 역할의 권한을 세지 않는다', async () => {
155
- await getRepository(Role).save({ name: 'other', domain: child, privileges: [boardQuery, boardMutation] })
156
- const mine = await getRepository(Role).save({ name: 'mine', domain: child, privileges: [boardQuery] })
157
-
158
- expect(await query.privilegeCount(mine)).toBe(1)
159
- })
160
- })
161
-
162
- /* 가진 사람 수 — 가진 사람이 있는 역할은 지우지 않으므로 목록이 지우기 전에 보인다(2026-10-28). */
163
- describe('가진 사람 수', () => {
164
- async function holder(roles: any[]) {
165
- const salt = User.generateSalt()
166
- const tag = Math.random().toString(36).slice(2, 8)
167
- return await getRepository(User).save({ username: `h-${tag}`, email: `h-${tag}@example.com`, name: 'Lee', salt, status: 'activated', roles })
168
- }
169
-
170
- it('그 역할을 가진 사람만 센다', async () => {
171
- const mine = await getRepository(Role).save({ name: 'mine', domain: child, privileges: [] })
172
- const other = await getRepository(Role).save({ name: 'other', domain: child, privileges: [] })
173
- await holder([mine])
174
- await holder([mine, other])
175
- await holder([other])
176
-
177
- expect(await query.userCount(mine)).toBe(2)
178
- expect(await query.userCount(other)).toBe(2)
179
- })
180
-
181
- it('아무도 없으면 0 이다', async () => {
182
- const role = await getRepository(Role).save({ name: 'blank', domain: child, privileges: [] })
183
-
184
- expect(await query.userCount(role)).toBe(0)
185
- })
186
- })
187
-
188
- /**
189
- * 한 사람의 역할 — `rolePrivileges` 와 **같은 결함을 갖고 있던** 자리.
190
- *
191
- * 원시 SQL 로 `userId` 를 이어 붙이고, MSSQL 에 없는 `THEN true` 를 쓰고, `FROM roles` 로
192
- * **도메인을 아예 안 봤다.** 한 자리를 고쳤으면 두 번째 자리도 재야 한다.
193
- */
194
- describe('사용자의 역할', () => {
195
- async function makeUser(roles: any[]) {
196
- const salt = User.generateSalt()
197
- return await getRepository(User).save({
198
- username: `u-${Math.random().toString(36).slice(2, 8)}`,
199
- email: `u-${Math.random().toString(36).slice(2, 8)}@example.com`,
200
- name: 'Kim',
201
- salt,
202
- status: 'activated',
203
- domains: [child],
204
- roles
205
- })
206
- }
207
-
208
- it('부여할 수 있는 전체를 주고, 가진 것에만 표시한다', async () => {
209
- const held = await getRepository(Role).save({ name: 'held', domain: child })
210
- await getRepository(Role).save({ name: 'free', domain: child })
211
- const user = await makeUser([held])
212
-
213
- const rows = await askUserRoles(user.id, child)
214
-
215
- expect(rows).toHaveLength(2)
216
- expect(rows.find(row => row.id === held.id).assigned).toBe(true)
217
- expect(rows.find(row => row.name === 'free').assigned).toBe(false)
218
- })
219
-
220
- it('부모 도메인의 역할도 준다 — 상속이 부여 범위다', async () => {
221
- await getRepository(Role).save({ name: 'shared', domain: parent })
222
- const user = await makeUser([])
223
-
224
- expect((await askUserRoles(user.id, child)).map((row: any) => row.name)).toEqual(['shared'])
225
- })
226
-
227
- it('남의 도메인 역할은 주지 않는다 — 예전에는 전부 줬다', async () => {
228
- await getRepository(Role).save({ name: 'theirs', domain: stranger })
229
- await getRepository(Role).save({ name: 'mine', domain: child })
230
- const user = await makeUser([])
231
-
232
- expect((await askUserRoles(user.id, child)).map((row: any) => row.name)).toEqual(['mine'])
233
- })
234
-
235
- it('없는 사용자에게도 목록을 준다 — 가진 것이 0 인 것과 같다', async () => {
236
- await getRepository(Role).save({ name: 'mine', domain: child })
237
-
238
- const rows = await askUserRoles('00000000-0000-0000-0000-000000000000', child)
239
-
240
- expect(rows).toHaveLength(1)
241
- expect(rows[0].assigned).toBe(false)
242
- })
243
-
244
- it('id 자리에 SQL 을 넣어도 던지지 않는다', async () => {
245
- await getRepository(Role).save({ name: 'mine', domain: child })
246
-
247
- const rows = await askUserRoles("' OR 1=1 --", child)
248
-
249
- expect(rows).toHaveLength(1)
250
- expect(rows[0].assigned).toBe(false)
251
- })
252
- })
@@ -1,168 +0,0 @@
1
- /**
2
- * Role tasks — the registry, what it refuses, and how a role is laid against tasks (ADR-0139 decision 11).
3
- *
4
- * The comparison is pure: a role's held privileges, the registered tasks, the declared privileges. The role screen
5
- * draws exactly this, so what "all", "some", "none" and "broken" mean is pinned here.
6
- */
7
-
8
- import { loadCompiled } from './compiled'
9
-
10
- const { registerRoleTask, roleTasks, roleTaskById, mergeGrants, taskCoverageOf, undeclaredTaskGrants } = loadCompiled('service/role-template/role-task')
11
- const { registerRoleTemplate, templateGrantsOf, templateDefinitionErrors, unknownTemplateGrants } = loadCompiled('service/role-template/role-template')
12
-
13
- const DECLARED = {
14
- 'board query': ['board', 'query'],
15
- 'board mutation': ['board', 'mutation'],
16
- 'order query': ['order', 'query'],
17
- 'worklist query': ['worklist', 'query'],
18
- 'worklist mutation': ['worklist', 'mutation']
19
- }
20
-
21
- beforeEach(() => {
22
- ;(process as any)['ROLE_TASKS'] = {}
23
- ;(process as any)['ROLE_TEMPLATES'] = {}
24
- ;(process as any).PRIVILEGES = { ...DECLARED }
25
- })
26
-
27
- const held = (...pairs: string[]) => new Set(pairs.map(pair => pair.replace(':', ' ')))
28
-
29
- describe('declaring a task', () => {
30
- it('can be found again by id', () => {
31
- registerRoleTask({ id: 'board.reading', grants: [{ category: 'board', axes: ['query'] }] })
32
-
33
- expect(roleTaskById('board.reading')).toEqual({ id: 'board.reading', grants: [{ category: 'board', axes: ['query'] }] })
34
- expect(roleTasks()).toHaveLength(1)
35
- })
36
-
37
- it.each([['reading'], [''], ['.reading']])('refuses the id "%s" - the declaring module comes first', id => {
38
- expect(() => registerRoleTask({ id, grants: [{ category: 'board', axes: ['query'] }] })).toThrow(/<module>.<work>/)
39
- })
40
-
41
- it('refuses a task that holds no privileges', () => {
42
- expect(() => registerRoleTask({ id: 'board.nothing', grants: [] })).toThrow(/holds no privileges/)
43
- })
44
-
45
- it('refuses a grant with no axes - axes are listed one by one', () => {
46
- expect(() => registerRoleTask({ id: 'board.reading', grants: [{ category: 'board', axes: [] }] })).toThrow(/one by one/)
47
- })
48
-
49
- it('refuses the same task declared twice - one piece of work is one task', () => {
50
- registerRoleTask({ id: 'board.reading', grants: [{ category: 'board', axes: ['query'] }] })
51
-
52
- expect(() => registerRoleTask({ id: 'board.reading', grants: [{ category: 'order', axes: ['query'] }] })).toThrow(/declared twice/)
53
- })
54
-
55
- it('holds a privilege written twice once', () => {
56
- registerRoleTask({ id: 'board.reading', grants: [{ category: 'board', axes: ['query'] }, { category: 'board', axes: ['query'] }] })
57
-
58
- expect(roleTaskById('board.reading').grants).toEqual([{ category: 'board', axes: ['query'] }])
59
- })
60
-
61
- it('★ lets two tasks share a privilege - that is not a duplicate', () => {
62
- registerRoleTask({ id: 'board.reading', grants: [{ category: 'board', axes: ['query'] }] })
63
- registerRoleTask({ id: 'order.reading', grants: [{ category: 'order', axes: ['query'] }, { category: 'board', axes: ['query'] }] })
64
-
65
- expect(roleTasks()).toHaveLength(2)
66
- })
67
-
68
- it('keeps a change-only grant as written - nothing adds query to it', () => {
69
- registerRoleTask({ id: 'worklist.putting-up', grants: [{ category: 'worklist', axes: ['mutation'] }] })
70
-
71
- expect(roleTaskById('worklist.putting-up').grants).toEqual([{ category: 'worklist', axes: ['mutation'] }])
72
- })
73
- })
74
-
75
- describe('a template built from tasks', () => {
76
- beforeEach(() => {
77
- registerRoleTask({ id: 'board.reading', grants: [{ category: 'board', axes: ['query'] }] })
78
- registerRoleTask({ id: 'order.reading', grants: [{ category: 'order', axes: ['query'] }, { category: 'board', axes: ['query'] }] })
79
- })
80
-
81
- it('★ holds the union of its tasks and its own grants, each privilege once', () => {
82
- registerRoleTemplate({ id: 'plant.reader', name: 'READER', tasks: ['board.reading', 'order.reading'], grants: [{ category: 'board', axes: ['mutation', 'query'] }] })
83
-
84
- expect(templateGrantsOf({ id: 'plant.reader', name: 'READER', tasks: ['board.reading', 'order.reading'], grants: [{ category: 'board', axes: ['mutation', 'query'] }] })).toEqual([
85
- { category: 'board', axes: ['query', 'mutation'] },
86
- { category: 'order', axes: ['query'] }
87
- ])
88
- })
89
-
90
- it('may be written with tasks and no grants', () => {
91
- expect(() => registerRoleTemplate({ id: 'plant.reader', name: 'READER', tasks: ['board.reading'] })).not.toThrow()
92
- })
93
-
94
- it('still refuses a template with neither - leaving both out is forgetting', () => {
95
- expect(() => registerRoleTemplate({ id: 'plant.reader', name: 'READER' } as any)).toThrow(/has no grants/)
96
- })
97
-
98
- it('★ names an unregistered task as a definition error', () => {
99
- expect(templateDefinitionErrors({ id: 'plant.reader', name: 'READER', tasks: ['board.reading', 'board.writting'] })).toEqual(['task "board.writting" is not registered'])
100
- })
101
-
102
- it('★ names a task privilege this installation does not declare as a definition error', () => {
103
- registerRoleTask({ id: 'board.exporting', grants: [{ category: 'board-export', axes: ['query'] }] })
104
-
105
- expect(undeclaredTaskGrants(roleTaskById('board.exporting'))).toEqual(['board-export:query'])
106
- expect(templateDefinitionErrors({ id: 'plant.reader', name: 'READER', tasks: ['board.exporting'] })).toEqual([
107
- 'task "board.exporting" names board-export:query, which this installation does not declare'
108
- ])
109
- })
110
-
111
- it("keeps a template's own undeclared grants as before - skipped, not a definition error", () => {
112
- const template = { id: 'plant.reader', name: 'READER', grants: [{ category: 'kpi', axes: ['query'] }] }
113
-
114
- expect(templateDefinitionErrors(template)).toEqual([])
115
- expect(unknownTemplateGrants(template)).toEqual(['kpi:query'])
116
- })
117
- })
118
-
119
- describe('a role laid against the tasks', () => {
120
- beforeEach(() => {
121
- registerRoleTask({ id: 'board.reading', grants: [{ category: 'board', axes: ['query'] }] })
122
- registerRoleTask({ id: 'board.keeping', grants: [{ category: 'board', axes: ['query', 'mutation'] }] })
123
- registerRoleTask({ id: 'order.reading', grants: [{ category: 'order', axes: ['query'] }, { category: 'board', axes: ['query'] }] })
124
- })
125
-
126
- const statuses = (coverage: any) => Object.fromEntries(coverage.tasks.map((task: any) => [task.taskId, task.status]))
127
-
128
- it('★ says all, some and none by what the role holds of each task', () => {
129
- const coverage = taskCoverageOf(held('board:query'))
130
-
131
- expect(statuses(coverage)).toEqual({ 'board.reading': 'all', 'board.keeping': 'some', 'order.reading': 'some' })
132
- expect(coverage.tasks.find((task: any) => task.taskId === 'board.keeping').missing).toEqual([{ category: 'board', axes: ['mutation'] }])
133
- })
134
-
135
- it('★ a shared privilege alone makes every task that names it "some" - reported, not supplied', () => {
136
- const coverage = taskCoverageOf(held('board:query'))
137
-
138
- expect(coverage.tasks.find((task: any) => task.taskId === 'order.reading')).toMatchObject({
139
- status: 'some',
140
- held: [{ category: 'board', axes: ['query'] }],
141
- missing: [{ category: 'order', axes: ['query'] }]
142
- })
143
- })
144
-
145
- it('says none for a role that holds nothing of a task', () => {
146
- expect(statuses(taskCoverageOf(held('worklist:query')))).toEqual({ 'board.reading': 'none', 'board.keeping': 'none', 'order.reading': 'none' })
147
- })
148
-
149
- it('★ keeps what no task names apart - and leaves out what nothing declares any more', () => {
150
- const coverage = taskCoverageOf(held('board:query', 'worklist:query', 'worklist:mutation', 'retired:query'))
151
-
152
- expect(coverage.unclassified).toEqual([{ category: 'worklist', axes: ['mutation', 'query'] }])
153
- })
154
-
155
- it('★ says broken for a task naming an undeclared privilege, whatever the role holds', () => {
156
- registerRoleTask({ id: 'board.exporting', grants: [{ category: 'board', axes: ['query'] }, { category: 'board-export', axes: ['query'] }] })
157
-
158
- const task = taskCoverageOf(held('board:query')).tasks.find((one: any) => one.taskId === 'board.exporting')
159
- expect(task).toMatchObject({ status: 'broken', module: 'board', undeclared: [{ category: 'board-export', axes: ['query'] }] })
160
- })
161
-
162
- it('merges grants by category in the order first written', () => {
163
- expect(mergeGrants([[{ category: 'b', axes: ['x'] }], [{ category: 'a', axes: ['y'] }, { category: 'b', axes: ['z', 'x'] }]])).toEqual([
164
- { category: 'b', axes: ['x', 'z'] },
165
- { category: 'a', axes: ['y'] }
166
- ])
167
- })
168
- })