@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,268 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* What a role lacks against its base template - against a real database (ADR-0075 decision 1, amended 2026-10-26:
|
|
3
|
-
* a role keeps one base template, `Role.templateId`, and is never paired by name).
|
|
4
|
-
*
|
|
5
|
-
* A template is a seed and nothing pushes its later grants into roles already standing. When
|
|
6
|
-
* operato-plant's templates gained ai-assistant, the roles in miratek stayed without it and only
|
|
7
|
-
* plant's security runner noticed. The role screen now shows the gap and the administrator adds
|
|
8
|
-
* it; these are the facts that screen stands on.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase, authDataSource } from './db'
|
|
12
|
-
import { loadCompiled } from './compiled'
|
|
13
|
-
|
|
14
|
-
const { Role } = loadCompiled('service/role/role')
|
|
15
|
-
const { Privilege } = loadCompiled('service/privilege/privilege')
|
|
16
|
-
const { User } = loadCompiled('service/user/user')
|
|
17
|
-
const { RoleTemplateMutation } = loadCompiled('service/role-template/role-template-mutation')
|
|
18
|
-
const { RoleMutation } = loadCompiled('service/role/role-mutation')
|
|
19
|
-
const { RoleTemplateGapResolver, RoleBriefTaskResolver } = loadCompiled('service/role-template/role-template-gap-resolver')
|
|
20
|
-
const { RoleQuery } = loadCompiled('service/role/role-query')
|
|
21
|
-
const { registerRoleTemplate } = loadCompiled('service/role-template/role-template')
|
|
22
|
-
const { registerRoleTask } = loadCompiled('service/role-template/role-task')
|
|
23
|
-
|
|
24
|
-
const mutation = new RoleTemplateMutation()
|
|
25
|
-
const gapOf = (role: any) => new RoleTemplateGapResolver().templateGap(role)
|
|
26
|
-
const reread = (role: any) => getRepository(Role).findOneBy({ id: role.id })
|
|
27
|
-
/* The role screen's base template field saves through `updateRole`. */
|
|
28
|
-
const baseOn = (role: any, templateId: string | null) => new RoleMutation().updateRole(role.id, { templateId } as any, { ...context(), t: (key: string) => key } as any)
|
|
29
|
-
const coverageOf = (role: any) => new RoleTemplateGapResolver().taskCoverage(role)
|
|
30
|
-
|
|
31
|
-
let domain: any
|
|
32
|
-
let actor: any
|
|
33
|
-
|
|
34
|
-
const context = () => ({ state: { domain, user: actor, tx: authDataSource().manager } }) as any
|
|
35
|
-
|
|
36
|
-
/** The template as it stood when the role was made, then as it stands after it gained a door. */
|
|
37
|
-
function declareTemplate(axes: string[], extra: { category: string; axes: string[] }[] = []) {
|
|
38
|
-
;(process as any)['ROLE_TEMPLATES'] = {}
|
|
39
|
-
registerRoleTemplate({ id: 'plant.qi', name: 'QUALITY-INSPECTOR', grants: [{ category: 'board', axes }, ...extra] })
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
beforeAll(async () => {
|
|
43
|
-
await openAuthDatabase()
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
afterAll(async () => {
|
|
47
|
-
await closeAuthDatabase()
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
beforeEach(async () => {
|
|
51
|
-
await resetAuthDatabase()
|
|
52
|
-
|
|
53
|
-
const saved = await getRepository(Domain).save({ name: 'Plant A', subdomain: 'plant-a' })
|
|
54
|
-
domain = await getRepository(Domain).findOneBy({ id: saved.id })
|
|
55
|
-
|
|
56
|
-
await getRepository(Privilege).save({ name: 'query', category: 'board' })
|
|
57
|
-
await getRepository(Privilege).save({ name: 'mutation', category: 'board' })
|
|
58
|
-
;(process as any).PRIVILEGES = {
|
|
59
|
-
'board query': ['board', 'query'],
|
|
60
|
-
'board mutation': ['board', 'mutation']
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
actor = await getRepository(User).save({
|
|
64
|
-
username: 'admin',
|
|
65
|
-
email: 'admin@example.com',
|
|
66
|
-
name: 'Admin',
|
|
67
|
-
salt: User.generateSalt(),
|
|
68
|
-
status: 'activated',
|
|
69
|
-
domains: [domain]
|
|
70
|
-
})
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
describe('the gap between a role and its template', () => {
|
|
74
|
-
it('is zero for a role made from the template as it stands', async () => {
|
|
75
|
-
declareTemplate(['query'])
|
|
76
|
-
const role = await mutation.createRoleFromTemplate('plant.qi', undefined, context())
|
|
77
|
-
|
|
78
|
-
expect(await gapOf(role)).toEqual({
|
|
79
|
-
templateId: 'plant.qi',
|
|
80
|
-
templateName: 'QUALITY-INSPECTOR',
|
|
81
|
-
missing: [],
|
|
82
|
-
missingCount: 0,
|
|
83
|
-
extra: [],
|
|
84
|
-
extraCount: 0
|
|
85
|
-
})
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
it('★ names what the template gained after the role was made', async () => {
|
|
89
|
-
declareTemplate(['query'])
|
|
90
|
-
const role = await mutation.createRoleFromTemplate('plant.qi', undefined, context())
|
|
91
|
-
|
|
92
|
-
declareTemplate(['query', 'mutation'])
|
|
93
|
-
|
|
94
|
-
const gap = await gapOf(role)
|
|
95
|
-
expect(gap.missingCount).toBe(1)
|
|
96
|
-
expect(gap.missing).toEqual([{ category: 'board', axes: ['mutation'] }])
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
it('★ closes when the administrator adds it - the same top-up the template button runs', async () => {
|
|
100
|
-
declareTemplate(['query'])
|
|
101
|
-
const role = await mutation.createRoleFromTemplate('plant.qi', undefined, context())
|
|
102
|
-
declareTemplate(['query', 'mutation'])
|
|
103
|
-
|
|
104
|
-
await mutation.createRoleFromTemplate('plant.qi', 'QUALITY-INSPECTOR', context())
|
|
105
|
-
|
|
106
|
-
expect((await gapOf(role)).missingCount).toBe(0)
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('★ does not count what this installation does not declare', async () => {
|
|
110
|
-
declareTemplate(['query'], [{ category: 'order', axes: ['query'] }])
|
|
111
|
-
const role = await mutation.createRoleFromTemplate('plant.qi', undefined, context())
|
|
112
|
-
|
|
113
|
-
expect((await gapOf(role)).missingCount).toBe(0)
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('★ a role made under a name of its own is still based on the template', async () => {
|
|
117
|
-
declareTemplate(['query'])
|
|
118
|
-
const role = await mutation.createRoleFromTemplate('plant.qi', 'NIGHT-INSPECTOR', context())
|
|
119
|
-
declareTemplate(['query', 'mutation'])
|
|
120
|
-
|
|
121
|
-
expect((await reread(role)).templateId).toBe('plant.qi')
|
|
122
|
-
expect(await gapOf(await reread(role))).toMatchObject({ templateId: 'plant.qi', missingCount: 1 })
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
it('★ is null for a role that is based on no template, even when a template carries its name', async () => {
|
|
126
|
-
declareTemplate(['query'])
|
|
127
|
-
const role = await getRepository(Role).save({ domain, name: 'QUALITY-INSPECTOR', privileges: [] })
|
|
128
|
-
|
|
129
|
-
expect(await gapOf(role)).toBeNull()
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('★ refuses to top up a role based on another template, and changes nothing', async () => {
|
|
133
|
-
declareTemplate(['query'])
|
|
134
|
-
registerRoleTemplate({ id: 'plant.qm', name: 'QUALITY-MANAGER', grants: [{ category: 'board', axes: ['query', 'mutation'] }] })
|
|
135
|
-
const role = await mutation.createRoleFromTemplate('plant.qi', undefined, context())
|
|
136
|
-
|
|
137
|
-
await expect(mutation.createRoleFromTemplate('plant.qm', 'QUALITY-INSPECTOR', context())).rejects.toThrow(/based on role template "plant.qi", not "plant.qm"/)
|
|
138
|
-
|
|
139
|
-
const stored = await getRepository(Role).findOne({ where: { id: role.id }, relations: ['privileges'] })
|
|
140
|
-
expect(stored.templateId).toBe('plant.qi')
|
|
141
|
-
expect(stored.privileges.map((p: any) => p.name)).toEqual(['query'])
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
it('★ tops up a standing role that has no base and makes the template its base', async () => {
|
|
145
|
-
declareTemplate(['query', 'mutation'])
|
|
146
|
-
const role = await getRepository(Role).save({ domain, name: 'QUALITY-INSPECTOR', privileges: [] })
|
|
147
|
-
|
|
148
|
-
await mutation.createRoleFromTemplate('plant.qi', undefined, context())
|
|
149
|
-
|
|
150
|
-
expect((await reread(role)).templateId).toBe('plant.qi')
|
|
151
|
-
expect((await gapOf(await reread(role))).missingCount).toBe(0)
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
it('★ is null when the base is a template this installation no longer declares', async () => {
|
|
155
|
-
declareTemplate(['query'])
|
|
156
|
-
const role = await mutation.createRoleFromTemplate('plant.qi', undefined, context())
|
|
157
|
-
;(process as any)['ROLE_TEMPLATES'] = {}
|
|
158
|
-
|
|
159
|
-
expect(await gapOf(await reread(role))).toBeNull()
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it('does not take away what the administrator added beyond the template', async () => {
|
|
163
|
-
declareTemplate(['query'])
|
|
164
|
-
const role = await mutation.createRoleFromTemplate('plant.qi', undefined, context())
|
|
165
|
-
const stored = await getRepository(Role).findOne({ where: { id: role.id }, relations: ['privileges'] })
|
|
166
|
-
stored.privileges = [
|
|
167
|
-
...stored.privileges,
|
|
168
|
-
await getRepository(Privilege).findOneBy({ category: 'board', name: 'mutation' })
|
|
169
|
-
]
|
|
170
|
-
await getRepository(Role).save(stored)
|
|
171
|
-
|
|
172
|
-
const gap = await gapOf(role)
|
|
173
|
-
expect(gap.missingCount).toBe(0)
|
|
174
|
-
/* Shown as wider than the template - not a fault, and nothing takes it away. */
|
|
175
|
-
expect(gap.extra).toEqual([{ category: 'board', axes: ['mutation'] }])
|
|
176
|
-
expect(gap.extraCount).toBe(1)
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
it('★ lays a role named in the site\'s own words against the base the administrator gives it', async () => {
|
|
180
|
-
declareTemplate(['query', 'mutation'])
|
|
181
|
-
const role = await getRepository(Role).save({ domain, name: '품질검사', privileges: [await getRepository(Privilege).findOneBy({ category: 'board', name: 'query' })] })
|
|
182
|
-
|
|
183
|
-
expect(await gapOf(role)).toBeNull()
|
|
184
|
-
|
|
185
|
-
await baseOn(role, 'plant.qi')
|
|
186
|
-
expect(await gapOf(await reread(role))).toMatchObject({ templateId: 'plant.qi', missing: [{ category: 'board', axes: ['mutation'] }], extraCount: 0 })
|
|
187
|
-
|
|
188
|
-
await baseOn(role, null)
|
|
189
|
-
expect((await reread(role)).templateId).toBeNull()
|
|
190
|
-
expect(await gapOf(await reread(role))).toBeNull()
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
it('refuses a base template id that does not exist rather than storing it', async () => {
|
|
194
|
-
declareTemplate(['query'])
|
|
195
|
-
const role = await mutation.createRoleFromTemplate('plant.qi', undefined, context())
|
|
196
|
-
|
|
197
|
-
await expect(baseOn(role, 'plant.nobody')).rejects.toThrow(/no role template "plant.nobody"/)
|
|
198
|
-
expect((await reread(role)).templateId).toBe('plant.qi')
|
|
199
|
-
})
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
describe('a template built from tasks (ADR-0139 decision 11)', () => {
|
|
203
|
-
beforeEach(async () => {
|
|
204
|
-
;(process as any)['ROLE_TASKS'] = {}
|
|
205
|
-
;(process as any)['ROLE_TEMPLATES'] = {}
|
|
206
|
-
await getRepository(Privilege).save({ name: 'query', category: 'order' })
|
|
207
|
-
;(process as any).PRIVILEGES['order query'] = ['order', 'query']
|
|
208
|
-
registerRoleTask({ id: 'board.reading', grants: [{ category: 'board', axes: ['query'] }] })
|
|
209
|
-
registerRoleTask({ id: 'order.reading', grants: [{ category: 'order', axes: ['query'] }, { category: 'board', axes: ['query'] }] })
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
it('★ holds every privilege of every task once, plus its own grants', async () => {
|
|
213
|
-
registerRoleTemplate({ id: 'plant.reader', name: 'READER', tasks: ['board.reading', 'order.reading'], grants: [{ category: 'board', axes: ['mutation'] }] })
|
|
214
|
-
const role = await mutation.createRoleFromTemplate('plant.reader', undefined, context())
|
|
215
|
-
|
|
216
|
-
const held = (await getRepository(Role).findOne({ where: { id: role.id }, relations: ['privileges'] })).privileges.map((p: any) => `${p.category}:${p.name}`).sort()
|
|
217
|
-
expect(held).toEqual(['board:mutation', 'board:query', 'order:query'])
|
|
218
|
-
expect((await gapOf(role)).missingCount).toBe(0)
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
it('★ is refused whole when a task is not registered - no role is made, no privilege added', async () => {
|
|
222
|
-
registerRoleTemplate({ id: 'plant.reader', name: 'READER', tasks: ['board.reading', 'board.writting'] })
|
|
223
|
-
|
|
224
|
-
await expect(mutation.createRoleFromTemplate('plant.reader', undefined, context())).rejects.toThrow(/task "board.writting" is not registered/)
|
|
225
|
-
expect(await getRepository(Role).findOneBy({ name: 'READER' })).toBeNull()
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
it('★ is refused whole when a task names a privilege this installation does not declare', async () => {
|
|
229
|
-
registerRoleTask({ id: 'board.exporting', grants: [{ category: 'board-export', axes: ['query'] }] })
|
|
230
|
-
registerRoleTemplate({ id: 'plant.reader', name: 'READER', tasks: ['board.reading', 'board.exporting'] })
|
|
231
|
-
const standing = await getRepository(Role).save({ domain, name: 'READER', privileges: [] })
|
|
232
|
-
|
|
233
|
-
await expect(mutation.createRoleFromTemplate('plant.reader', undefined, context())).rejects.toThrow(/board-export:query/)
|
|
234
|
-
expect((await getRepository(Role).findOne({ where: { id: standing.id }, relations: ['privileges'] })).privileges).toEqual([])
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
it('★ the role screen sees a role by task: all, some, none, and what no task names', async () => {
|
|
238
|
-
const role = await getRepository(Role).save({
|
|
239
|
-
domain,
|
|
240
|
-
name: '판독',
|
|
241
|
-
privileges: [await getRepository(Privilege).findOneBy({ category: 'board', name: 'query' }), await getRepository(Privilege).findOneBy({ category: 'board', name: 'mutation' })]
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
const coverage = await coverageOf(role)
|
|
245
|
-
expect(coverage.tasks.map((task: any) => [task.taskId, task.status])).toEqual([
|
|
246
|
-
['board.reading', 'all'],
|
|
247
|
-
['order.reading', 'some']
|
|
248
|
-
])
|
|
249
|
-
expect(coverage.tasks[1].missing).toEqual([{ category: 'order', axes: ['query'] }])
|
|
250
|
-
expect(coverage.unclassified).toEqual([{ category: 'board', axes: ['mutation'] }])
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
it('★ a person reads their own roles with the same reading - name, description and the work each holds (§11-11)', async () => {
|
|
254
|
-
const role = await getRepository(Role).save({
|
|
255
|
-
domain,
|
|
256
|
-
name: '판독',
|
|
257
|
-
description: '도면을 읽는다',
|
|
258
|
-
privileges: [await getRepository(Privilege).findOneBy({ category: 'board', name: 'query' })]
|
|
259
|
-
})
|
|
260
|
-
await getRepository(Role).save({ domain, name: '남의 역할', privileges: [] })
|
|
261
|
-
actor.roles = [role]
|
|
262
|
-
await getRepository(User).save(actor)
|
|
263
|
-
|
|
264
|
-
const mine = await new RoleQuery().myRoles(context())
|
|
265
|
-
expect(mine.map((one: any) => [one.name, one.description])).toEqual([['판독', '도면을 읽는다']])
|
|
266
|
-
expect(await new RoleBriefTaskResolver().taskCoverage(mine[0])).toEqual(await coverageOf(role))
|
|
267
|
-
})
|
|
268
|
-
})
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Seeding a role from a template — against a real database.
|
|
3
|
-
*
|
|
4
|
-
* `role-template.test.ts` covers what the registry accepts. What it cannot cover is the
|
|
5
|
-
* part that touches rows, and that is where the rules with teeth are:
|
|
6
|
-
*
|
|
7
|
-
* - the plant names the seat, not the template;
|
|
8
|
-
* - an existing name is reused rather than duplicated, because approval lines hold a
|
|
9
|
-
* role's id and a second row would leave them pointing at the one nobody edits;
|
|
10
|
-
* - two templates on one name make the union, and privileges are never taken away;
|
|
11
|
-
* - a privilege this installation does not declare is skipped, never invented.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
|
|
15
|
-
import { authDataSource } from './db'
|
|
16
|
-
import { loadCompiled } from './compiled'
|
|
17
|
-
|
|
18
|
-
const { Role } = loadCompiled('service/role/role')
|
|
19
|
-
const { Privilege } = loadCompiled('service/privilege/privilege')
|
|
20
|
-
const { User } = loadCompiled('service/user/user')
|
|
21
|
-
const { RoleTemplateMutation } = loadCompiled('service/role-template/role-template-mutation')
|
|
22
|
-
const { registerRoleTemplate } = loadCompiled('service/role-template/role-template')
|
|
23
|
-
|
|
24
|
-
const mutation = new RoleTemplateMutation()
|
|
25
|
-
|
|
26
|
-
let domain: any
|
|
27
|
-
let actor: any
|
|
28
|
-
let boardQuery: any
|
|
29
|
-
let boardMutation: any
|
|
30
|
-
|
|
31
|
-
function context() {
|
|
32
|
-
return {
|
|
33
|
-
state: { domain, user: actor, tx: authDataSource().manager }
|
|
34
|
-
} as any
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function privilegesOf(id: string): Promise<string[]> {
|
|
38
|
-
const role = await getRepository(Role).findOne({ where: { id }, relations: ['privileges'] })
|
|
39
|
-
|
|
40
|
-
return (role!.privileges || []).map((one: any) => `${one.category}:${one.name}`).sort()
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
beforeAll(async () => {
|
|
44
|
-
await openAuthDatabase()
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
afterAll(async () => {
|
|
48
|
-
await closeAuthDatabase()
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
beforeEach(async () => {
|
|
52
|
-
await resetAuthDatabase()
|
|
53
|
-
;(process as any)['ROLE_TEMPLATES'] = {}
|
|
54
|
-
|
|
55
|
-
const saved = await getRepository(Domain).save({ name: 'Plant A', subdomain: 'plant-a' })
|
|
56
|
-
domain = await getRepository(Domain).findOneBy({ id: saved.id })
|
|
57
|
-
|
|
58
|
-
const privilegeRepository = getRepository(Privilege)
|
|
59
|
-
boardQuery = await privilegeRepository.save({ name: 'query', category: 'board' })
|
|
60
|
-
boardMutation = await privilegeRepository.save({ name: 'mutation', category: 'board' })
|
|
61
|
-
|
|
62
|
-
/* The gate reads this; seeding reads the rows above. Both have to agree. */
|
|
63
|
-
;(process as any).PRIVILEGES = {
|
|
64
|
-
'board query': ['board', 'query'],
|
|
65
|
-
'board mutation': ['board', 'mutation']
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
actor = await getRepository(User).save({
|
|
69
|
-
username: 'admin',
|
|
70
|
-
email: 'admin@example.com',
|
|
71
|
-
name: 'Admin',
|
|
72
|
-
salt: User.generateSalt(),
|
|
73
|
-
status: 'activated',
|
|
74
|
-
domains: [domain]
|
|
75
|
-
})
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
describe('seeding', () => {
|
|
79
|
-
it('creates the role with the privileges the template names', async () => {
|
|
80
|
-
registerRoleTemplate({
|
|
81
|
-
id: 'plant.qi',
|
|
82
|
-
name: 'QUALITY-INSPECTOR',
|
|
83
|
-
grants: [{ category: 'board', axes: ['query'] }]
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
const role = await mutation.createRoleFromTemplate('plant.qi', undefined, context())
|
|
87
|
-
|
|
88
|
-
expect(role.name).toBe('QUALITY-INSPECTOR')
|
|
89
|
-
expect(await privilegesOf(role.id)).toEqual(['board:query'])
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it('lets the plant name the seat itself', async () => {
|
|
93
|
-
registerRoleTemplate({ id: 'plant.qi', name: 'QUALITY-INSPECTOR', grants: [{ category: 'board', axes: ['query'] }] })
|
|
94
|
-
|
|
95
|
-
const role = await mutation.createRoleFromTemplate('plant.qi', '품질검사원', context())
|
|
96
|
-
|
|
97
|
-
expect(role.name).toBe('품질검사원')
|
|
98
|
-
expect(await privilegesOf(role.id)).toEqual(['board:query'])
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
it('makes a second seat from one template when the name differs — day shift and night shift', async () => {
|
|
102
|
-
registerRoleTemplate({ id: 'plant.qi', name: 'QUALITY-INSPECTOR', grants: [{ category: 'board', axes: ['query'] }] })
|
|
103
|
-
|
|
104
|
-
const day = await mutation.createRoleFromTemplate('plant.qi', 'QI-DAY', context())
|
|
105
|
-
const night = await mutation.createRoleFromTemplate('plant.qi', 'QI-NIGHT', context())
|
|
106
|
-
|
|
107
|
-
expect(day.id).not.toBe(night.id)
|
|
108
|
-
expect(await privilegesOf(day.id)).toEqual(['board:query'])
|
|
109
|
-
expect(await privilegesOf(night.id)).toEqual(['board:query'])
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
it('reuses an existing role of that name instead of creating a second one', async () => {
|
|
113
|
-
registerRoleTemplate({ id: 'plant.qi', name: 'QUALITY-INSPECTOR', grants: [{ category: 'board', axes: ['query'] }] })
|
|
114
|
-
|
|
115
|
-
const existing = await getRepository(Role).save({ name: 'QUALITY-INSPECTOR', domain, privileges: [] })
|
|
116
|
-
const role = await mutation.createRoleFromTemplate('plant.qi', undefined, context())
|
|
117
|
-
|
|
118
|
-
expect(role.id).toBe(existing.id)
|
|
119
|
-
expect(await getRepository(Role).count()).toBe(1)
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
/* A role has one base template (ADR-0075 decision 1, amended 2026-10-26) - two apps declaring one name no longer union. */
|
|
123
|
-
it('refuses a second template onto a role of the same name based on the first', async () => {
|
|
124
|
-
registerRoleTemplate({ id: 'plant.qi', name: 'QUALITY-INSPECTOR', grants: [{ category: 'board', axes: ['query'] }] })
|
|
125
|
-
registerRoleTemplate({
|
|
126
|
-
id: 'twin.qi',
|
|
127
|
-
name: 'QUALITY-INSPECTOR',
|
|
128
|
-
grants: [{ category: 'board', axes: ['mutation'] }]
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
const role = await mutation.createRoleFromTemplate('plant.qi', undefined, context())
|
|
132
|
-
await expect(mutation.createRoleFromTemplate('twin.qi', undefined, context())).rejects.toThrow(/based on role template "plant.qi", not "twin.qi"/)
|
|
133
|
-
|
|
134
|
-
expect(await privilegesOf(role.id)).toEqual(['board:query'])
|
|
135
|
-
expect(await getRepository(Role).count()).toBe(1)
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
it('leaves privileges the administrator added, and takes nothing away', async () => {
|
|
139
|
-
registerRoleTemplate({ id: 'plant.qi', name: 'QUALITY-INSPECTOR', grants: [{ category: 'board', axes: ['query'] }] })
|
|
140
|
-
|
|
141
|
-
const existing = await getRepository(Role).save({
|
|
142
|
-
name: 'QUALITY-INSPECTOR',
|
|
143
|
-
domain,
|
|
144
|
-
privileges: [boardMutation]
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
await mutation.createRoleFromTemplate('plant.qi', undefined, context())
|
|
148
|
-
|
|
149
|
-
expect(await privilegesOf(existing.id)).toEqual(['board:mutation', 'board:query'])
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
it('skips a privilege this installation does not declare rather than inventing the row', async () => {
|
|
153
|
-
registerRoleTemplate({
|
|
154
|
-
id: 'plant.x',
|
|
155
|
-
name: 'X',
|
|
156
|
-
grants: [
|
|
157
|
-
{ category: 'board', axes: ['query'] },
|
|
158
|
-
{ category: 'board-ai', axes: ['query'] }
|
|
159
|
-
]
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
const role = await mutation.createRoleFromTemplate('plant.x', undefined, context())
|
|
163
|
-
|
|
164
|
-
expect(await privilegesOf(role.id)).toEqual(['board:query'])
|
|
165
|
-
expect(await getRepository(Privilege).count()).toBe(2)
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
it('refuses a template this installation does not have', async () => {
|
|
169
|
-
await expect(mutation.createRoleFromTemplate('plant.missing', undefined, context())).rejects.toThrow(
|
|
170
|
-
/no role template/
|
|
171
|
-
)
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
it('seeds the framework-computed administrator without any application declaring it', async () => {
|
|
175
|
-
const role = await mutation.createRoleFromTemplate('framework.administrator', undefined, context())
|
|
176
|
-
|
|
177
|
-
expect(role.name).toBe('ADMINISTRATOR')
|
|
178
|
-
expect(await privilegesOf(role.id)).toEqual(['board:mutation', 'board:query'])
|
|
179
|
-
})
|
|
180
|
-
})
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Role templates — the registry and what it refuses.
|
|
3
|
-
*
|
|
4
|
-
* The refusals are the interesting part. A template that registers happily and then grants
|
|
5
|
-
* nothing is the quiet failure this design exists to avoid, so the checks that make noise
|
|
6
|
-
* are the ones pinned here.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { loadCompiled } from './compiled'
|
|
10
|
-
|
|
11
|
-
const {
|
|
12
|
-
registerRoleTemplate,
|
|
13
|
-
roleTemplates,
|
|
14
|
-
roleTemplateById,
|
|
15
|
-
computedRoleTemplates,
|
|
16
|
-
allRoleTemplates,
|
|
17
|
-
unknownTemplateGrants
|
|
18
|
-
} = loadCompiled('service/role-template/role-template')
|
|
19
|
-
|
|
20
|
-
const { declareAxes } = loadCompiled('service/privilege/privilege-axis')
|
|
21
|
-
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
/* Nobody declared, so the defaults apply — query/mutation with query read-only. */
|
|
24
|
-
delete (process as any).PRIVILEGE_AXES
|
|
25
|
-
;(process as any)['ROLE_TEMPLATES'] = {}
|
|
26
|
-
;(process as any).PRIVILEGES = {
|
|
27
|
-
'board query': ['board', 'query'],
|
|
28
|
-
'board mutation': ['board', 'mutation'],
|
|
29
|
-
'order query': ['order', 'query'],
|
|
30
|
-
'worklist query': ['worklist', 'query'],
|
|
31
|
-
'worklist mutation': ['worklist', 'mutation']
|
|
32
|
-
}
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
describe('declaring a template', () => {
|
|
36
|
-
it('can be found again by id', () => {
|
|
37
|
-
registerRoleTemplate({
|
|
38
|
-
id: 'plant.production-lead',
|
|
39
|
-
name: 'PRODUCTION-LEAD',
|
|
40
|
-
grants: [{ category: 'worklist', axes: ['query', 'mutation'] }]
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
expect(roleTemplateById('plant.production-lead')!.name).toBe('PRODUCTION-LEAD')
|
|
44
|
-
expect(roleTemplates()).toHaveLength(1)
|
|
45
|
-
})
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
describe('what registration refuses', () => {
|
|
49
|
-
it('refuses a template with no grants written at all', () => {
|
|
50
|
-
expect(() => registerRoleTemplate({ id: 'x', name: 'X' } as any)).toThrow(/no grants/)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('accepts an empty list — a seat that opens nothing on purpose, for approval routing', () => {
|
|
54
|
-
registerRoleTemplate({ id: 'twin.quality-inspector', name: 'QUALITY-INSPECTOR', grants: [] })
|
|
55
|
-
|
|
56
|
-
expect(roleTemplateById('twin.quality-inspector')!.grants).toEqual([])
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('refuses a grant with no axes', () => {
|
|
60
|
-
expect(() => registerRoleTemplate({ id: 'x', name: 'X', grants: [{ category: 'board', axes: [] }] })).toThrow(
|
|
61
|
-
/no axes/
|
|
62
|
-
)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('refuses the same id twice', () => {
|
|
66
|
-
registerRoleTemplate({ id: 'x', name: 'X', grants: [] })
|
|
67
|
-
|
|
68
|
-
expect(() => registerRoleTemplate({ id: 'x', name: 'Y', grants: [] })).toThrow(/declared twice/)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('accepts the same role name from two applications — one seat, declared twice', () => {
|
|
72
|
-
registerRoleTemplate({ id: 'plant.qi', name: 'QUALITY-INSPECTOR', grants: [{ category: 'board', axes: ['query'] }] })
|
|
73
|
-
registerRoleTemplate({ id: 'twin.qi', name: 'QUALITY-INSPECTOR', grants: [] })
|
|
74
|
-
|
|
75
|
-
expect(roleTemplates().filter(one => one.name === 'QUALITY-INSPECTOR')).toHaveLength(2)
|
|
76
|
-
})
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
describe('names this installation does not declare', () => {
|
|
80
|
-
it('points at grants that would silently open nothing after a category rename', () => {
|
|
81
|
-
registerRoleTemplate({
|
|
82
|
-
id: 'plant.x',
|
|
83
|
-
name: 'X',
|
|
84
|
-
grants: [
|
|
85
|
-
{ category: 'board', axes: ['query'] },
|
|
86
|
-
{ category: 'board-ai', axes: ['query', 'mutation'] }
|
|
87
|
-
]
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
expect(unknownTemplateGrants(roleTemplateById('plant.x')!)).toEqual(['board-ai:query', 'board-ai:mutation'])
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
it('reports nothing when every named privilege is installed', () => {
|
|
94
|
-
registerRoleTemplate({ id: 'plant.y', name: 'Y', grants: [{ category: 'order', axes: ['query'] }] })
|
|
95
|
-
|
|
96
|
-
expect(unknownTemplateGrants(roleTemplateById('plant.y')!)).toEqual([])
|
|
97
|
-
})
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
describe('the two templates the framework computes', () => {
|
|
101
|
-
it('gives the viewer every category that declares a query axis', () => {
|
|
102
|
-
const viewer = computedRoleTemplates().find(one => one.id === 'framework.viewer')!
|
|
103
|
-
|
|
104
|
-
expect(viewer.grants.map(grant => grant.category).sort()).toEqual(['board', 'order', 'worklist'])
|
|
105
|
-
expect(viewer.grants.every(grant => grant.axes.join() === 'query')).toBe(true)
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it('gives the administrator everything declared', () => {
|
|
109
|
-
const administrator = computedRoleTemplates().find(one => one.id === 'framework.administrator')!
|
|
110
|
-
const pairs = administrator.grants.flatMap(grant => grant.axes.map(axis => `${grant.category}:${axis}`)).sort()
|
|
111
|
-
|
|
112
|
-
expect(pairs).toEqual(['board:mutation', 'board:query', 'order:query', 'worklist:mutation', 'worklist:query'])
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
it('widens only the administrator when a new axis is declared', () => {
|
|
116
|
-
;(process as any).PRIVILEGES['board execute'] = ['board', 'execute']
|
|
117
|
-
|
|
118
|
-
const administrator = computedRoleTemplates().find(one => one.id === 'framework.administrator')!
|
|
119
|
-
const viewer = computedRoleTemplates().find(one => one.id === 'framework.viewer')!
|
|
120
|
-
|
|
121
|
-
expect(administrator.grants.find(grant => grant.category === 'board')!.axes).toContain('execute')
|
|
122
|
-
expect(viewer.grants.find(grant => grant.category === 'board')!.axes).toEqual(['query'])
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
it('builds the viewer from the declared read-only axes, not from the word "query"', () => {
|
|
126
|
-
/*
|
|
127
|
-
* A house that spells its read axis `read` gets a viewer over `read`. Matching the literal
|
|
128
|
-
* `query` was right here and wrong there — dssp's `kpi` has eight axes and none of them is
|
|
129
|
-
* `query`, so its main module would have gone missing from VIEWER without a word anywhere.
|
|
130
|
-
*/
|
|
131
|
-
declareAxes(['read', 'input'], { readOnly: ['read'] })
|
|
132
|
-
;(process as any).PRIVILEGES = {
|
|
133
|
-
'kpi read': ['kpi', 'read'],
|
|
134
|
-
'kpi input': ['kpi', 'input']
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const viewer = computedRoleTemplates().find(one => one.id === 'framework.viewer')!
|
|
138
|
-
|
|
139
|
-
expect(viewer.grants).toEqual([{ category: 'kpi', axes: ['read'] }])
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
it('does not offer a viewer at all when nobody said which axes are read-only', () => {
|
|
143
|
-
/*
|
|
144
|
-
* ⚠ The point of this one. Offering an empty VIEWER would let an administrator pick it,
|
|
145
|
-
* get a role, and open nothing — and the role list would count that zero correctly and
|
|
146
|
-
* present it as fact. "We do not know" is not "there are none", so the template is
|
|
147
|
-
* withheld and `reportDeclaredAxes` says so at boot.
|
|
148
|
-
*/
|
|
149
|
-
declareAxes(['read', 'input'])
|
|
150
|
-
|
|
151
|
-
const ids = computedRoleTemplates().map(one => one.id)
|
|
152
|
-
|
|
153
|
-
expect(ids).toEqual(['framework.administrator'])
|
|
154
|
-
expect(ids).not.toContain('framework.viewer')
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
it('offers the computed two alongside whatever applications declared', () => {
|
|
158
|
-
registerRoleTemplate({ id: 'plant.z', name: 'Z', grants: [] })
|
|
159
|
-
|
|
160
|
-
expect(allRoleTemplates().map(one => one.id)).toEqual(['framework.viewer', 'framework.administrator', 'plant.z'])
|
|
161
|
-
})
|
|
162
|
-
})
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { buildSchema } from 'type-graphql'
|
|
2
|
-
|
|
3
|
-
import { loadCompiled } from './compiled'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Reading roles does not list who holds them (ADR-0139).
|
|
7
|
-
*
|
|
8
|
-
* The `user:query` gate stood on `RoleQuery.users`, a field resolver, while `Role` declared `users` itself - and
|
|
9
|
-
* type-graphql keeps the declaration's directives, so the gate was never in the served schema: `role:query` alone listed
|
|
10
|
-
* every role's people. It now stands on the declaration; this reads the schema that is served, not the source.
|
|
11
|
-
*/
|
|
12
|
-
const { schema: declared } = loadCompiled('service/index')
|
|
13
|
-
const { privilegeDirectiveResolver, privilegeOfField } = loadCompiled('service/privilege/privilege-directive')
|
|
14
|
-
|
|
15
|
-
describe("a role's people", () => {
|
|
16
|
-
it('★ Role.users asks user:query in the served schema', async () => {
|
|
17
|
-
const schema = await buildSchema({ resolvers: declared.resolverClasses, validate: false })
|
|
18
|
-
privilegeDirectiveResolver(schema, { modules: [] })
|
|
19
|
-
const gate = privilegeOfField('Role', 'users')
|
|
20
|
-
expect([gate.known, gate['gate']?.category, gate['gate']?.privilege]).toEqual([true, 'user', 'query'])
|
|
21
|
-
})
|
|
22
|
-
})
|