@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.
- package/package.json +24 -7
- package/CHANGELOG.md +0 -55
- package/dist-client/actions/auth.js.map +0 -1
- package/dist-client/apply-domain-theme.js.map +0 -1
- package/dist-client/auth.js.map +0 -1
- package/dist-client/bootstrap.js.map +0 -1
- package/dist-client/directive/privileged.js.map +0 -1
- package/dist-client/domain-required.js.map +0 -1
- package/dist-client/index.js.map +0 -1
- package/dist-client/profiled.js.map +0 -1
- package/dist-client/reducers/auth.js.map +0 -1
- package/dist-client/tsconfig.tsbuildinfo +0 -1
- package/dist-client/verify-webauthn.js.map +0 -1
- package/dist-server/ai-entry/access-assistant.js.map +0 -1
- package/dist-server/ai-entry/access-entry-targets.js.map +0 -1
- package/dist-server/ai-entry/entry-target-shape.js.map +0 -1
- package/dist-server/constants/error-code.js.map +0 -1
- package/dist-server/constants/error-message.js.map +0 -1
- package/dist-server/constants/max-age.js.map +0 -1
- package/dist-server/controllers/access-request.js.map +0 -1
- package/dist-server/controllers/account-mail.js.map +0 -1
- package/dist-server/controllers/auth.js.map +0 -1
- package/dist-server/controllers/change-pwd.js.map +0 -1
- package/dist-server/controllers/confirm-identity.js.map +0 -1
- package/dist-server/controllers/delete-user.js.map +0 -1
- package/dist-server/controllers/passkey.js.map +0 -1
- package/dist-server/controllers/password-setup.js.map +0 -1
- package/dist-server/controllers/profile.js.map +0 -1
- package/dist-server/controllers/reset-password.js.map +0 -1
- package/dist-server/controllers/signin.js.map +0 -1
- package/dist-server/controllers/signup.js.map +0 -1
- package/dist-server/controllers/unlock-user.js.map +0 -1
- package/dist-server/controllers/utils/find-verification-token.js.map +0 -1
- package/dist-server/controllers/utils/make-verification-token.js.map +0 -1
- package/dist-server/controllers/utils/password-rule.js.map +0 -1
- package/dist-server/controllers/utils/save-verification-token.js.map +0 -1
- package/dist-server/controllers/verification.js.map +0 -1
- package/dist-server/errors/auth-error.js.map +0 -1
- package/dist-server/errors/index.js.map +0 -1
- package/dist-server/errors/user-domain-not-match-error.js.map +0 -1
- package/dist-server/index.js.map +0 -1
- package/dist-server/middlewares/audit-scope-middleware.js.map +0 -1
- package/dist-server/middlewares/authenticate-401-middleware.js.map +0 -1
- package/dist-server/middlewares/bypass-signin-middleware.js.map +0 -1
- package/dist-server/middlewares/domain-authenticate-middleware.js.map +0 -1
- package/dist-server/middlewares/graphql-authenticate-middleware.js.map +0 -1
- package/dist-server/middlewares/index.js.map +0 -1
- package/dist-server/middlewares/jwt-authenticate-middleware.js.map +0 -1
- package/dist-server/middlewares/signin-middleware.js.map +0 -1
- package/dist-server/middlewares/verify-recaptcha-middleware.js.map +0 -1
- package/dist-server/migrations/1548206416130-SeedUser.js.map +0 -1
- package/dist-server/migrations/1566805283882-SeedPrivilege.js.map +0 -1
- package/dist-server/migrations/1745000000000-MigrateDomainOwnersToTable.js.map +0 -1
- package/dist-server/migrations/index.js.map +0 -1
- package/dist-server/role-tasks.js.map +0 -1
- package/dist-server/router/auth-checkin-router.js.map +0 -1
- package/dist-server/router/auth-private-process-router.js.map +0 -1
- package/dist-server/router/auth-public-process-router.js.map +0 -1
- package/dist-server/router/auth-signin-router.js.map +0 -1
- package/dist-server/router/auth-signup-router.js.map +0 -1
- package/dist-server/router/index.js.map +0 -1
- package/dist-server/router/oauth2/index.js.map +0 -1
- package/dist-server/router/oauth2/oauth2-authorize-router.js.map +0 -1
- package/dist-server/router/oauth2/oauth2-router.js.map +0 -1
- package/dist-server/router/oauth2/oauth2-server.js.map +0 -1
- package/dist-server/router/oauth2/passport-oauth2-client-password.js.map +0 -1
- package/dist-server/router/oauth2/passport-refresh-token.js.map +0 -1
- package/dist-server/router/path-base-domain-router.js.map +0 -1
- package/dist-server/router/site-root-router.js.map +0 -1
- package/dist-server/router/webauthn-router.js.map +0 -1
- package/dist-server/routes.js.map +0 -1
- package/dist-server/service/access-request/access-request-mutation.js.map +0 -1
- package/dist-server/service/access-request/access-request-query.js.map +0 -1
- package/dist-server/service/access-request/access-request-types.js.map +0 -1
- package/dist-server/service/access-request/access-request.js.map +0 -1
- package/dist-server/service/access-request/index.js.map +0 -1
- package/dist-server/service/app-binding/app-binding-mutation.js.map +0 -1
- package/dist-server/service/app-binding/app-binding-query.js.map +0 -1
- package/dist-server/service/app-binding/app-binding-types.js.map +0 -1
- package/dist-server/service/app-binding/app-binding.js.map +0 -1
- package/dist-server/service/app-binding/index.js.map +0 -1
- package/dist-server/service/appliance/appliance-mutation.js.map +0 -1
- package/dist-server/service/appliance/appliance-query.js.map +0 -1
- package/dist-server/service/appliance/appliance-types.js.map +0 -1
- package/dist-server/service/appliance/appliance.js.map +0 -1
- package/dist-server/service/appliance/index.js.map +0 -1
- package/dist-server/service/application/application-mutation.js.map +0 -1
- package/dist-server/service/application/application-query.js.map +0 -1
- package/dist-server/service/application/application-types.js.map +0 -1
- package/dist-server/service/application/application.js.map +0 -1
- package/dist-server/service/application/index.js.map +0 -1
- package/dist-server/service/audit/audit-action.js.map +0 -1
- package/dist-server/service/audit/audit-kinds.js.map +0 -1
- package/dist-server/service/audit/audit-query.js.map +0 -1
- package/dist-server/service/audit/audit-recorder.js.map +0 -1
- package/dist-server/service/audit/audit-request.js.map +0 -1
- package/dist-server/service/audit/audit-retention.js.map +0 -1
- package/dist-server/service/audit/audit-subscriber.js.map +0 -1
- package/dist-server/service/audit/audit-target.js.map +0 -1
- package/dist-server/service/audit/audit-types.js.map +0 -1
- package/dist-server/service/audit/audit-work.js.map +0 -1
- package/dist-server/service/audit/index.js.map +0 -1
- package/dist-server/service/auth-provider/auth-provider-mutation.js.map +0 -1
- package/dist-server/service/auth-provider/auth-provider-parameter-spec.js.map +0 -1
- package/dist-server/service/auth-provider/auth-provider-query.js.map +0 -1
- package/dist-server/service/auth-provider/auth-provider-type.js.map +0 -1
- package/dist-server/service/auth-provider/auth-provider.js.map +0 -1
- package/dist-server/service/auth-provider/index.js.map +0 -1
- package/dist-server/service/domain-generator/domain-generator-mutation.js.map +0 -1
- package/dist-server/service/domain-generator/domain-generator-types.js.map +0 -1
- package/dist-server/service/domain-generator/index.js.map +0 -1
- package/dist-server/service/domain-link/domain-link-mutation.js.map +0 -1
- package/dist-server/service/domain-link/domain-link-query.js.map +0 -1
- package/dist-server/service/domain-link/domain-link-types.js.map +0 -1
- package/dist-server/service/domain-link/domain-link.js.map +0 -1
- package/dist-server/service/domain-link/index.js.map +0 -1
- package/dist-server/service/domain-owner/domain-owner-mutation.js.map +0 -1
- package/dist-server/service/domain-owner/domain-owner-query.js.map +0 -1
- package/dist-server/service/domain-owner/domain-owner.js.map +0 -1
- package/dist-server/service/domain-owner/index.js.map +0 -1
- package/dist-server/service/granted-role/granted-role-mutation.js.map +0 -1
- package/dist-server/service/granted-role/granted-role-query.js.map +0 -1
- package/dist-server/service/granted-role/granted-role.js.map +0 -1
- package/dist-server/service/granted-role/index.js.map +0 -1
- package/dist-server/service/identity-confirmation/identity-confirmation.js.map +0 -1
- package/dist-server/service/index.js.map +0 -1
- package/dist-server/service/open-fields.js.map +0 -1
- package/dist-server/service/partner/index.js.map +0 -1
- package/dist-server/service/partner/partner-mutation.js.map +0 -1
- package/dist-server/service/partner/partner-query.js.map +0 -1
- package/dist-server/service/partner/partner-types.js.map +0 -1
- package/dist-server/service/partner/partner.js.map +0 -1
- package/dist-server/service/password-history/index.js.map +0 -1
- package/dist-server/service/password-history/password-history.js.map +0 -1
- package/dist-server/service/privilege/index.js.map +0 -1
- package/dist-server/service/privilege/open-field-rule.js.map +0 -1
- package/dist-server/service/privilege/privilege-axis.js.map +0 -1
- package/dist-server/service/privilege/privilege-deprecation.js.map +0 -1
- package/dist-server/service/privilege/privilege-directive.js.map +0 -1
- package/dist-server/service/privilege/privilege-mutation.js.map +0 -1
- package/dist-server/service/privilege/privilege-query.js.map +0 -1
- package/dist-server/service/privilege/privilege-rejection.js.map +0 -1
- package/dist-server/service/privilege/privilege-sync.js.map +0 -1
- package/dist-server/service/privilege/privilege-types.js.map +0 -1
- package/dist-server/service/privilege/privilege.js.map +0 -1
- package/dist-server/service/role/index.js.map +0 -1
- package/dist-server/service/role/role-mutation.js.map +0 -1
- package/dist-server/service/role/role-privilege-query.js.map +0 -1
- package/dist-server/service/role/role-query.js.map +0 -1
- package/dist-server/service/role/role-types.js.map +0 -1
- package/dist-server/service/role/role.js.map +0 -1
- package/dist-server/service/role-template/index.js.map +0 -1
- package/dist-server/service/role-template/role-task.js.map +0 -1
- package/dist-server/service/role-template/role-template-gap-resolver.js.map +0 -1
- package/dist-server/service/role-template/role-template-mutation.js.map +0 -1
- package/dist-server/service/role-template/role-template-query.js.map +0 -1
- package/dist-server/service/role-template/role-template-types.js.map +0 -1
- package/dist-server/service/role-template/role-template.js.map +0 -1
- package/dist-server/service/user/domain-query.js.map +0 -1
- package/dist-server/service/user/index.js.map +0 -1
- package/dist-server/service/user/user-mutation.js.map +0 -1
- package/dist-server/service/user/user-query.js.map +0 -1
- package/dist-server/service/user/user-types.js.map +0 -1
- package/dist-server/service/user/user.js.map +0 -1
- package/dist-server/service/users-auth-providers/index.js.map +0 -1
- package/dist-server/service/users-auth-providers/users-auth-providers.js.map +0 -1
- package/dist-server/service/verification-token/index.js.map +0 -1
- package/dist-server/service/verification-token/verification-token.js.map +0 -1
- package/dist-server/service/web-auth-credential/index.js.map +0 -1
- package/dist-server/service/web-auth-credential/web-auth-credential-mutation.js.map +0 -1
- package/dist-server/service/web-auth-credential/web-auth-credential-query.js.map +0 -1
- package/dist-server/service/web-auth-credential/web-auth-credential.js.map +0 -1
- package/dist-server/tsconfig.tsbuildinfo +0 -1
- package/dist-server/types.js.map +0 -1
- package/dist-server/utils/accepts.js.map +0 -1
- package/dist-server/utils/access-request-review.js.map +0 -1
- package/dist-server/utils/access-token-cookie.js.map +0 -1
- package/dist-server/utils/account-lock-rule.js.map +0 -1
- package/dist-server/utils/assignable-roles.js.map +0 -1
- package/dist-server/utils/build-auth-url.js.map +0 -1
- package/dist-server/utils/can-user-checkin-domain.js.map +0 -1
- package/dist-server/utils/check-permission.js.map +0 -1
- package/dist-server/utils/check-user-belongs-domain.js.map +0 -1
- package/dist-server/utils/check-user-has-role.js.map +0 -1
- package/dist-server/utils/checkin-domain-facts.js.map +0 -1
- package/dist-server/utils/checkin-domain-rule.js.map +0 -1
- package/dist-server/utils/credential-at-rest.js.map +0 -1
- package/dist-server/utils/credential-features.js.map +0 -1
- package/dist-server/utils/credential-serial-rule.js.map +0 -1
- package/dist-server/utils/domain-ownership.js.map +0 -1
- package/dist-server/utils/encrypt-state.js.map +0 -1
- package/dist-server/utils/get-aes-256-key.js.map +0 -1
- package/dist-server/utils/get-domain-users.js.map +0 -1
- package/dist-server/utils/get-secret.js.map +0 -1
- package/dist-server/utils/get-user-domains.js.map +0 -1
- package/dist-server/utils/lock-duration.js.map +0 -1
- package/dist-server/utils/password-reset-rule.js.map +0 -1
- package/dist-server/utils/profile-user.js.map +0 -1
- package/dist-server/utils/refuse-role-name.js.map +0 -1
- package/dist-server/utils/role-name-standing.js.map +0 -1
- package/dist-server/utils/same-origin-redirect.js.map +0 -1
- package/dist-server/utils/verification-token-rule.js.map +0 -1
- package/spec/unit/checkin-domain.spec.ts +0 -307
- package/spec/unit/checkin-privilege.spec.ts +0 -205
- package/spec/unit/domain-owner-entity.spec.ts +0 -179
- package/spec/unit/domain-owner-granted.spec.ts +0 -215
- package/spec/unit/domain-owner-migration.spec.ts +0 -236
- package/spec/unit/domain-owner-mutation.spec.ts +0 -337
- package/spec/unit/domain-ownership.spec.ts +0 -188
- package/tests/access-assistant-db.test.ts +0 -155
- package/tests/access-entry-targets-db.test.ts +0 -176
- package/tests/access-request-db.test.ts +0 -394
- package/tests/access-request-pg.test.ts +0 -83
- package/tests/access-request-review.test.ts +0 -193
- package/tests/account-lock-db.test.ts +0 -216
- package/tests/account-lock-rule.test.ts +0 -141
- package/tests/account-mail.test.ts +0 -69
- package/tests/activate-page-case.test.ts +0 -53
- package/tests/add-user-gates.test.ts +0 -34
- package/tests/app-binding-db.test.ts +0 -210
- package/tests/app-binding-delete-db.test.ts +0 -188
- package/tests/appliance-credential-state-db.test.ts +0 -207
- package/tests/appliance-delete-db.test.ts +0 -119
- package/tests/appliance-revoke-db.test.ts +0 -294
- package/tests/appliance-schema.test.ts +0 -129
- package/tests/application-delete-db.test.ts +0 -190
- package/tests/application-token-payload-db.test.ts +0 -185
- package/tests/assignable-roles-db.test.ts +0 -219
- package/tests/audit-db.test.ts +0 -410
- package/tests/audit-work.test.ts +0 -58
- package/tests/auth-base-role-tasks.test.ts +0 -58
- package/tests/checkin-domain-rule.test.ts +0 -128
- package/tests/checkin-privilege-seam-db.test.ts +0 -158
- package/tests/compiled.ts +0 -41
- package/tests/confirm-identity-db.test.ts +0 -85
- package/tests/credential-at-rest-db.test.ts +0 -246
- package/tests/credential-features-off-db.test.ts +0 -189
- package/tests/credential-serial-rule.test.ts +0 -84
- package/tests/declare-privileges.test.ts +0 -29
- package/tests/domain-assignees-db.test.ts +0 -95
- package/tests/domain-inheritance-sentinel.test.ts +0 -93
- package/tests/domain-required.test.ts +0 -127
- package/tests/has-privilege-db.test.ts +0 -198
- package/tests/identity-confirmation.test.ts +0 -146
- package/tests/list-inheritance-db.test.ts +0 -127
- package/tests/lock-recovery.test.ts +0 -206
- package/tests/mail-request-answers-db.test.ts +0 -142
- package/tests/open-doors.test.ts +0 -121
- package/tests/open-fields-enforcement.test.ts +0 -215
- package/tests/open-fields-framework.test.ts +0 -212
- package/tests/open-redirect-db.test.ts +0 -143
- package/tests/passkey-authenticator.ts +0 -104
- package/tests/passkey-db.test.ts +0 -295
- package/tests/passkey-signin-route-db.test.ts +0 -139
- package/tests/password-hashing.test.ts +0 -131
- package/tests/password-persistence.test.ts +0 -147
- package/tests/password-reset-rule-db.test.ts +0 -159
- package/tests/password-setup-db.test.ts +0 -215
- package/tests/permission-gate.test.ts +0 -228
- package/tests/picture-reducer.test.ts +0 -25
- package/tests/privilege-axis.test.ts +0 -157
- package/tests/privilege-deprecation.test.ts +0 -65
- package/tests/privilege-directive.test.ts +0 -332
- package/tests/privilege-rejection.test.ts +0 -104
- package/tests/privilege-sync-db.test.ts +0 -58
- package/tests/profile-user.test.ts +0 -24
- package/tests/refusal-cause.test.ts +0 -80
- package/tests/refusals-have-korean.test.ts +0 -24
- package/tests/request-holds.test.ts +0 -53
- package/tests/role-inheritance-db.test.ts +0 -286
- package/tests/role-mutation-db.test.ts +0 -293
- package/tests/role-name-lookup-sentinel.test.ts +0 -79
- package/tests/role-name-standing.test.ts +0 -83
- package/tests/role-privileges-db.test.ts +0 -252
- package/tests/role-task.test.ts +0 -168
- package/tests/role-template-gap-db.test.ts +0 -268
- package/tests/role-template-seed-db.test.ts +0 -180
- package/tests/role-template.test.ts +0 -162
- package/tests/role-users-gate.test.ts +0 -22
- package/tests/signin-policy.test.ts +0 -200
- package/tests/signup-duplicate-db.test.ts +0 -68
- package/tests/token-issuance.test.ts +0 -244
- package/tests/user-roles-field-db.test.ts +0 -134
- 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
|
-
})
|