@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,190 +0,0 @@
1
- /**
2
- * Deleting an application has to stop the clients bound to it.
3
- *
4
- * ── The asymmetry this came from ────────────────────────────────────────────
5
- * An appliance and an application both have a shadow `User` standing in for the machine, and
6
- * that user is the thing a token names. `deleteAppliance` deletes it along with the appliance.
7
- * `deleteApplication` deleted only the application row, and the shadow users stayed behind:
8
- *
9
- * ACTIVATED, so `User.checkAuth` passes every token naming them — it looks the user up by the
10
- * id in the token and never asks whether the application still exists
11
- *
12
- * holding a refresh token in `password`, which is what `/oauth2/refresh-token` finds a binding
13
- * by, so each refresh mints another year of them
14
- *
15
- * So the screen said the application was gone and its clients kept working, with no way left on
16
- * any screen to reach them — the list they would have appeared in is keyed on an application
17
- * that no longer exists.
18
- *
19
- * ⚠ sqlite (`tests/db.ts`).
20
- */
21
-
22
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
23
- import { loadCompiled } from './compiled'
24
-
25
- const { Application } = loadCompiled('service/application/application')
26
- const { User, UserStatus } = loadCompiled('service/user/user')
27
- const { ApplicationMutation } = loadCompiled('service/application/application-mutation')
28
-
29
- const mutation = new ApplicationMutation()
30
-
31
- let domain: any
32
- let other: any
33
- let actor: any
34
-
35
- function context(d: any = domain) {
36
- return {
37
- state: { domain: d, user: actor, tx: undefined },
38
- throw: (code: number, message: string) => {
39
- const error: any = new Error(message)
40
- error.status = code
41
- throw error
42
- }
43
- } as any
44
- }
45
-
46
- async function makeApplication(name: string, d: any = domain) {
47
- return await mutation.createApplication(
48
- {
49
- name,
50
- email: `${name}@acme.z`,
51
- url: `https://${name}.example.com`,
52
- redirectUrl: `https://${name}.example.com/callback`,
53
- type: 'OTHERS'
54
- },
55
- context(d)
56
- )
57
- }
58
-
59
- /** A binding, as the OAuth2 exchange leaves one: a shadow user holding a refresh token. */
60
- async function bind(application: any, label: string, d: any = domain) {
61
- const appuser = await getRepository(User).save({
62
- username: `${label}@acme`,
63
- email: `${label}@acme`,
64
- name: label,
65
- userType: 'application',
66
- reference: application.id,
67
- status: UserStatus.ACTIVATED,
68
- domains: [d]
69
- })
70
-
71
- const refreshToken = Application.generateRefreshToken(d, appuser, application, 'admin')
72
-
73
- return await getRepository(User).save({ ...appuser, password: refreshToken })
74
- }
75
-
76
- async function bindingsOf(applicationId: string) {
77
- return await getRepository(User).findBy({ reference: applicationId, userType: 'application' })
78
- }
79
-
80
- beforeAll(async () => {
81
- await openAuthDatabase()
82
- })
83
-
84
- afterAll(async () => {
85
- await closeAuthDatabase()
86
- })
87
-
88
- beforeEach(async () => {
89
- await resetAuthDatabase()
90
-
91
- domain = await getRepository(Domain).save({ name: 'acme', subdomain: 'acme' })
92
- other = await getRepository(Domain).save({ name: 'beta', subdomain: 'beta' })
93
- actor = await getRepository(User).save({
94
- username: 'operator@acme.z',
95
- email: 'operator@acme.z',
96
- name: 'operator',
97
- status: UserStatus.ACTIVATED,
98
- domains: [domain]
99
- })
100
- })
101
-
102
- describe('deleting an application', () => {
103
- it('takes its bindings with it', async () => {
104
- /*
105
- * ⚠ The case. A binding left behind is a live credential with no screen to reach it from —
106
- * the list it would appear in is keyed on an application that is gone.
107
- */
108
- const application = await makeApplication('billing')
109
- await bind(application, 'binding-a')
110
- await bind(application, 'binding-b')
111
-
112
- await mutation.deleteApplication(application.id, context())
113
-
114
- expect(await bindingsOf(application.id)).toHaveLength(0)
115
- })
116
-
117
- it('leaves no token that checkAuth would still accept', async () => {
118
- /*
119
- * ⚠ Why the row mattering is not enough to say. `checkAuth` looks the subject up by the id
120
- * baked into the token and never asks whether the application exists, so a surviving user
121
- * authenticates whatever happened to the application row.
122
- */
123
- const application = await makeApplication('billing')
124
- const binding = await bind(application, 'binding-a')
125
-
126
- await mutation.deleteApplication(application.id, context())
127
-
128
- await expect(
129
- User.checkAuth({ id: binding.id, userType: 'application', domain: { subdomain: 'acme' } })
130
- ).rejects.toThrow()
131
- })
132
-
133
- it('deletes the application row itself', async () => {
134
- const application = await makeApplication('billing')
135
-
136
- await mutation.deleteApplication(application.id, context())
137
-
138
- expect(await getRepository(Application).findOneBy({ id: application.id })).toBeNull()
139
- })
140
-
141
- it('leaves another application and its bindings alone', async () => {
142
- const going = await makeApplication('billing')
143
- const staying = await makeApplication('shipping')
144
-
145
- await bind(going, 'binding-a')
146
- const kept = await bind(staying, 'binding-b')
147
-
148
- await mutation.deleteApplication(going.id, context())
149
-
150
- expect(await bindingsOf(staying.id)).toHaveLength(1)
151
- expect(await getRepository(User).findOneBy({ id: kept.id })).toMatchObject({ status: UserStatus.ACTIVATED })
152
- })
153
-
154
- it('does not reach into another tenant', async () => {
155
- /*
156
- * The delete is scoped by domain, so an id from elsewhere matches nothing. The bindings must
157
- * not be cleared on the way to finding that out.
158
- */
159
- const theirs = await makeApplication('billing', other)
160
- await bind(theirs, 'binding-theirs', other)
161
-
162
- await mutation.deleteApplication(theirs.id, context(domain))
163
-
164
- expect(await getRepository(Application).findOneBy({ id: theirs.id })).not.toBeNull()
165
- expect(await bindingsOf(theirs.id)).toHaveLength(1)
166
- })
167
-
168
- it('does not touch an appliance that happens to carry the same reference', async () => {
169
- /*
170
- * `reference` is a plain column shared by both kinds of shadow user, so the delete has to
171
- * name `userType` as well. Two ids never collide in practice; the assertion is about the
172
- * filter, not about the odds.
173
- */
174
- const application = await makeApplication('billing')
175
-
176
- const applianceUser = await getRepository(User).save({
177
- username: 'device@acme',
178
- email: 'device@acme',
179
- name: 'device',
180
- userType: 'appliance',
181
- reference: application.id,
182
- status: UserStatus.ACTIVATED,
183
- domains: [domain]
184
- })
185
-
186
- await mutation.deleteApplication(application.id, context())
187
-
188
- expect(await getRepository(User).findOneBy({ id: applianceUser.id })).not.toBeNull()
189
- })
190
- })
@@ -1,185 +0,0 @@
1
- /**
2
- * What an application's tokens carry in their payload.
3
- *
4
- * ── Why this is worth a file of its own ─────────────────────────────────────
5
- * An application has two 32-character hex strings and they are opposite kinds of thing:
6
- *
7
- * appKey the client id. Public. It is what a token says it was issued to, and what the
8
- * OAuth2 refresh exchange looks the application up by.
9
- * appSecret the client secret. It is what `/oauth2/access-token` compares against to decide
10
- * the caller is that client.
11
- *
12
- * `Application.sign` takes the first in a positional slot, and a JWT payload is not encrypted —
13
- * anyone holding the token reads it. So putting the second one in that slot hands the client
14
- * secret to whoever holds the token, and nothing about the call site looks wrong at a glance:
15
- * both values are strings on the same object.
16
- *
17
- * That is exactly what `renewApplicationAccessToken` did. These cases pin the axis the fix
18
- * splits — what the payload names — rather than the shape of the call.
19
- *
20
- * ── What these do not cover ─────────────────────────────────────────────────
21
- * The tokens issued before the fix. They carry the secret and they keep carrying it, and the
22
- * decision is to leave them (2026-09-20 사용자 결정: 이미 발행된 토큰은 무시한다). So there is
23
- * no audit here and no rule for finding them — only that nothing new goes out that way.
24
- *
25
- * ⚠ sqlite (`tests/db.ts`).
26
- */
27
-
28
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
29
- import { loadCompiled } from './compiled'
30
-
31
- const jwt = require('jsonwebtoken')
32
-
33
- const { Application } = loadCompiled('service/application/application')
34
- const { User, UserStatus } = loadCompiled('service/user/user')
35
- const { ApplicationMutation } = loadCompiled('service/application/application-mutation')
36
-
37
- const mutation = new ApplicationMutation()
38
-
39
- let domain: any
40
- let actor: any
41
-
42
- function context(d: any = domain) {
43
- return {
44
- state: { domain: d, user: actor, tx: undefined },
45
- throw: (code: number, message: string) => {
46
- const error: any = new Error(message)
47
- error.status = code
48
- throw error
49
- }
50
- } as any
51
- }
52
-
53
- /** An application plus the shadow user a binding is, which the OAuth2 exchange creates. */
54
- async function bindApplication(name: string) {
55
- const application = await mutation.createApplication(
56
- {
57
- name,
58
- email: `${name}@acme.z`,
59
- url: `https://${name}.example.com`,
60
- redirectUrl: `https://${name}.example.com/callback`,
61
- type: 'OTHERS'
62
- },
63
- context()
64
- )
65
-
66
- const appuser = await getRepository(User).save({
67
- username: `${application.appKey}@acme`,
68
- email: `${application.appKey}@acme`,
69
- name,
70
- userType: 'application',
71
- reference: application.id,
72
- status: UserStatus.ACTIVATED,
73
- domains: [domain]
74
- })
75
-
76
- return { application, appuser }
77
- }
78
-
79
- beforeAll(async () => {
80
- await openAuthDatabase()
81
- })
82
-
83
- afterAll(async () => {
84
- await closeAuthDatabase()
85
- })
86
-
87
- beforeEach(async () => {
88
- await resetAuthDatabase()
89
-
90
- domain = await getRepository(Domain).save({ name: 'acme', subdomain: 'acme' })
91
- actor = await getRepository(User).save({
92
- username: 'operator@acme.z',
93
- email: 'operator@acme.z',
94
- name: 'operator',
95
- status: UserStatus.ACTIVATED,
96
- domains: [domain]
97
- })
98
- })
99
-
100
- describe('the two strings are not interchangeable', () => {
101
- it('gives an application a key and a secret that differ', async () => {
102
- /* If these were ever the same value the rest of this file would pass for the wrong reason. */
103
- const { application } = await bindApplication('billing')
104
-
105
- expect(application.appKey).toBeTruthy()
106
- expect(application.appSecret).toBeTruthy()
107
- expect(application.appKey).not.toEqual(application.appSecret)
108
- })
109
- })
110
-
111
- describe('renewing an application access token', () => {
112
- it('names the application by its key', async () => {
113
- const { application, appuser } = await bindApplication('billing')
114
-
115
- const { accessToken } = await mutation.renewApplicationAccessToken(appuser.id, context(), 'admin')
116
-
117
- expect(jwt.decode(accessToken).application.appKey).toEqual(application.appKey)
118
- })
119
-
120
- it('does not put the client secret in the access token', async () => {
121
- /*
122
- * ⚠ The defect. A JWT payload is base64, not encryption — whoever holds the token reads it.
123
- * With the secret and the key, which is public, a holder can call `/oauth2/access-token` as
124
- * that client.
125
- */
126
- const { application, appuser } = await bindApplication('billing')
127
-
128
- const { accessToken } = await mutation.renewApplicationAccessToken(appuser.id, context(), 'admin')
129
-
130
- expect(JSON.stringify(jwt.decode(accessToken))).not.toContain(application.appSecret)
131
- })
132
-
133
- it('does not put it in the refresh token either', async () => {
134
- /*
135
- * ⚠ The longer-lived half. The access token is 30 days; this one is a year, and it is the
136
- * one handed to the client to keep.
137
- */
138
- const { application, appuser } = await bindApplication('billing')
139
-
140
- const { refreshToken } = await mutation.renewApplicationAccessToken(appuser.id, context(), 'admin')
141
-
142
- expect(jwt.decode(refreshToken).application.appKey).toEqual(application.appKey)
143
- expect(JSON.stringify(jwt.decode(refreshToken))).not.toContain(application.appSecret)
144
- })
145
-
146
- it('does not leave it in the row it stores', async () => {
147
- /*
148
- * The refresh token is saved on the shadow user's `password`, so a payload carrying the
149
- * secret writes the secret into a second place as well.
150
- */
151
- const { application, appuser } = await bindApplication('billing')
152
-
153
- await mutation.renewApplicationAccessToken(appuser.id, context(), 'admin')
154
-
155
- const stored = await getRepository(User).findOneBy({ id: appuser.id })
156
-
157
- expect(stored.password).toBeTruthy()
158
- expect(JSON.stringify(jwt.decode(stored.password))).not.toContain(application.appSecret)
159
- })
160
-
161
- it('mints a refresh token the OAuth2 exchange can resolve back to the application', async () => {
162
- /*
163
- * ⚠ The second face of the same defect, and the one an operator would notice. The refresh
164
- * exchange reads `application.appKey` out of the token and does `findOneBy({ appKey })`
165
- * (`router/oauth2/oauth2-server.ts`). A payload naming the secret finds no row, so the
166
- * exchange answers "application is not exist" and the client cannot refresh at all.
167
- */
168
- const { application, appuser } = await bindApplication('billing')
169
-
170
- const { refreshToken } = await mutation.renewApplicationAccessToken(appuser.id, context(), 'admin')
171
- const named = jwt.decode(refreshToken).application.appKey
172
-
173
- expect(await getRepository(Application).findOneBy({ appKey: named })).toMatchObject({ id: application.id })
174
- })
175
-
176
- it('does not name one application with the key of another', async () => {
177
- const a = await bindApplication('billing')
178
- const b = await bindApplication('shipping')
179
-
180
- const renewed = await mutation.renewApplicationAccessToken(a.appuser.id, context(), 'admin')
181
-
182
- expect(jwt.decode(renewed.accessToken).application.appKey).toEqual(a.application.appKey)
183
- expect(jwt.decode(renewed.accessToken).application.appKey).not.toEqual(b.application.appKey)
184
- })
185
- })
@@ -1,219 +0,0 @@
1
- /**
2
- * Putting a person into roles - one function, the domain's scope, ids found again (ADR-0103 decision 2).
3
- *
4
- * `updateUserRoles` concatenated the client's `selectedRoles` onto the user as sent and removed every held role
5
- * whose id was in `availableRoles`. A role of another tenant went in; another tenant's role id in
6
- * `availableRoles` took that role away from the person in the other domain. The access request grants through
7
- * the same function, so both doors are held here.
8
- */
9
-
10
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
11
- import { loadCompiled } from './compiled'
12
-
13
- const { Role } = loadCompiled('service/role/role')
14
- const { GrantedRole } = loadCompiled('service/granted-role/granted-role')
15
- const { User } = loadCompiled('service/user/user')
16
- const { UserMutation } = loadCompiled('service/user/user-mutation')
17
- const { assignRoles } = loadCompiled('utils/assignable-roles')
18
- const { DomainOwner } = loadCompiled('service/domain-owner/domain-owner')
19
- const { Privilege } = loadCompiled('service/privilege/privilege')
20
-
21
- let parent: any
22
- let child: any
23
- let stranger: any
24
-
25
- async function loadDomain(id: string) {
26
- return await getRepository(Domain).findOneBy({ id })
27
- }
28
-
29
- async function makeUser(domains: any[], roles: any[]) {
30
- const tag = Math.random().toString(36).slice(2, 8)
31
- return await getRepository(User).save({
32
- username: `u-${tag}`,
33
- email: `u-${tag}@example.com`,
34
- name: 'Kim',
35
- salt: User.generateSalt(),
36
- status: 'activated',
37
- domains,
38
- roles
39
- })
40
- }
41
-
42
- async function heldRoleNames(user: any) {
43
- const read = await getRepository(User).findOne({ where: { id: user.id }, relations: ['roles'] })
44
- return read.roles.map((role: any) => role.name).sort()
45
- }
46
-
47
- function updateUserRoles(user: any, available: any[], selected: any[], domain: any) {
48
- return new UserMutation().updateUserRoles(
49
- user.username,
50
- available.map(role => ({ id: role.id })),
51
- selected.map(role => ({ id: role.id })),
52
- { state: { domain }, t: (key: string) => key } as any
53
- )
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 domainRepository = getRepository(Domain)
68
- const savedParent = await domainRepository.save({ name: 'HQ', subdomain: 'hq' })
69
- const savedChild = await domainRepository.save({ name: 'Plant A', subdomain: 'plant-a', parent: savedParent })
70
- const savedStranger = await domainRepository.save({ name: 'Other', subdomain: 'other' })
71
-
72
- parent = await loadDomain(savedParent.id)
73
- child = await loadDomain(savedChild.id)
74
- stranger = await loadDomain(savedStranger.id)
75
- })
76
-
77
- describe('what a domain can put a person into', () => {
78
- it('its own role, its parent role and a role granted to it - all three', async () => {
79
- const own = await getRepository(Role).save({ name: 'operator', domain: child })
80
- const inherited = await getRepository(Role).save({ name: 'viewer', domain: parent })
81
- const granted = await getRepository(Role).save({ name: 'partner', domain: stranger })
82
- await getRepository(GrantedRole).save({ role: granted, domain: child })
83
- const user = await makeUser([child], [])
84
-
85
- const parentOwner = await makeUser([parent], [])
86
- await getRepository(DomainOwner).save({ domain: parent, user: parentOwner })
87
- const { added } = await assignRoles(user, { add: [own.id, inherited.id, granted.id] }, child, undefined, parentOwner)
88
-
89
- expect(added.map((role: any) => role.name).sort()).toEqual(['operator', 'partner', 'viewer'])
90
- expect(await heldRoleNames(user)).toEqual(['operator', 'partner', 'viewer'])
91
- })
92
-
93
- it('★ another tenant\'s role is refused by name, and nothing is saved', async () => {
94
- const own = await getRepository(Role).save({ name: 'operator', domain: child })
95
- const foreign = await getRepository(Role).save({ name: 'auditor', domain: stranger })
96
- const user = await makeUser([child], [])
97
-
98
- await expect(assignRoles(user, { add: [own.id, foreign.id] }, child)).rejects.toMatchObject({
99
- code: 'role-not-assignable-here',
100
- params: { roles: foreign.id }
101
- })
102
- expect(await heldRoleNames(user)).toEqual([])
103
- })
104
-
105
- it('a role already held is not added twice and is not reported as added', async () => {
106
- const own = await getRepository(Role).save({ name: 'operator', domain: child })
107
- const user = await makeUser([child], [own])
108
-
109
- const { added } = await assignRoles(user, { add: [own.id] }, child)
110
-
111
- expect(added).toEqual([])
112
- expect(await heldRoleNames(user)).toEqual(['operator'])
113
- })
114
- })
115
-
116
- describe('updateUserRoles goes through the same function', () => {
117
- it('★ a foreign role in selectedRoles is refused - it used to be attached as sent', async () => {
118
- const foreign = await getRepository(Role).save({ name: 'auditor', domain: stranger })
119
- const user = await makeUser([child], [])
120
-
121
- /* The method is called directly, so the Refusal arrives before SpeakRefusals turns it into a sentence. */
122
- await expect(updateUserRoles(user, [], [foreign], child)).rejects.toMatchObject({ code: 'role-not-assignable-here' })
123
- expect(await heldRoleNames(user)).toEqual([])
124
- })
125
-
126
- it('★ a foreign role in availableRoles stays with the person - this domain cannot take it', async () => {
127
- const own = await getRepository(Role).save({ name: 'operator', domain: child })
128
- const theirs = await getRepository(Role).save({ name: 'auditor', domain: stranger })
129
- const user = await makeUser([child, stranger], [own, theirs])
130
-
131
- await updateUserRoles(user, [own, theirs], [], child)
132
-
133
- expect(await heldRoleNames(user)).toEqual(['auditor'])
134
- })
135
-
136
- it('within the scope it replaces as before - remove the shown, add the chosen', async () => {
137
- const a = await getRepository(Role).save({ name: 'a', domain: child })
138
- const b = await getRepository(Role).save({ name: 'b', domain: child })
139
- const user = await makeUser([child], [a])
140
-
141
- await updateUserRoles(user, [a, b], [b], child)
142
-
143
- expect(await heldRoleNames(user)).toEqual(['b'])
144
- })
145
- })
146
-
147
- describe('the parent domain\'s roles - only someone who decides there (ADR-0103 decision 8)', () => {
148
- let parentRole: any
149
- let childAdmin: any
150
- let user: any
151
-
152
- beforeEach(async () => {
153
- const grant = await getRepository(Privilege).save({ name: 'mutation', category: 'user' })
154
- const childAdmins = await getRepository(Role).save({ name: 'child-admins', domain: child, privileges: [grant] })
155
- parentRole = await getRepository(Role).save({ name: 'hq-approver', domain: parent })
156
- childAdmin = await makeUser([child], [childAdmins])
157
- user = await makeUser([child], [])
158
- })
159
-
160
- it('★ a child domain\'s decider cannot give the parent\'s role - refused by name', async () => {
161
- await expect(assignRoles(user, { add: [parentRole.id] }, child, undefined, childAdmin)).rejects.toMatchObject({
162
- code: 'role-belongs-to-parent',
163
- params: { roles: 'hq-approver' }
164
- })
165
- expect(await heldRoleNames(user)).toEqual([])
166
- })
167
-
168
- it('★ nor take it away', async () => {
169
- const holder = await makeUser([child], [parentRole])
170
- await expect(assignRoles(holder, { remove: [parentRole.id] }, child, undefined, childAdmin)).rejects.toMatchObject({ code: 'role-belongs-to-parent' })
171
- expect(await heldRoleNames(holder)).toEqual(['hq-approver'])
172
- })
173
-
174
- it('★ the parent\'s owner can - from the child', async () => {
175
- const parentOwner = await makeUser([parent], [])
176
- await getRepository(DomainOwner).save({ domain: parent, user: parentOwner })
177
- await assignRoles(user, { add: [parentRole.id] }, child, undefined, parentOwner)
178
- expect(await heldRoleNames(user)).toEqual(['hq-approver'])
179
- })
180
-
181
- it('★ a holder of the role-giving privilege in the parent can', async () => {
182
- const grant = await getRepository(Privilege).findOneBy({ name: 'mutation', category: 'user' })
183
- const hqAdmins = await getRepository(Role).save({ name: 'hq-admins', domain: parent, privileges: [grant] })
184
- const hqAdmin = await makeUser([parent, child], [hqAdmins])
185
- await assignRoles(user, { add: [parentRole.id] }, child, undefined, hqAdmin)
186
- expect(await heldRoleNames(user)).toEqual(['hq-approver'])
187
- })
188
-
189
- it('a parent role left as it was is no change - the user screen sends every shown role, nothing is refused', async () => {
190
- const own = await getRepository(Role).save({ name: 'operator', domain: child })
191
- const holder = await makeUser([child], [parentRole])
192
- await assignRoles(holder, { add: [parentRole.id, own.id], remove: [parentRole.id, own.id] }, child, undefined, childAdmin)
193
- expect(await heldRoleNames(holder)).toEqual(['hq-approver', 'operator'])
194
- })
195
-
196
- it('★ updateUserRoles carries the caller - a child admin adding the parent\'s role is refused', async () => {
197
- const mutation = new UserMutation()
198
- await expect(
199
- mutation.updateUserRoles(user.username, [{ id: parentRole.id }], [{ id: parentRole.id }], { state: { domain: child, user: childAdmin }, t: (k: string) => k } as any)
200
- ).rejects.toMatchObject({ code: 'role-belongs-to-parent' })
201
- })
202
-
203
- it('★ updateUserRoles carries the caller - the parent\'s owner adding it from the child passes', async () => {
204
- const parentOwner = await makeUser([parent, child], [])
205
- await getRepository(DomainOwner).save({ domain: parent, user: parentOwner })
206
- await new UserMutation().updateUserRoles(user.username, [{ id: parentRole.id }], [{ id: parentRole.id }], {
207
- state: { domain: child, user: parentOwner },
208
- t: (k: string) => k
209
- } as any)
210
- expect(await heldRoleNames(user)).toEqual(['hq-approver'])
211
- })
212
-
213
- it('a granted role is the granting domain\'s delegation - a child decider assigns it', async () => {
214
- const partnerRole = await getRepository(Role).save({ name: 'partner', domain: stranger })
215
- await getRepository(GrantedRole).save({ role: partnerRole, domain: child })
216
- await assignRoles(user, { add: [partnerRole.id] }, child, undefined, childAdmin)
217
- expect(await heldRoleNames(user)).toEqual(['partner'])
218
- })
219
- })