@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,158 +0,0 @@
1
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
2
- import { loadCompiled } from './compiled'
3
-
4
- const { Privilege } = loadCompiled('service/privilege/privilege')
5
- const { Role } = loadCompiled('service/role/role')
6
- const { User, UserStatus } = loadCompiled('service/user/user')
7
- const { canUserCheckinDomain } = loadCompiled('utils/can-user-checkin-domain')
8
-
9
- /**
10
- * The seam between "may this person come in" and "may this person do that".
11
- *
12
- * ── The two rules are not the same rule ─────────────────────────────────────
13
- * Coming in (`checkin-domain-rule.ts`) asks for **membership AND a role**, or a partner grant,
14
- * or ownership. The membership half is load-bearing and the file says why: a role defined in a
15
- * parent domain is inherited by every child, so "holds a parent role" on its own would make a
16
- * role granted for one child an entry pass to all its siblings.
17
- *
18
- * Acting (`User.hasPrivilege`) asks only for a role in this domain or its parent. There is no
19
- * membership condition. That is on purpose and written down — the comment above it delegates
20
- * the sibling problem to the checkin gate.
21
- *
22
- * ── So what does this file do ───────────────────────────────────────────────
23
- * It writes down the one line where the two answers differ, which the specs never had:
24
- *
25
- * not a member + holds a parent role checkin ✗ privilege ○
26
- *
27
- * `spec/unit/checkin-privilege.spec.ts` opens by talking about "a user who checked in to a
28
- * child domain", and then every one of its ten fixtures makes that user a member — the variable
29
- * is even called `member`. The premise it names is never the thing it varies, so the gap sits
30
- * outside all of it.
31
- *
32
- * ── Why this is pinned green rather than written red ────────────────────────
33
- * Both halves are what the code does today, and today it does not leak: every server caller of
34
- * `hasPrivilege` sits behind `domainAuthenticateMiddleware`, which runs the checkin gate first
35
- * (measured 2026-09-20 across all nineteen call sites — board-service and figure-service routers
36
- * are on `domainPrivateRouter`, the rest arrive through GraphQL or the subscription middleware).
37
- *
38
- * So this is one layer of depth, not an open door. A red test would mean a broken build for a
39
- * decision nobody has taken yet. What this does instead is make the gap impossible to close or
40
- * widen without saying so: **when the privilege layer gains the membership condition, the second
41
- * assertion here flips**, and that is the axis the fix splits.
42
- *
43
- * ⚠ sqlite (`tests/db.ts`).
44
- */
45
-
46
- let parent: any
47
- let child: any
48
- let sibling: any
49
- let outsider: any
50
-
51
- /** `context.state.domain` carries `parentId`; the repository does not fill it in. */
52
- async function withParentId(domain: any, parentId: string | null) {
53
- return { ...(await getRepository(Domain).findOneBy({ id: domain.id })), parentId }
54
- }
55
-
56
- beforeAll(async () => {
57
- await openAuthDatabase()
58
- })
59
-
60
- afterAll(async () => {
61
- await closeAuthDatabase()
62
- })
63
-
64
- beforeEach(async () => {
65
- await resetAuthDatabase()
66
-
67
- const p = await getRepository(Domain).save({ name: 'system', subdomain: 'system' })
68
- const c = await getRepository(Domain).save({ name: 'acme', subdomain: 'acme', parent: p })
69
- const s = await getRepository(Domain).save({ name: 'beta', subdomain: 'beta', parent: p })
70
- const o = await getRepository(Domain).save({ name: 'gamma', subdomain: 'gamma' })
71
-
72
- parent = await withParentId(p, null)
73
- child = await withParentId(c, p.id)
74
- sibling = await withParentId(s, p.id)
75
- outsider = await withParentId(o, null)
76
- })
77
-
78
- async function inheritedRoleHolder(memberOf: any[]) {
79
- const seeing = await getRepository(Privilege).save({ name: 'query', category: 'board' })
80
- const role = await getRepository(Role).save({
81
- name: 'viewer',
82
- domain: { id: parent.id },
83
- privileges: [seeing]
84
- })
85
-
86
- const person = await getRepository(User).save({
87
- username: 'kim@acme.z',
88
- email: 'kim@acme.z',
89
- name: 'kim',
90
- status: UserStatus.ACTIVATED,
91
- domains: memberOf.map(domain => ({ id: domain.id })),
92
- roles: [role]
93
- })
94
-
95
- return await getRepository(User).findOne({ where: { id: person.id }, relations: ['domains', 'roles'] })
96
- }
97
-
98
- describe('a member of this domain holding an inherited role', () => {
99
- it('comes in, and may act', async () => {
100
- /* The case the inheritance exists for, and the one the specs cover ten times over. */
101
- const person = await inheritedRoleHolder([child])
102
-
103
- expect(await canUserCheckinDomain(person, child)).toBe(true)
104
- expect(await User.hasPrivilege('query', 'board', child, person)).toBe(true)
105
- })
106
- })
107
-
108
- describe('⚠ someone who is not a member but holds the parent role', () => {
109
- it('does not come in', async () => {
110
- /*
111
- * The membership half doing its job. Without it, a role granted inside one child would be
112
- * an entry pass to every sibling of that child.
113
- */
114
- const person = await inheritedRoleHolder([sibling])
115
-
116
- expect(await canUserCheckinDomain(person, child)).toBe(false)
117
- })
118
-
119
- it('⚠ but would be allowed to act, if anything asked', async () => {
120
- /*
121
- * ⚠ **This is the gap, written down.** The privilege layer does not ask about membership, so
122
- * it answers yes for a person the door would have turned away.
123
- *
124
- * It costs nothing today because nothing reaches `hasPrivilege` without the door having run
125
- * first. It is one layer rather than two, and this line is what a second layer would change.
126
- */
127
- const person = await inheritedRoleHolder([sibling])
128
-
129
- expect(await User.hasPrivilege('query', 'board', child, person)).toBe(true)
130
- })
131
-
132
- it('holds for someone with no membership at all', async () => {
133
- /* Not an artefact of being a member of a sibling — belonging nowhere reads the same. */
134
- const person = await inheritedRoleHolder([])
135
-
136
- expect(await canUserCheckinDomain(person, child)).toBe(false)
137
- expect(await User.hasPrivilege('query', 'board', child, person)).toBe(true)
138
- })
139
- })
140
-
141
- describe('what the gap does not extend to', () => {
142
- it('a domain that is not below the role domain', async () => {
143
- /*
144
- * The privilege layer is loose about membership, not about inheritance. A role defined in
145
- * `system` reaches `system` and its children, and `gamma` is neither.
146
- */
147
- const person = await inheritedRoleHolder([outsider])
148
-
149
- expect(await canUserCheckinDomain(person, outsider)).toBe(false)
150
- expect(await User.hasPrivilege('query', 'board', outsider, person)).toBe(false)
151
- })
152
-
153
- it('a privilege the role does not carry', async () => {
154
- const person = await inheritedRoleHolder([sibling])
155
-
156
- expect(await User.hasPrivilege('mutation', 'board', child, person)).toBe(false)
157
- })
158
- })
package/tests/compiled.ts DELETED
@@ -1,41 +0,0 @@
1
- /**
2
- * 게이트 시험이 **컴파일된 산출물**(`dist-server`)을 드는 이유.
3
- *
4
- * 이 패키지의 엔티티는 `@Field({ nullable: true })` 처럼 **명시 타입이 없는** type-graphql
5
- * 데코레이터를 쓴다. 그것을 읽으려면 `emitDecoratorMetadata` 가 필요한데 jest 의 변환기는
6
- * babel 이고, babel 은 tsc 의 그 기능을 대체하지 못한다. 그래서 소스에서 `user.ts` 를 들면
7
- * import 시점에 죽는다:
8
- *
9
- * Unable to infer GraphQL type from TypeScript reflection system.
10
- * at privilege.ts:15 ← role.ts → user.ts 를 타고 올라온다
11
- *
12
- * legacy 데코레이터로 바꾸는 길은 이미 시도했고 되돌렸다(루트 `jest.config.js` 주석).
13
- * 그래서 **엔티티 그래프에 닿는 시험은 tsc 산출물을 든다.** 루트 `pretest` 가 `yarn build` 이므로
14
- * `yarn test` 경로에서는 산출물이 항상 갱신되어 있다.
15
- *
16
- * **대가**: 산출물은 낡을 수 있고, 타입 검사가 이 경계를 넘지 않는다 — `yarn _test` 로 빌드를
17
- * 건너뛰고 돌리면 옛 시그니처를 시험하게 된다. 그래서 아래 로더가 산출물이 없으면
18
- * **무엇을 해야 하는지 말하고** 죽는다(조용한 초록보다 낫다).
19
- *
20
- * 순수 함수(`checkin-domain-rule` · `privilege-rejection` · `privilege-deprecation`)는
21
- * 엔티티에 닿지 않으므로 소스에서 그대로 든다. 이 로더는 그 밖의 것에만 쓴다.
22
- */
23
-
24
- import { existsSync } from 'fs'
25
- import { join } from 'path'
26
-
27
- const DIST = join(__dirname, '..', 'dist-server')
28
-
29
- export function loadCompiled<T = any>(modulePath: string): T {
30
- const resolved = join(DIST, `${modulePath}.js`)
31
-
32
- if (!existsSync(resolved)) {
33
- throw new Error(
34
- `auth-base 산출물이 없다: ${resolved}\n` +
35
- `이 시험은 tsc 산출물을 든다(이유는 tests/compiled.ts). 먼저 빌드할 것:\n` +
36
- ` yarn workspace @things-factory/auth-base build:server`
37
- )
38
- }
39
-
40
- return require(resolved)
41
- }
@@ -1,85 +0,0 @@
1
- /**
2
- * Confirming it is still the signed-in person (ADR-0168) by password, through the real controller on the sqlite
3
- * harness: a miss counts toward the same lock as signing in and leaves no confirmation; a match leaves one; both
4
- * are audited as `auth.confirm-identity`.
5
- */
6
-
7
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase, authDataSource } from './db'
8
- import { loadCompiled } from './compiled'
9
-
10
- const { User } = loadCompiled('service/user/user')
11
- const { AuditAction } = loadCompiled('service/audit/audit-action')
12
- const { flushAudit } = loadCompiled('service/audit/audit-recorder')
13
- const { confirmIdentity } = loadCompiled('controllers/confirm-identity')
14
- const { verifyPassword } = loadCompiled('controllers/signin')
15
- const { isRecentlyConfirmed } = loadCompiled('service/identity-confirmation/identity-confirmation')
16
-
17
- const PASSWORD = 'Passw0rd!'
18
-
19
- let domain: any
20
- let kim: any
21
-
22
- function request(user: any) {
23
- const jar = new Map<string, string>()
24
- return {
25
- state: { user, domain, tx: authDataSource().manager },
26
- cookies: { get: (name: string) => jar.get(name) || undefined, set: (name: string, value: string) => jar.set(name, value) }
27
- } as any
28
- }
29
-
30
- const byPassword = (context: any, password: string) => confirmIdentity('password', context, (user: any) => verifyPassword(user, password, context))
31
-
32
- async function audits() {
33
- await flushAudit()
34
- const rows = await getRepository(AuditAction).find({ where: { kind: 'auth.confirm-identity' }, order: { createdAt: 'ASC' } })
35
- return rows.map(row => [row.outcome, row.code, row.detail?.method])
36
- }
37
-
38
- beforeAll(async () => {
39
- await openAuthDatabase()
40
- })
41
-
42
- afterAll(async () => {
43
- await closeAuthDatabase()
44
- })
45
-
46
- beforeEach(async () => {
47
- await resetAuthDatabase()
48
- domain = await getRepository(Domain).save({ name: 'office', subdomain: 'office' })
49
- const salt = User.generateSalt()
50
- kim = await getRepository(User).save({
51
- username: 'kim',
52
- email: 'kim@example.com',
53
- name: 'kim',
54
- salt,
55
- password: User.encode(PASSWORD, salt),
56
- status: 'activated',
57
- domains: [domain]
58
- })
59
- })
60
-
61
- it('leaves a confirmation for the person when the password matches', async () => {
62
- const context = request(kim)
63
- await byPassword(context, PASSWORD)
64
-
65
- expect(isRecentlyConfirmed(context, kim.id)).toBe(true)
66
- expect(await audits()).toEqual([['ok', null, 'password']])
67
- })
68
-
69
- it('a miss leaves none, counts toward the lock, and is audited with its code', async () => {
70
- const context = request(kim)
71
- await expect(byPassword(context, 'wrong')).rejects.toMatchObject({ errorCode: 'password-not-matched' })
72
-
73
- expect(isRecentlyConfirmed(context, kim.id)).toBe(false)
74
- expect((await getRepository(User).findOneBy({ id: kim.id })).failCount).toBe(1)
75
- expect(await audits()).toEqual([['error', 'password-not-matched', 'password']])
76
- })
77
-
78
- it('the fifth miss locks the account, and a locked account confirms nothing', async () => {
79
- for (let i = 0; i < 4; i++) await expect(byPassword(request(kim), 'wrong')).rejects.toMatchObject({ errorCode: 'password-not-matched' })
80
- await expect(byPassword(request(kim), 'wrong')).rejects.toMatchObject({ errorCode: 'user-locked' })
81
-
82
- const context = request(kim)
83
- await expect(byPassword(context, PASSWORD)).rejects.toMatchObject({ errorCode: 'user-locked' })
84
- expect(isRecentlyConfirmed(context, kim.id)).toBe(false)
85
- })
@@ -1,246 +0,0 @@
1
- import { config } from '@things-factory/env'
2
-
3
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
4
- import { loadCompiled } from './compiled'
5
-
6
- const { Appliance } = loadCompiled('service/appliance/appliance')
7
- const { Application } = loadCompiled('service/application/application')
8
- const { User, UserStatus } = loadCompiled('service/user/user')
9
-
10
- /**
11
- * Storing a credential as ciphertext, on the installations that asked for it (ADR-0085).
12
- *
13
- * ── The three states that have to hold ──────────────────────────────────────
14
- *
15
- * off writes plaintext, reads plaintext an untouched deployment
16
- * on writes ciphertext, reads either the rows convert one at a time
17
- * on then off writes plaintext again, reads either turning it off loses nothing
18
- *
19
- * The third is the one that makes the switch safe to try. If reading followed the switch, a
20
- * deployment that turned it on and then changed its mind would lose every row written while it
21
- * was on — the values would still be in the table and would read as nothing.
22
- *
23
- * These go through the repository rather than calling the transformer, so what is checked is
24
- * what a column actually does. The raw column is read with a second query that bypasses the
25
- * entity, which is the only way to see which shape is on disk.
26
- *
27
- * ⚠ sqlite (`tests/db.ts`).
28
- */
29
-
30
- /** What `shell`'s transformer writes: 32 hex characters of IV, a colon, then hex. */
31
- const CIPHERTEXT = /^[0-9a-f]{32}:(?:[0-9a-f]{2})+$/
32
-
33
- const configGet = config.get
34
-
35
- function encryptionOff() {
36
- config.get = configGet
37
- }
38
-
39
- function encryptionOn() {
40
- config.get = ((key: string, fallback?: any) =>
41
- key === 'credential/encryptAtRest' ? true : configGet(key, fallback)) as typeof config.get
42
- }
43
-
44
- let domain: any
45
-
46
- /** The bytes on disk, read without the entity so the transformer does not run. */
47
- async function storedAccessToken(id: string): Promise<string> {
48
- const [row] = await getRepository(Appliance).query('select access_token from appliances where id = ?', [id])
49
-
50
- return row?.access_token
51
- }
52
-
53
- async function storedAppSecret(id: string): Promise<string> {
54
- const [row] = await getRepository(Application).query('select app_secret from applications where id = ?', [id])
55
-
56
- return row?.app_secret
57
- }
58
-
59
- /** Application declares six columns non-null; only appSecret matters here. */
60
- async function saveApplication(appSecret: string) {
61
- return await getRepository(Application).save({
62
- domain,
63
- name: 'billing',
64
- email: 'billing@acme.z',
65
- url: 'https://billing.example.com',
66
- redirectUrl: 'https://billing.example.com/callback',
67
- status: 0,
68
- type: 'OTHERS',
69
- appSecret
70
- })
71
- }
72
-
73
- async function saveAppliance(name: string, accessToken: string) {
74
- return await getRepository(Appliance).save({
75
- domain,
76
- name,
77
- serialNo: `${name}-SN`,
78
- brand: 'Acme',
79
- model: 'X1',
80
- accessToken
81
- })
82
- }
83
-
84
- beforeAll(async () => {
85
- await openAuthDatabase()
86
- })
87
-
88
- afterAll(async () => {
89
- encryptionOff()
90
- await closeAuthDatabase()
91
- })
92
-
93
- beforeEach(async () => {
94
- encryptionOff()
95
- await resetAuthDatabase()
96
-
97
- domain = await getRepository(Domain).save({ name: 'acme', subdomain: 'acme' })
98
- })
99
-
100
- describe('with the switch off — an installation that configured nothing', () => {
101
- it('stores the token as it always did', async () => {
102
- const saved = await saveAppliance('gate-a', 'plain-token-value')
103
-
104
- expect(await storedAccessToken(saved.id)).toEqual('plain-token-value')
105
- })
106
-
107
- it('reads back what it wrote', async () => {
108
- const saved = await saveAppliance('gate-b', 'plain-token-value')
109
-
110
- expect((await getRepository(Appliance).findOneBy({ id: saved.id })).accessToken).toEqual('plain-token-value')
111
- })
112
-
113
- it('stores an application secret as it always did', async () => {
114
- const saved = await saveApplication('plain-secret')
115
-
116
- expect(await storedAppSecret(saved.id)).toEqual('plain-secret')
117
- expect((await getRepository(Application).findOneBy({ id: saved.id })).appSecret).toEqual('plain-secret')
118
- })
119
- })
120
-
121
- describe('with the switch on', () => {
122
- beforeEach(() => encryptionOn())
123
-
124
- it('stores the token as ciphertext', async () => {
125
- const saved = await saveAppliance('gate-c', 'secret-token-value')
126
- const onDisk = await storedAccessToken(saved.id)
127
-
128
- expect(onDisk).toMatch(CIPHERTEXT)
129
- expect(onDisk).not.toContain('secret-token-value')
130
- })
131
-
132
- it('reads it back as what was written', async () => {
133
- const saved = await saveAppliance('gate-d', 'secret-token-value')
134
-
135
- expect((await getRepository(Appliance).findOneBy({ id: saved.id })).accessToken).toEqual('secret-token-value')
136
- })
137
-
138
- it('stores an application secret as ciphertext', async () => {
139
- const saved = await saveApplication('secret-value')
140
-
141
- expect(await storedAppSecret(saved.id)).toMatch(CIPHERTEXT)
142
- expect((await getRepository(Application).findOneBy({ id: saved.id })).appSecret).toEqual('secret-value')
143
- })
144
-
145
- it('does not make two rows with the same secret look the same at rest', async () => {
146
- /* The IV is per write. Without it, matching rows would say which appliances share a token. */
147
- const a = await saveAppliance('gate-e', 'same-token')
148
- const b = await saveAppliance('gate-f', 'same-token')
149
-
150
- expect(await storedAccessToken(a.id)).not.toEqual(await storedAccessToken(b.id))
151
- })
152
- })
153
-
154
- describe('turning it on over rows that are already there', () => {
155
- it('still reads a row written before the switch', async () => {
156
- /*
157
- * ⚠ The case that lets the switch be turned on at all. Every row in the table is plaintext
158
- * on the day it goes on. If reading them answered nothing, the credentials would vanish from
159
- * the screens while sitting in the table.
160
- */
161
- const saved = await saveAppliance('gate-g', 'written-before')
162
-
163
- encryptionOn()
164
-
165
- expect((await getRepository(Appliance).findOneBy({ id: saved.id })).accessToken).toEqual('written-before')
166
- expect(await storedAccessToken(saved.id)).toEqual('written-before')
167
- })
168
-
169
- it('converts that row the next time it is written', async () => {
170
- /* One row at a time, as each is touched. There is no pass over the table. */
171
- const saved = await saveAppliance('gate-h', 'written-before')
172
-
173
- encryptionOn()
174
- await getRepository(Appliance).save({ ...saved, accessToken: 'written-after' })
175
-
176
- expect(await storedAccessToken(saved.id)).toMatch(CIPHERTEXT)
177
- expect((await getRepository(Appliance).findOneBy({ id: saved.id })).accessToken).toEqual('written-after')
178
- })
179
-
180
- it('leaves untouched rows alone', async () => {
181
- const converted = await saveAppliance('gate-i', 'one')
182
- const untouched = await saveAppliance('gate-j', 'two')
183
-
184
- encryptionOn()
185
- await getRepository(Appliance).save({ ...converted, accessToken: 'one-again' })
186
-
187
- expect(await storedAccessToken(converted.id)).toMatch(CIPHERTEXT)
188
- expect(await storedAccessToken(untouched.id)).toEqual('two')
189
-
190
- /* And both still read. That is the state a half-converted installation is in. */
191
- expect((await getRepository(Appliance).findOneBy({ id: converted.id })).accessToken).toEqual('one-again')
192
- expect((await getRepository(Appliance).findOneBy({ id: untouched.id })).accessToken).toEqual('two')
193
- })
194
- })
195
-
196
- describe('turning it off after it has been on', () => {
197
- it('keeps reading the rows written while it was on', async () => {
198
- /*
199
- * ⚠ What makes the switch safe to try. The switch governs writing only — if it governed
200
- * reading, turning it off would strand every row written while it was on.
201
- */
202
- encryptionOn()
203
- const saved = await saveAppliance('gate-k', 'written-while-on')
204
-
205
- expect(await storedAccessToken(saved.id)).toMatch(CIPHERTEXT)
206
-
207
- encryptionOff()
208
-
209
- expect((await getRepository(Appliance).findOneBy({ id: saved.id })).accessToken).toEqual('written-while-on')
210
- })
211
-
212
- it('writes plaintext again from then on', async () => {
213
- encryptionOn()
214
- const saved = await saveAppliance('gate-l', 'written-while-on')
215
-
216
- encryptionOff()
217
- await getRepository(Appliance).save({ ...saved, accessToken: 'written-while-off' })
218
-
219
- expect(await storedAccessToken(saved.id)).toEqual('written-while-off')
220
- })
221
- })
222
-
223
- describe('what this does not touch', () => {
224
- it('leaves User.password alone', async () => {
225
- /*
226
- * ⚠ Deliberate, and the reason is in `credential-at-rest.ts`. That column holds an HMAC for a
227
- * person and a token for a machine, and a column transformer cannot tell them apart.
228
- * Encrypting everybody's password hash to reach the machines' tokens is the wrong trade; the
229
- * machine credential needs a column of its own (§8 step 4).
230
- */
231
- encryptionOn()
232
-
233
- const user = await getRepository(User).save({
234
- username: 'operator@acme.z',
235
- email: 'operator@acme.z',
236
- name: 'operator',
237
- status: UserStatus.ACTIVATED,
238
- password: 'hashed-value',
239
- domains: [domain]
240
- })
241
-
242
- const [row] = await getRepository(User).query('select password from users where id = ?', [user.id])
243
-
244
- expect(row.password).toEqual('hashed-value')
245
- })
246
- })