@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,34 +0,0 @@
1
- import { buildSchema } from 'type-graphql'
2
-
3
- import { loadCompiled } from './compiled'
4
-
5
- /**
6
- * The questions the user popups ask are gated as the actions they lead to.
7
- *
8
- * `checkUserExistence` is asked only by the add-user popup, before `inviteUser` / `createUser`
9
- * (each `user:mutation`, owner granted). It asked `user:query` alone, so a domain owner without that role was refused
10
- * the lookup and the popup took the refusal for "no account" - and set out to create an account for someone who
11
- * had one. `mayIssuePasswordSetupLink` answers for the setup link button, so it stands where `issuePasswordSetupLink` does.
12
- */
13
- const { schema: declared } = loadCompiled('service/index')
14
- const { privilegeDirectiveResolver, privilegeOfField } = loadCompiled('service/privilege/privilege-directive')
15
-
16
- function gateOf(field: string) {
17
- const gate = privilegeOfField('Query', field)
18
- return [gate.known, gate['gate']?.category, gate['gate']?.privilege, gate['gate']?.owner]
19
- }
20
-
21
- describe('what the user popups ask', () => {
22
- beforeAll(async () => {
23
- const schema = await buildSchema({ resolvers: declared.resolverClasses, validate: false })
24
- privilegeDirectiveResolver(schema, { modules: [] })
25
- })
26
-
27
- it('★ checkUserExistence asks what adding a user asks - user:mutation, or the owner', () => {
28
- expect(gateOf('checkUserExistence')).toEqual([true, 'user', 'mutation', true])
29
- })
30
-
31
- it('mayIssuePasswordSetupLink asks what issuing asks', () => {
32
- expect(gateOf('mayIssuePasswordSetupLink')).toEqual([true, 'user', 'mutation', true])
33
- })
34
- })
@@ -1,210 +0,0 @@
1
- /**
2
- * App binding lookups — against a real database.
3
- *
4
- * Both defects here are invisible to a pure test. One is about which tenant's rows come
5
- * back; the other is about the order paging and filtering happen in, which only shows up
6
- * once there are more rows than fit on a page. Neither can be seen by reading the function.
7
- */
8
-
9
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
10
- import { loadCompiled } from './compiled'
11
-
12
- const { User } = loadCompiled('service/user/user')
13
- const { AppBindingQuery } = loadCompiled('service/app-binding/app-binding-query')
14
-
15
- const query = new AppBindingQuery()
16
-
17
- let ours: any
18
- let theirs: any
19
-
20
- function context(domain: any) {
21
- return { state: { domain } } as any
22
- }
23
-
24
- async function makeUser(domain: any, username: string, userType: string) {
25
- return await getRepository(User).save({
26
- username,
27
- email: `${username}@example.com`,
28
- name: username,
29
- userType,
30
- salt: User.generateSalt(),
31
- status: 'activated',
32
- domains: [domain]
33
- })
34
- }
35
-
36
- beforeAll(async () => {
37
- await openAuthDatabase()
38
- })
39
-
40
- afterAll(async () => {
41
- await closeAuthDatabase()
42
- })
43
-
44
- beforeEach(async () => {
45
- await resetAuthDatabase()
46
-
47
- const a = await getRepository(Domain).save({ name: 'Plant A', subdomain: 'plant-a' })
48
- const b = await getRepository(Domain).save({ name: 'Plant B', subdomain: 'plant-b' })
49
-
50
- ours = await getRepository(Domain).findOneBy({ id: a.id })
51
- theirs = await getRepository(Domain).findOneBy({ id: b.id })
52
- })
53
-
54
- describe('one binding', () => {
55
- it('answers for a binding in this domain', async () => {
56
- const mine = await makeUser(ours, 'app-mine', 'application')
57
-
58
- const found = await query.appBinding(mine.id, context(ours))
59
-
60
- expect(found?.id).toBe(mine.id)
61
- })
62
-
63
- it('does not answer for a binding in another domain', async () => {
64
- /*
65
- * ⚠ The isolation case. The resolver read `domain` out of the context and never used
66
- * it — `findOneBy({ id, userType })` searched every tenant, and a comment beside it
67
- * said "TODO should check domain is available". An id from anywhere returned that
68
- * application user's name, email and reference.
69
- */
70
- const foreign = await makeUser(theirs, 'app-theirs', 'application')
71
-
72
- const found = await query.appBinding(foreign.id, context(ours))
73
-
74
- expect(found).toBeFalsy()
75
- })
76
-
77
- it('answers the same for a foreign id as for one that does not exist', async () => {
78
- /*
79
- * Deliberate: a different answer would confirm that some id is real somewhere else.
80
- */
81
- const foreign = await makeUser(theirs, 'app-theirs', 'application')
82
-
83
- const foreignAnswer = await query.appBinding(foreign.id, context(ours))
84
- const nonsenseAnswer = await query.appBinding('00000000-0000-4000-8000-000000000000', context(ours))
85
-
86
- expect(foreignAnswer).toEqual(nonsenseAnswer)
87
- })
88
-
89
- it('does not answer for a person, only for an application', async () => {
90
- const person = await makeUser(ours, 'someone', 'user')
91
-
92
- expect(await query.appBinding(person.id, context(ours))).toBeFalsy()
93
- })
94
- })
95
-
96
- describe('the list', () => {
97
- it('returns only the applications of this domain', async () => {
98
- await makeUser(ours, 'app-one', 'application')
99
- await makeUser(ours, 'a-person', 'user')
100
- await makeUser(theirs, 'app-theirs', 'application')
101
-
102
- const { items, total } = await query.appBindings({ filters: [] } as any, context(ours))
103
-
104
- expect(items.map((one: any) => one.username)).toEqual(['app-one'])
105
- expect(total).toBe(1)
106
- })
107
-
108
- it('counts every match, not the ones on this page', async () => {
109
- /*
110
- * ⚠ The paging case, and it needs rows to show at all.
111
- *
112
- * The filter used to run in JavaScript on whatever the paged query returned, and the
113
- * total was `items.length` — the survivors of that one page. A grid asking for page 1
114
- * of 2 was told the total was 2, so it never offered page 2 and the remaining bindings
115
- * could not be reached from the screen at all.
116
- */
117
- for (let i = 0; i < 5; i++) {
118
- await makeUser(ours, `app-${i}`, 'application')
119
- }
120
-
121
- const { items, total } = await query.appBindings(
122
- { filters: [], pagination: { page: 1, limit: 2 } } as any,
123
- context(ours)
124
- )
125
-
126
- expect(items).toHaveLength(2)
127
- expect(total).toBe(5)
128
- })
129
-
130
- it('fills a page with matches rather than with whatever the page happened to hold', async () => {
131
- /*
132
- * The other half of the same defect. Interleaving people and applications used to leave
133
- * a page of two carrying one row, or none, depending on the order rows came back.
134
- */
135
- for (let i = 0; i < 4; i++) {
136
- await makeUser(ours, `person-${i}`, 'user')
137
- await makeUser(ours, `app-${i}`, 'application')
138
- }
139
-
140
- const { items, total } = await query.appBindings(
141
- { filters: [], pagination: { page: 1, limit: 3 } } as any,
142
- context(ours)
143
- )
144
-
145
- expect(items).toHaveLength(3)
146
- expect(items.every((one: any) => one.userType === 'application')).toBe(true)
147
- expect(total).toBe(4)
148
- })
149
-
150
- it('reaches the last page', async () => {
151
- for (let i = 0; i < 5; i++) {
152
- await makeUser(ours, `app-${i}`, 'application')
153
- }
154
-
155
- const { items } = await query.appBindings(
156
- { filters: [], pagination: { page: 3, limit: 2 } } as any,
157
- context(ours)
158
- )
159
-
160
- expect(items).toHaveLength(1)
161
- })
162
- })
163
-
164
- describe('the free-text term', () => {
165
- /*
166
- * The screen now sends the term itself (`ListParam.search`) and lets the server say where it
167
- * looks. This door builds with the older `buildQuery`, which only understood search *filters* -
168
- * so a term that arrived here was dropped, and the whole list came back as if it were the answer.
169
- * A person searching for a binding's name read that as "not here".
170
- */
171
- it('narrows the list to the bindings that match', async () => {
172
- await makeUser(ours, 'erp-bridge', 'application')
173
- await makeUser(ours, 'label-printer', 'application')
174
- await makeUser(ours, 'erp-archive', 'application')
175
-
176
- const { items, total } = await query.appBindings({ search: 'erp' } as any, context(ours))
177
-
178
- expect(items.map((one: any) => one.username).sort()).toEqual(['erp-archive', 'erp-bridge'])
179
- expect(total).toBe(2)
180
- })
181
-
182
- it('looks in the email too', async () => {
183
- await makeUser(ours, 'scanner-1', 'application')
184
- await makeUser(ours, 'scanner-2', 'application')
185
-
186
- const { items } = await query.appBindings({ search: 'scanner-2@example' } as any, context(ours))
187
-
188
- expect(items.map((one: any) => one.username)).toEqual(['scanner-2'])
189
- })
190
-
191
- it('still answers only for this domain when a term is given', async () => {
192
- /* A term must not widen the tenant: the OR of the search sits inside the domain condition. */
193
- await makeUser(ours, 'erp-ours', 'application')
194
- await makeUser(theirs, 'erp-theirs', 'application')
195
-
196
- const { items } = await query.appBindings({ search: 'erp' } as any, context(ours))
197
-
198
- expect(items.map((one: any) => one.username)).toEqual(['erp-ours'])
199
- })
200
-
201
- it('an empty term is no term', async () => {
202
- await makeUser(ours, 'app-one', 'application')
203
- await makeUser(ours, 'app-two', 'application')
204
-
205
- const { total } = await query.appBindings({ search: ' ' } as any, context(ours))
206
-
207
- expect(total).toBe(2)
208
- })
209
- })
210
-
@@ -1,188 +0,0 @@
1
- /**
2
- * `deleteAppBinding` — cutting one client off without touching the application.
3
- *
4
- * ── What the door was ───────────────────────────────────────────────────────
5
- * It read the domain out of the context, used it for nothing, and ran
6
- *
7
- * getRepository(User).delete({ id })
8
- *
9
- * That criteria names neither the kind of row nor the tenant. `AppBinding` is a `User` wearing
10
- * a type, so every account in the installation is the same kind of row as the one this was for:
11
- * a person, a domain owner, an appliance's shadow user, another tenant's binding. Any id at all
12
- * deleted that row and answered true.
13
- *
14
- * It carried no `@privilege` either, so the schema recorded it as open and anyone holding a
15
- * valid token could reach it.
16
- *
17
- * ── Why revocation is a delete here ─────────────────────────────────────────
18
- * An appliance keeps its identity when its credential is taken away, because past rows point at
19
- * it as creator and updater and a new secret should land on the same subject. A binding is not
20
- * like that: it is created by the OAuth2 exchange whenever a client is granted access, and the
21
- * grant is the only thing it records. Deleting it is the revocation — there is no second state
22
- * for it to be in (ADR-0055).
23
- *
24
- * ⚠ sqlite (`tests/db.ts`).
25
- */
26
-
27
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
28
- import { loadCompiled } from './compiled'
29
-
30
- const { Application } = loadCompiled('service/application/application')
31
- const { User, UserStatus } = loadCompiled('service/user/user')
32
- const { AppBindingMutation } = loadCompiled('service/app-binding/app-binding-mutation')
33
- const { ApplicationMutation } = loadCompiled('service/application/application-mutation')
34
-
35
- const mutation = new AppBindingMutation()
36
- const applications = new ApplicationMutation()
37
-
38
- let ours: any
39
- let theirs: any
40
- let actor: any
41
-
42
- function context(d: any) {
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
- async function makeApplication(name: string, d: any) {
54
- return await applications.createApplication(
55
- {
56
- name,
57
- email: `${name}@acme.z`,
58
- url: `https://${name}.example.com`,
59
- redirectUrl: `https://${name}.example.com/callback`,
60
- type: 'OTHERS'
61
- },
62
- context(d)
63
- )
64
- }
65
-
66
- async function bind(application: any, label: string, d: any) {
67
- return await getRepository(User).save({
68
- username: `${label}@acme`,
69
- email: `${label}@acme`,
70
- name: label,
71
- userType: 'application',
72
- reference: application.id,
73
- status: UserStatus.ACTIVATED,
74
- domains: [d]
75
- })
76
- }
77
-
78
- async function stillThere(id: string) {
79
- return !!(await getRepository(User).findOneBy({ id }))
80
- }
81
-
82
- beforeAll(async () => {
83
- await openAuthDatabase()
84
- })
85
-
86
- afterAll(async () => {
87
- await closeAuthDatabase()
88
- })
89
-
90
- beforeEach(async () => {
91
- await resetAuthDatabase()
92
-
93
- ours = await getRepository(Domain).save({ name: 'acme', subdomain: 'acme' })
94
- theirs = await getRepository(Domain).save({ name: 'beta', subdomain: 'beta' })
95
- actor = await getRepository(User).save({
96
- username: 'operator@acme.z',
97
- email: 'operator@acme.z',
98
- name: 'operator',
99
- status: UserStatus.ACTIVATED,
100
- domains: [ours]
101
- })
102
- })
103
-
104
- describe('cutting off a binding of ours', () => {
105
- it('deletes it', async () => {
106
- const application = await makeApplication('billing', ours)
107
- const binding = await bind(application, 'binding-a', ours)
108
-
109
- await mutation.deleteAppBinding(binding.id, context(ours))
110
-
111
- expect(await stillThere(binding.id)).toBe(false)
112
- })
113
-
114
- it('leaves the application and its other bindings alone', async () => {
115
- /* Revoking one client is not deleting the application. */
116
- const application = await makeApplication('billing', ours)
117
- const going = await bind(application, 'binding-a', ours)
118
- const staying = await bind(application, 'binding-b', ours)
119
-
120
- await mutation.deleteAppBinding(going.id, context(ours))
121
-
122
- expect(await stillThere(staying.id)).toBe(true)
123
- expect(await getRepository(Application).findOneBy({ id: application.id })).not.toBeNull()
124
- })
125
- })
126
-
127
- describe('an id that is not a binding of ours', () => {
128
- it('does not delete a person', async () => {
129
- /*
130
- * ⚠ The worst of these. A person's account is the same kind of row as a binding, so
131
- * `delete({ id })` took it with the same answer — and the caller did not have to be anyone
132
- * in particular, because the mutation carried no privilege at all.
133
- */
134
- const person = await getRepository(User).save({
135
- username: 'kim@acme.z',
136
- email: 'kim@acme.z',
137
- name: 'kim',
138
- status: UserStatus.ACTIVATED,
139
- domains: [ours]
140
- })
141
-
142
- await mutation.deleteAppBinding(person.id, context(ours))
143
-
144
- expect(await stillThere(person.id)).toBe(true)
145
- })
146
-
147
- it('does not delete an appliance shadow user', async () => {
148
- /*
149
- * ⚠ Deleting one stops the device at its next request and leaves the appliance on screen
150
- * looking registered — the same end state as the appliance delete leak, reached from here.
151
- */
152
- const applianceUser = await getRepository(User).save({
153
- username: 'device@acme',
154
- email: 'device@acme',
155
- name: 'device',
156
- userType: 'appliance',
157
- reference: 'some-appliance-id',
158
- status: UserStatus.ACTIVATED,
159
- domains: [ours]
160
- })
161
-
162
- await mutation.deleteAppBinding(applianceUser.id, context(ours))
163
-
164
- expect(await stillThere(applianceUser.id)).toBe(true)
165
- })
166
-
167
- it('does not delete another tenant s binding', async () => {
168
- /* ⚠ The tenant boundary. `appBinding` already refuses to read one; this deleted it. */
169
- const application = await makeApplication('billing', theirs)
170
- const foreign = await bind(application, 'binding-theirs', theirs)
171
-
172
- await mutation.deleteAppBinding(foreign.id, context(ours))
173
-
174
- expect(await stillThere(foreign.id)).toBe(true)
175
- })
176
-
177
- it('answers the same whether the id was ours, theirs, or nothing', async () => {
178
- /*
179
- * Telling them apart would confirm that some id is real somewhere else — the reason
180
- * `appBinding` answers nothing for both cases.
181
- */
182
- const application = await makeApplication('billing', theirs)
183
- const foreign = await bind(application, 'binding-theirs', theirs)
184
-
185
- await expect(mutation.deleteAppBinding(foreign.id, context(ours))).resolves.toBe(true)
186
- await expect(mutation.deleteAppBinding('no-such-id', context(ours))).resolves.toBe(true)
187
- })
188
- })
@@ -1,207 +0,0 @@
1
- /**
2
- * What the screen is allowed to ask about a credential (ADR-0085, the revocation screen).
3
- *
4
- * Two questions, and they are not the same one:
5
- *
6
- * applianceCredentialRevocable does revoking work here at all?
7
- * Appliance.credentialRevoked has this one been revoked?
8
- *
9
- * ── Why the first exists ────────────────────────────────────────────────────
10
- * Revocation is behind `credential/revocable`, and the client cannot read configuration. Without
11
- * an answer the screen either hides a feature the installation asked for or shows a button that
12
- * always refuses — and a button that always refuses reads as a broken product rather than as
13
- * something nobody turned on.
14
- *
15
- * ── Why the second exists ───────────────────────────────────────────────────
16
- * Revoking clears `accessToken`, so a revoked appliance and one that was never issued a
17
- * credential look identical on screen: an empty box. They are opposite situations. The state
18
- * that tells them apart is on the shadow user, not on the appliance row, so the field has to be
19
- * resolved rather than read.
20
- *
21
- * ⚠ sqlite (`tests/db.ts`).
22
- */
23
-
24
- import { config } from '@things-factory/env'
25
-
26
- import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
27
- import { loadCompiled } from './compiled'
28
-
29
- const { Appliance } = loadCompiled('service/appliance/appliance')
30
- const { User, UserStatus } = loadCompiled('service/user/user')
31
- const { ApplianceMutation } = loadCompiled('service/appliance/appliance-mutation')
32
- const { ApplianceQuery } = loadCompiled('service/appliance/appliance-query')
33
-
34
- const mutation = new ApplianceMutation()
35
- const query = new ApplianceQuery()
36
-
37
- const configGet = config.get
38
-
39
- function revocationOff() {
40
- config.get = configGet
41
- }
42
-
43
- function revocationOn() {
44
- config.get = ((key: string, fallback?: any) =>
45
- key === 'credential/revocable' ? true : configGet(key, fallback)) as typeof config.get
46
- }
47
-
48
- let domain: any
49
- let actor: any
50
-
51
- function context(d: any = domain) {
52
- return {
53
- state: { domain: d, user: actor, tx: undefined },
54
- throw: (code: number, message: string) => {
55
- const error: any = new Error(message)
56
- error.status = code
57
- throw error
58
- }
59
- } as any
60
- }
61
-
62
- async function register(name: string) {
63
- return await mutation.createAppliance({ name, serialNo: `${name}-SN`, brand: 'Acme', model: 'X1' }, context())
64
- }
65
-
66
- beforeAll(async () => {
67
- await openAuthDatabase()
68
- })
69
-
70
- afterAll(async () => {
71
- revocationOff()
72
- await closeAuthDatabase()
73
- })
74
-
75
- beforeEach(async () => {
76
- revocationOff()
77
- await resetAuthDatabase()
78
-
79
- domain = await getRepository(Domain).save({ name: 'acme', subdomain: 'acme' })
80
- actor = await getRepository(User).save({
81
- username: 'operator@acme.z',
82
- email: 'operator@acme.z',
83
- name: 'operator',
84
- status: UserStatus.ACTIVATED,
85
- domains: [domain]
86
- })
87
- })
88
-
89
- describe('does revoking work here', () => {
90
- it('says no where the installation configured nothing', async () => {
91
- /*
92
- * ⚠ The default. An installation that takes this release and changes no configuration must
93
- * not grow a button, because the mutation behind it answers 404.
94
- */
95
- expect(await query.applianceCredentialRevocable()).toBe(false)
96
- })
97
-
98
- it('says yes once the installation turns it on', async () => {
99
- revocationOn()
100
-
101
- expect(await query.applianceCredentialRevocable()).toBe(true)
102
- })
103
-
104
- it('answers the same thing the door does', async () => {
105
- /*
106
- * ⚠ The two have to move together. If the answer said yes while the mutation refused, the
107
- * screen would offer an action that cannot be completed — which is the exact thing this
108
- * query exists to prevent.
109
- */
110
- const appliance = await register('gate-a')
111
-
112
- expect(await query.applianceCredentialRevocable()).toBe(false)
113
- await expect(mutation.revokeApplianceCredential(appliance.id, context())).rejects.toThrow(/not enabled/)
114
-
115
- revocationOn()
116
-
117
- expect(await query.applianceCredentialRevocable()).toBe(true)
118
- await expect(mutation.revokeApplianceCredential(appliance.id, context())).resolves.toBeTruthy()
119
- })
120
- })
121
-
122
- describe('has this one been revoked', () => {
123
- it('says no for an appliance nobody has issued a credential for', async () => {
124
- /*
125
- * ⚠ The case the whole field is for. This appliance and a revoked one both show an empty
126
- * access token; only this answer separates "waiting to be set up" from "deliberately shut
127
- * off".
128
- */
129
- const appliance = await register('gate-b')
130
-
131
- expect(await query.credentialRevoked(appliance)).toBe(false)
132
- })
133
-
134
- it('says no for one holding a working credential', async () => {
135
- const issued = await mutation.generateApplianceSecret((await register('gate-c')).id, context())
136
-
137
- expect(await query.credentialRevoked(issued)).toBe(false)
138
- })
139
-
140
- it('says yes once it has been revoked', async () => {
141
- revocationOn()
142
-
143
- const issued = await mutation.generateApplianceSecret((await register('gate-d')).id, context())
144
- const revoked = await mutation.revokeApplianceCredential(issued.id, context())
145
-
146
- expect(await query.credentialRevoked(revoked)).toBe(true)
147
- })
148
-
149
- it('says no again after a new credential is issued', async () => {
150
- /*
151
- * Re-issuing puts the shadow user back to ACTIVATED, which is what makes the new token
152
- * usable. The screen has to follow, or a working appliance keeps saying it was revoked.
153
- */
154
- revocationOn()
155
-
156
- const issued = await mutation.generateApplianceSecret((await register('gate-e')).id, context())
157
- await mutation.revokeApplianceCredential(issued.id, context())
158
- const reissued = await mutation.generateApplianceSecret(issued.id, context())
159
-
160
- expect(await query.credentialRevoked(reissued)).toBe(false)
161
- })
162
-
163
- it('does not read one appliance state onto another', async () => {
164
- revocationOn()
165
-
166
- const a = await mutation.generateApplianceSecret((await register('gate-f')).id, context())
167
- const b = await mutation.generateApplianceSecret((await register('gate-g')).id, context())
168
-
169
- await mutation.revokeApplianceCredential(a.id, context())
170
-
171
- expect(await query.credentialRevoked(await getRepository(Appliance).findOneBy({ id: a.id }))).toBe(true)
172
- expect(await query.credentialRevoked(await getRepository(Appliance).findOneBy({ id: b.id }))).toBe(false)
173
- })
174
-
175
- it('reads the shadow user rather than the cleared column', async () => {
176
- /*
177
- * ⚠ The tempting shortcut is "no access token means revoked". It is wrong in both
178
- * directions: a registered appliance has no token and is not revoked, and clearing the
179
- * column by hand does not stop anything — the JWT verifies on its signature.
180
- *
181
- * So an appliance whose token is emptied without going through revocation is not revoked,
182
- * and one whose status is INACTIVE is, whatever the column holds.
183
- */
184
- const issued = await mutation.generateApplianceSecret((await register('gate-h')).id, context())
185
-
186
- await getRepository(Appliance).save({ ...issued, accessToken: null })
187
- expect(await query.credentialRevoked(await getRepository(Appliance).findOneBy({ id: issued.id }))).toBe(false)
188
-
189
- const appuser = await getRepository(User).findOneBy({ reference: issued.id, userType: 'appliance' })
190
- await getRepository(User).save({ ...appuser, status: UserStatus.INACTIVE })
191
-
192
- expect(await query.credentialRevoked(await getRepository(Appliance).findOneBy({ id: issued.id }))).toBe(true)
193
- })
194
-
195
- it('does not call a locked appliance revoked', async () => {
196
- /*
197
- * LOCKED is the failed-sign-in lockout and it ages out by itself, which is why revocation
198
- * does not use it. Reporting it as revoked would tell the reader a lock is permanent.
199
- */
200
- const issued = await mutation.generateApplianceSecret((await register('gate-i')).id, context())
201
-
202
- const appuser = await getRepository(User).findOneBy({ reference: issued.id, userType: 'appliance' })
203
- await getRepository(User).save({ ...appuser, status: UserStatus.LOCKED })
204
-
205
- expect(await query.credentialRevoked(await getRepository(Appliance).findOneBy({ id: issued.id }))).toBe(false)
206
- })
207
- })