@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,119 +0,0 @@
1
- /**
2
- * Deleting an appliance stays inside the tenant that asked.
3
- *
4
- * ── The order the deletes happen in ─────────────────────────────────────────
5
- * An appliance is two rows: the appliance itself, which carries a domain, and a shadow `User`
6
- * standing in for the device, which is found by `reference` and carries no tenant of its own.
7
- *
8
- * `deleteAppliance` deleted the shadow user first and scoped only the appliance row afterwards.
9
- * So an id belonging to somebody else deleted their device's identity and then failed to delete
10
- * their appliance — the row stayed on their screen looking registered, and the device behind it
11
- * stopped at the next request, because `checkAuth` looks the subject up by the id in the token
12
- * and there was no longer a subject. Nothing in the answer said so: the mutation returns true.
13
- *
14
- * ⚠ sqlite (`tests/db.ts`).
15
- */
16
-
17
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
18
- import { loadCompiled } from './compiled'
19
-
20
- const { Appliance } = loadCompiled('service/appliance/appliance')
21
- const { User, UserStatus } = loadCompiled('service/user/user')
22
- const { ApplianceMutation } = loadCompiled('service/appliance/appliance-mutation')
23
-
24
- const mutation = new ApplianceMutation()
25
-
26
- let ours: any
27
- let theirs: any
28
- let actor: any
29
-
30
- function context(d: any) {
31
- return {
32
- state: { domain: d, user: actor, tx: undefined },
33
- throw: (code: number, message: string) => {
34
- const error: any = new Error(message)
35
- error.status = code
36
- throw error
37
- }
38
- } as any
39
- }
40
-
41
- async function register(name: string, d: any) {
42
- return await mutation.createAppliance({ name, serialNo: `${name}-SN`, brand: 'Acme', model: 'X1' }, context(d))
43
- }
44
-
45
- async function shadowUserOf(applianceId: string) {
46
- return await getRepository(User).findOneBy({ reference: applianceId, userType: 'appliance' })
47
- }
48
-
49
- beforeAll(async () => {
50
- await openAuthDatabase()
51
- })
52
-
53
- afterAll(async () => {
54
- await closeAuthDatabase()
55
- })
56
-
57
- beforeEach(async () => {
58
- await resetAuthDatabase()
59
-
60
- ours = await getRepository(Domain).save({ name: 'acme', subdomain: 'acme' })
61
- theirs = await getRepository(Domain).save({ name: 'beta', subdomain: 'beta' })
62
- actor = await getRepository(User).save({
63
- username: 'operator@acme.z',
64
- email: 'operator@acme.z',
65
- name: 'operator',
66
- status: UserStatus.ACTIVATED,
67
- domains: [ours]
68
- })
69
- })
70
-
71
- describe('deleting our own appliance', () => {
72
- it('takes the appliance and its shadow user', async () => {
73
- const appliance = await register('gate-a', ours)
74
- await mutation.generateApplianceSecret(appliance.id, context(ours))
75
-
76
- await mutation.deleteAppliance(appliance.id, context(ours))
77
-
78
- expect(await getRepository(Appliance).findOneBy({ id: appliance.id })).toBeNull()
79
- expect(await shadowUserOf(appliance.id)).toBeNull()
80
- })
81
- })
82
-
83
- describe('an id that belongs to another tenant', () => {
84
- it('leaves their appliance where it is', async () => {
85
- const foreign = await register('their-gate', theirs)
86
-
87
- await mutation.deleteAppliance(foreign.id, context(ours))
88
-
89
- expect(await getRepository(Appliance).findOneBy({ id: foreign.id })).not.toBeNull()
90
- })
91
-
92
- it('leaves their device able to authenticate', async () => {
93
- /*
94
- * ⚠ The case. Deleting the shadow user before checking the tenant stopped somebody else's
95
- * device while leaving their appliance row on screen looking registered — no screen would
96
- * show what happened, and the fix from their side is to issue a new secret for a device
97
- * that had a working one.
98
- */
99
- const foreign = await register('their-gate', theirs)
100
- const issued = await mutation.generateApplianceSecret(foreign.id, context(theirs))
101
-
102
- await mutation.deleteAppliance(foreign.id, context(ours))
103
-
104
- expect(await shadowUserOf(foreign.id)).not.toBeNull()
105
-
106
- const jwt = require('jsonwebtoken')
107
- await expect(User.checkAuth(jwt.decode(issued.accessToken))).resolves.toBeTruthy()
108
- })
109
-
110
- it('does not take ours down with it either way', async () => {
111
- const mine = await register('gate-a', ours)
112
- const foreign = await register('their-gate', theirs)
113
-
114
- await mutation.deleteAppliance(foreign.id, context(ours))
115
-
116
- expect(await getRepository(Appliance).findOneBy({ id: mine.id })).not.toBeNull()
117
- expect(await shadowUserOf(mine.id)).not.toBeNull()
118
- })
119
- })
@@ -1,294 +0,0 @@
1
- /**
2
- * `revokeApplianceCredential` — taking a device's access away while keeping the device.
3
- *
4
- * ── What was missing ────────────────────────────────────────────────────────
5
- * There was no way to leave an appliance without a working credential. You could delete the
6
- * row, which takes its history with it, or issue a new secret, which puts a **live** credential
7
- * in the database and on the screen. Neither is revocation.
8
- *
9
- * ── Why the status is the switch, not the field ─────────────────────────────
10
- * The access token is a signed JWT. Clearing `Appliance.accessToken` drops our copy and does
11
- * nothing to the token already on the device — it keeps verifying against SECRET until its own
12
- * `exp`, a year out. `User.checkAuth` is what refuses it, and it refuses on the shadow user's
13
- * status. So these tests assert the status; the cleared fields are tidiness, and one case says
14
- * so explicitly because the opposite is easy to assume.
15
- *
16
- * ⚠ sqlite (`tests/db.ts`). Enum columns fall to the varchar branch here, so this pins the
17
- * value that is stored and read back, not the dialect's own enum behaviour.
18
- */
19
-
20
- import { config } from '@things-factory/env'
21
-
22
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
23
- import { loadCompiled } from './compiled'
24
-
25
- const jwt = require('jsonwebtoken')
26
-
27
- const { Appliance } = loadCompiled('service/appliance/appliance')
28
- const { User, UserStatus } = loadCompiled('service/user/user')
29
- const { ApplianceMutation } = loadCompiled('service/appliance/appliance-mutation')
30
-
31
- const mutation = new ApplianceMutation()
32
-
33
- let domain: any
34
- let actor: any
35
-
36
- function context(d: any = domain) {
37
- return {
38
- state: { domain: d, user: actor, tx: undefined },
39
- throw: (code: number, message: string) => {
40
- const error: any = new Error(message)
41
- error.status = code
42
- throw error
43
- }
44
- } as any
45
- }
46
-
47
- async function shadowUserOf(applianceId: string) {
48
- return await getRepository(User).findOneBy({ reference: applianceId, userType: 'appliance' })
49
- }
50
-
51
- async function makeAppliance(name: string) {
52
- const appliance = await mutation.createAppliance(
53
- { name, serialNo: `${name}-SN`, brand: 'Acme', model: 'X1' },
54
- context()
55
- )
56
-
57
- /* Registering does not issue a token; the screen shows "not issued" until someone asks. */
58
- return await mutation.generateApplianceSecret(appliance.id, context())
59
- }
60
-
61
- /*
62
- * Revocation is off unless the installation asks for it (ADR-0085 decision 2), so these cases
63
- * turn it on. `credential-features.ts` reads the config on each call, which is what lets a test
64
- * stand in for a configured installation.
65
- *
66
- * `credential-features-off.test.ts` holds the other side — what an installation that configures
67
- * nothing sees.
68
- */
69
- const configGet = config.get
70
-
71
- beforeAll(async () => {
72
- config.get = ((key: string, fallback?: any) =>
73
- key === 'credential/revocable' ? true : configGet(key, fallback)) as typeof config.get
74
-
75
- await openAuthDatabase()
76
- })
77
-
78
- afterAll(async () => {
79
- config.get = configGet
80
- await closeAuthDatabase()
81
- })
82
-
83
- beforeEach(async () => {
84
- await resetAuthDatabase()
85
-
86
- domain = await getRepository(Domain).save({ name: 'acme', subdomain: 'acme' })
87
- actor = await getRepository(User).save({
88
- username: 'operator@acme.z',
89
- email: 'operator@acme.z',
90
- name: 'operator',
91
- status: UserStatus.ACTIVATED,
92
- domains: [domain]
93
- })
94
- })
95
-
96
- describe('revoking an appliance credential', () => {
97
- it('leaves the appliance, and leaves it with nothing to authenticate with', async () => {
98
- const issued = await makeAppliance('gate-a')
99
-
100
- expect(issued.accessToken).toBeTruthy()
101
- expect((await shadowUserOf(issued.id))!.status).toEqual(UserStatus.ACTIVATED)
102
-
103
- const revoked = await mutation.revokeApplianceCredential(issued.id, context())
104
-
105
- /* The record survives — that is the whole difference from deleteAppliance. */
106
- expect(revoked.id).toEqual(issued.id)
107
- expect(revoked.name).toEqual('gate-a')
108
- expect(await getRepository(Appliance).findOneBy({ id: issued.id })).toBeTruthy()
109
-
110
- expect(revoked.accessToken).toBeNull()
111
- })
112
-
113
- it('turns the shadow user inactive — the switch every request reads', async () => {
114
- /*
115
- * ⚠ The case the feature exists for. `checkAuth` throws USER_NOT_ACTIVATED on INACTIVE, so
116
- * this is what refuses the token that is already out on the device. Without it the cleared
117
- * field means nothing: the JWT verifies on its signature alone.
118
- */
119
- const issued = await makeAppliance('gate-b')
120
-
121
- await mutation.revokeApplianceCredential(issued.id, context())
122
-
123
- const appuser = await shadowUserOf(issued.id)
124
-
125
- expect(appuser).toBeTruthy()
126
- expect(appuser!.status).toEqual(UserStatus.INACTIVE)
127
- })
128
-
129
- it('moves the credential serial, which is what a status alone cannot do', async () => {
130
- /*
131
- * ⚠ The serial is the half of revocation that cannot be undone by putting the status back.
132
- * Its effect shows up in `checkAuth` (ADR-0085 decision 4), so until that lands nothing else
133
- * here would notice if the bump went missing — this case watches the column directly.
134
- */
135
- const issued = await makeAppliance('gate-s')
136
- const before = (await shadowUserOf(issued.id))!.credentialSerial
137
-
138
- expect(before).toEqual(1)
139
-
140
- await mutation.revokeApplianceCredential(issued.id, context())
141
-
142
- expect((await shadowUserOf(issued.id))!.credentialSerial).toEqual(before + 1)
143
- })
144
-
145
- it('moves it again on the next issue, so no generation is reused', async () => {
146
- const issued = await makeAppliance('gate-t')
147
-
148
- await mutation.revokeApplianceCredential(issued.id, context())
149
- await mutation.generateApplianceSecret(issued.id, context())
150
-
151
- expect((await shadowUserOf(issued.id))!.credentialSerial).toEqual(3)
152
- })
153
-
154
- it('keeps the shadow user rather than deleting it', async () => {
155
- /*
156
- * `deleteAppliance` deletes it, and that is right when the appliance goes too. Here the
157
- * appliance stays, so the identity has to stay with it — past rows point at it as creator
158
- * and updater, and re-issuing keeps the same id rather than orphaning them.
159
- */
160
- const issued = await makeAppliance('gate-c')
161
- const before = await shadowUserOf(issued.id)
162
-
163
- await mutation.revokeApplianceCredential(issued.id, context())
164
-
165
- const after = await shadowUserOf(issued.id)
166
-
167
- expect(after).toBeTruthy()
168
- expect(after!.id).toEqual(before!.id)
169
- })
170
-
171
- it('stops holding a copy of the credential it just killed', async () => {
172
- /*
173
- * Tidiness, not a control — and worth saying so, because the opposite is easy to believe.
174
- * The JWT verifies on its signature, never against this column, so clearing it does not
175
- * stop the token on the device. The status does that.
176
- *
177
- * ⚠ Which leaves a real hazard this does NOT close: put the status back to ACTIVATED
178
- * without issuing a new secret and the old token authenticates again, cleared column or
179
- * not. Nothing here prevents that. Closing it needs the token itself to be refusable —
180
- * a jti denylist, or re-issuing as part of reactivation.
181
- */
182
- const issued = await makeAppliance('gate-d')
183
-
184
- expect((await shadowUserOf(issued.id))!.password).toBeTruthy()
185
-
186
- await mutation.revokeApplianceCredential(issued.id, context())
187
-
188
- expect((await shadowUserOf(issued.id))!.password).toBeNull()
189
- })
190
-
191
- it('lets a new secret work afterwards', async () => {
192
- /*
193
- * A revoked appliance is INACTIVE, and `checkAuth` refuses that whatever the token says. So
194
- * issuing again has to put the status back, or the new credential is dead on arrival — a
195
- * failure that would look like the token being wrong.
196
- */
197
- const issued = await makeAppliance('gate-e')
198
- await mutation.revokeApplianceCredential(issued.id, context())
199
-
200
- const reissued = await mutation.generateApplianceSecret(issued.id, context())
201
-
202
- expect(reissued.accessToken).toBeTruthy()
203
-
204
- const appuser = await shadowUserOf(issued.id)
205
-
206
- expect(appuser!.status).toEqual(UserStatus.ACTIVATED)
207
- expect(appuser!.password).toEqual(reissued.accessToken)
208
- })
209
-
210
- it('does not hand back the token it just revoked, even re-issued in the same second', async () => {
211
- /*
212
- * This used to pin a defect. The payload carried `{ id, userType, appliance.id, status,
213
- * domain.subdomain }` and nothing else, and `iat` counts whole seconds, so two signings
214
- * inside one second produced a byte-identical string — revoking and immediately re-issuing,
215
- * which is the obvious way to rotate a suspect device, handed back the credential just
216
- * revoked.
217
- *
218
- * The credential serial closes it: it moves on every issue and every revoke, and it is in
219
- * the payload, so no two issues can produce the same token.
220
- */
221
- const issued = await makeAppliance('gate-g')
222
-
223
- await mutation.revokeApplianceCredential(issued.id, context())
224
- const reissued = await mutation.generateApplianceSecret(issued.id, context())
225
-
226
- expect(reissued.accessToken).not.toEqual(issued.accessToken)
227
-
228
- /* And the reason is the serial, not the clock — the two tokens name different generations. */
229
- expect(jwt.decode(reissued.accessToken).cs).toBeGreaterThan(jwt.decode(issued.accessToken).cs)
230
- })
231
-
232
- it('refuses an appliance that belongs to another domain', async () => {
233
- /*
234
- * Revocation stops a device. Reaching across a tenant boundary with it would let one
235
- * customer turn off another's equipment.
236
- */
237
- const issued = await makeAppliance('gate-f')
238
- const stranger = await getRepository(Domain).save({ name: 'other', subdomain: 'other' })
239
-
240
- await expect(mutation.revokeApplianceCredential(issued.id, context(stranger))).rejects.toThrow()
241
-
242
- const appuser = await shadowUserOf(issued.id)
243
-
244
- expect(appuser!.status).toEqual(UserStatus.ACTIVATED)
245
- })
246
- })
247
-
248
- describe('what an already-issued token can still do', () => {
249
- /*
250
- * The token is a signed JWT. `checkAuth` is the only thing that looks at our records once the
251
- * signature verifies, so these cases run the real token through the real function rather than
252
- * reasoning about it.
253
- */
254
- function decode(token: string) {
255
- return jwt.decode(token)
256
- }
257
-
258
- it('is refused while the appliance is revoked', async () => {
259
- const issued = await makeAppliance('gate-h')
260
-
261
- await expect(User.checkAuth(decode(issued.accessToken))).resolves.toBeTruthy()
262
-
263
- await mutation.revokeApplianceCredential(issued.id, context())
264
-
265
- await expect(User.checkAuth(decode(issued.accessToken))).rejects.toThrow()
266
- })
267
-
268
- it('stays refused when the status goes back, because the serial moved', async () => {
269
- /*
270
- * This used to pin a hazard. Revocation lived entirely in the shadow user's status, so
271
- * putting the status back — by hand, by a fixture, by some future screen — let every token
272
- * ever issued to that appliance work again. Clearing the stored copy did not help: the JWT
273
- * verifies on its signature and never against that column.
274
- *
275
- * The serial closes it. It moved on revoke and does not move back, so the old token names a
276
- * generation this subject has left. Access can only come from issuing a new one.
277
- */
278
- const issued = await makeAppliance('gate-i')
279
- const oldToken = issued.accessToken
280
-
281
- await mutation.revokeApplianceCredential(issued.id, context())
282
- await expect(User.checkAuth(decode(oldToken))).rejects.toThrow()
283
-
284
- const appuser = await shadowUserOf(issued.id)
285
- await getRepository(User).save({ ...appuser, status: UserStatus.ACTIVATED })
286
-
287
- await expect(User.checkAuth(decode(oldToken))).rejects.toThrow()
288
-
289
- /* And a freshly issued one does work — the subject is usable again, its old tokens are not. */
290
- const reissued = await mutation.generateApplianceSecret(issued.id, context())
291
-
292
- await expect(User.checkAuth(decode(reissued.accessToken))).resolves.toBeTruthy()
293
- })
294
- })
@@ -1,129 +0,0 @@
1
- /**
2
- * The appliance resolvers make a schema, and the two new fields carry the gate they meant to.
3
- *
4
- * ── Why a schema test and not another unit test ─────────────────────────────
5
- * `credentialRevoked` is a field with no column behind it and `applianceCredentialRevocable` is
6
- * a Query on a resolver whose other methods all return objects. Both are the shapes that build
7
- * fine in TypeScript and fail when type-graphql reads the decorators — an inferred type it
8
- * cannot resolve takes the whole schema down, and a schema that does not build is a server that
9
- * comes up without its doors (`reference_resolver_load_failure_leaves_server_half_up`).
10
- *
11
- * ── What the gate assertion is for ──────────────────────────────────────────
12
- * `applianceCredentialRevocable` exists so a screen can ask "would pressing revoke work for
13
- * me". That answer is only worth anything if the query is refused in the same cases the
14
- * mutation is, so the two declarations have to stay identical. Nothing else would notice them
15
- * drifting apart: the query would simply start answering people who cannot act on it, and the
16
- * button would appear and then refuse.
17
- */
18
-
19
- import { buildSchema } from 'type-graphql'
20
- import { GraphQLSchema } from 'graphql'
21
-
22
- import { loadCompiled } from './compiled'
23
-
24
- const { ApplianceQuery } = loadCompiled('service/appliance/appliance-query')
25
- const { ApplianceMutation } = loadCompiled('service/appliance/appliance-mutation')
26
- const { ApplicationQuery } = loadCompiled('service/application/application-query')
27
- const { ApplicationMutation } = loadCompiled('service/application/application-mutation')
28
- const { AppBindingQuery } = loadCompiled('service/app-binding/app-binding-query')
29
- const { AppBindingMutation } = loadCompiled('service/app-binding/app-binding-mutation')
30
- const { privilegeDirectiveResolver, privilegeOfField } = loadCompiled('service/privilege/privilege-directive')
31
-
32
- let schema: GraphQLSchema
33
-
34
- beforeAll(async () => {
35
- schema = await buildSchema({
36
- resolvers: [
37
- ApplianceQuery,
38
- ApplianceMutation,
39
- ApplicationQuery,
40
- ApplicationMutation,
41
- AppBindingQuery,
42
- AppBindingMutation
43
- ],
44
- validate: false
45
- })
46
-
47
- /* The map of what each field asks for is filled in while the directive walks the schema. */
48
- privilegeDirectiveResolver(schema)
49
- })
50
-
51
- describe('the schema builds', () => {
52
- it('has the query the screen asks before showing a revoke button', () => {
53
- const field = schema.getQueryType()?.getFields()['applianceCredentialRevocable']
54
-
55
- expect(field).toBeDefined()
56
-
57
- /* Non-null: it always has an answer, and "no answer" would read as "no button". */
58
- expect(String(field!.type)).toEqual('Boolean!')
59
- })
60
-
61
- it('has the field that separates revoked from never issued', () => {
62
- const field = schema.getType('Appliance')?.['getFields']()['credentialRevoked']
63
-
64
- expect(field).toBeDefined()
65
-
66
- /*
67
- * Nullable, unlike the query above. This field rides on the Appliance type, which is also
68
- * what the mutations hand back, and a caller that does not ask for it should get a record
69
- * without it rather than one asserting the credential is live. The screen reads absent as
70
- * not revoked — `client/utils/credential-panel-state.ts` in auth-ui pins that.
71
- */
72
- expect(String(field.type)).toEqual('Boolean')
73
- })
74
-
75
- it('still has the door those two describe', () => {
76
- expect(schema.getMutationType()?.getFields()['revokeApplianceCredential']).toBeDefined()
77
- })
78
- })
79
-
80
- describe('the question is gated like the act it describes', () => {
81
- it('asks for exactly what revoking asks for', () => {
82
- /*
83
- * ⚠ The pair that has to stay together. If the query opened up, it would answer yes to a
84
- * reader the mutation refuses, and the screen would put a button in front of somebody who
85
- * cannot press it.
86
- */
87
- const asking = privilegeOfField('Query', 'applianceCredentialRevocable')
88
- const acting = privilegeOfField('Mutation', 'revokeApplianceCredential')
89
-
90
- expect(asking.known).toBe(true)
91
- expect(acting.known).toBe(true)
92
- expect(asking['gate']).toEqual(acting['gate'])
93
- })
94
-
95
- it('is not an open field', () => {
96
- /* `null` is how the map records a root field that declares nothing. */
97
- expect(privilegeOfField('Query', 'applianceCredentialRevocable')['gate']).not.toBeNull()
98
- })
99
- })
100
-
101
- describe('no door on a machine credential is left open', () => {
102
- /*
103
- * ⚠ The sweep this file exists for as much as for the two fields above. `deleteAppBinding`
104
- * carried no privilege at all, and nothing noticed: an undeclared root field is recorded as
105
- * open, so it reads exactly like a field somebody decided to open. The two app binding reads
106
- * were the same.
107
- *
108
- * These three resources are one family — an appliance, an application, and a client bound to
109
- * one — and every way in or out of them is a machine credential. So the assertion is on the
110
- * whole family rather than on the doors that happen to be remembered.
111
- */
112
-
113
- /** Every root field these six resolvers put in the schema. */
114
- function rootFields(typeName: string): string[] {
115
- const type = typeName === 'Query' ? schema.getQueryType() : schema.getMutationType()
116
-
117
- return Object.keys(type?.getFields() || {})
118
- }
119
-
120
- it.each(['Query', 'Mutation'])('%s', typeName => {
121
- const open = rootFields(typeName).filter(field => {
122
- const declared = privilegeOfField(typeName, field)
123
-
124
- return declared.known && declared['gate'] === null
125
- })
126
-
127
- expect(open).toEqual([])
128
- })
129
- })