@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,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
|
-
})
|