@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,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `requestHolds` - whether the request holds a privilege, asked as the doors ask it and once per request.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { loadCompiled } from './compiled'
|
|
6
|
-
|
|
7
|
-
const { requestHolds } = loadCompiled('utils/check-permission')
|
|
8
|
-
const { User } = loadCompiled('service/user/user')
|
|
9
|
-
|
|
10
|
-
const DOMAIN = { id: 'domain-1', subdomain: 'plant-a' } as any
|
|
11
|
-
const USER = { id: 'user-1', domains: [DOMAIN] } as any
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
;(process as any).domainOwnerGranted = jest.fn().mockResolvedValue(false)
|
|
15
|
-
;(process as any).superUserGranted = jest.fn().mockResolvedValue(false)
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
test('a role that holds the privilege answers yes, and the database is asked once per request', async () => {
|
|
19
|
-
User.hasPrivilege = jest.fn().mockResolvedValue(true)
|
|
20
|
-
const context = { state: { domain: DOMAIN, user: USER } }
|
|
21
|
-
expect(await requestHolds(context, 'course', 'query')).toBe(true)
|
|
22
|
-
expect(await requestHolds(context, 'course', 'query')).toBe(true)
|
|
23
|
-
expect(User.hasPrivilege).toHaveBeenCalledTimes(1)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
test('each privilege is asked on its own - query and mutation are two answers', async () => {
|
|
27
|
-
User.hasPrivilege = jest.fn().mockImplementation(async (privilege: string) => privilege === 'query')
|
|
28
|
-
const context = { state: { domain: DOMAIN, user: USER } }
|
|
29
|
-
expect(await requestHolds(context, 'course', 'query')).toBe(true)
|
|
30
|
-
expect(await requestHolds(context, 'course', 'mutation')).toBe(false)
|
|
31
|
-
expect(await requestHolds(context, 'learning', 'query')).toBe(true)
|
|
32
|
-
expect(User.hasPrivilege).toHaveBeenCalledTimes(3)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
test('the domain owner holds it, as `domainOwnerGranted: true` doors let them in', async () => {
|
|
36
|
-
User.hasPrivilege = jest.fn().mockResolvedValue(false)
|
|
37
|
-
;(process as any).domainOwnerGranted = jest.fn().mockResolvedValue(true)
|
|
38
|
-
expect(await requestHolds({ state: { domain: DOMAIN, user: USER } }, 'course', 'mutation')).toBe(true)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
test('a privilege prohibited for the request is not held', async () => {
|
|
42
|
-
User.hasPrivilege = jest.fn().mockResolvedValue(true)
|
|
43
|
-
const state = { domain: DOMAIN, user: USER, prohibitedPrivileges: [{ category: 'course', privilege: 'mutation', by: 'license' }] }
|
|
44
|
-
expect(await requestHolds({ state }, 'course', 'mutation')).toBe(false)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test('no domain or no user holds nothing', async () => {
|
|
48
|
-
User.hasPrivilege = jest.fn().mockResolvedValue(true)
|
|
49
|
-
expect(await requestHolds({ state: { user: USER } }, 'course')).toBe(false)
|
|
50
|
-
expect(await requestHolds({ state: { domain: DOMAIN } }, 'course')).toBe(false)
|
|
51
|
-
expect(await requestHolds(undefined, 'course')).toBe(false)
|
|
52
|
-
expect(User.hasPrivilege).not.toHaveBeenCalled()
|
|
53
|
-
})
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A role name that exists in this domain **and** in the one it inherits from.
|
|
3
|
-
*
|
|
4
|
-
* ── How the two rows come about ─────────────────────────────────────────────
|
|
5
|
-
* Roles are visible one level up: every read is scoped to `[domain.id, domain.parentId]`. The
|
|
6
|
-
* four duplicate checks in `role-mutation.ts` ask only about this domain, so a domain with a
|
|
7
|
-
* parent can be given a role whose name is already visible through it. Measured 2026-09-20:
|
|
8
|
-
* `createRole` accepts it, and both rows then come back from every list.
|
|
9
|
-
*
|
|
10
|
-
* ── What was settled, and when ──────────────────────────────────────────────
|
|
11
|
-
* ADR-0062 decision 3 (2026-09-20): the platform forbids it, and the operator may say otherwise.
|
|
12
|
-
* The name stays the same when they do — that is the point, because moving a domain off an
|
|
13
|
-
* inherited structure means standing up the same names here first (decision 2).
|
|
14
|
-
*
|
|
15
|
-
* Resolving the name was already deterministic before that, and had to be either way. `In([own, parent])`
|
|
16
|
-
* with no `ORDER BY` answered whichever the driver handed back first — the same call could
|
|
17
|
-
* answer the parent's role on one installation and this domain's on another, and a role is what
|
|
18
|
-
* a privilege check reads. So these cases pin **determinism and direction**, not the semantics.
|
|
19
|
-
*
|
|
20
|
-
* ⚠ sqlite (`tests/db.ts`). This harness cannot reproduce another driver's ordering, which is
|
|
21
|
-
* the point: the fix is to stop depending on ordering at all, and that is visible here as the
|
|
22
|
-
* parent's row never being the answer while this domain has one.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
|
|
26
|
-
import { loadCompiled } from './compiled'
|
|
27
|
-
|
|
28
|
-
const { Role } = loadCompiled('service/role/role')
|
|
29
|
-
const { RoleQuery } = loadCompiled('service/role/role-query')
|
|
30
|
-
const { RoleMutation } = loadCompiled('service/role/role-mutation')
|
|
31
|
-
const { User, UserStatus } = loadCompiled('service/user/user')
|
|
32
|
-
|
|
33
|
-
const query = new RoleQuery()
|
|
34
|
-
const mutation = new RoleMutation()
|
|
35
|
-
|
|
36
|
-
let parent: any
|
|
37
|
-
let child: any
|
|
38
|
-
let orphan: any
|
|
39
|
-
let actor: any
|
|
40
|
-
|
|
41
|
-
function context(d: any) {
|
|
42
|
-
return {
|
|
43
|
-
state: { domain: d, user: actor, tx: undefined },
|
|
44
|
-
throw: (code: number, message: string) => {
|
|
45
|
-
const error: any = new Error(message)
|
|
46
|
-
error.status = code
|
|
47
|
-
throw error
|
|
48
|
-
}
|
|
49
|
-
} as any
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** `context.state.domain` carries `parentId`; the repository does not fill it in for us. */
|
|
53
|
-
async function withParentId(domain: any, parentId: string | null) {
|
|
54
|
-
return { ...(await getRepository(Domain).findOneBy({ id: domain.id })), parentId }
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
beforeAll(async () => {
|
|
58
|
-
await openAuthDatabase()
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
afterAll(async () => {
|
|
62
|
-
await closeAuthDatabase()
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
beforeEach(async () => {
|
|
66
|
-
await resetAuthDatabase()
|
|
67
|
-
|
|
68
|
-
const p = await getRepository(Domain).save({ name: 'system', subdomain: 'system' })
|
|
69
|
-
const c = await getRepository(Domain).save({ name: 'acme', subdomain: 'acme', parent: p })
|
|
70
|
-
const o = await getRepository(Domain).save({ name: 'beta', subdomain: 'beta' })
|
|
71
|
-
|
|
72
|
-
parent = await withParentId(p, null)
|
|
73
|
-
child = await withParentId(c, p.id)
|
|
74
|
-
orphan = await withParentId(o, null)
|
|
75
|
-
|
|
76
|
-
actor = await getRepository(User).save({
|
|
77
|
-
username: 'operator@acme.z',
|
|
78
|
-
email: 'operator@acme.z',
|
|
79
|
-
name: 'operator',
|
|
80
|
-
status: UserStatus.ACTIVATED,
|
|
81
|
-
domains: [c]
|
|
82
|
-
})
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
/** 거절의 이름을 본다 — 영어 문장이 아니라 `code` 가 계약이다(ADR-0054). */
|
|
86
|
-
async function refusalCodeOf(promise: Promise<unknown>): Promise<string> {
|
|
87
|
-
try {
|
|
88
|
-
await promise
|
|
89
|
-
} catch (error: any) {
|
|
90
|
-
return error?.code || error?.extensions?.refusal?.code || `not a refusal: ${error?.message}`
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return 'nothing was thrown'
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** 거절이 싣고 온 값. 화면이 물음을 만드는 재료다. */
|
|
97
|
-
async function refusalParamsOf(promise: Promise<unknown>): Promise<Record<string, any>> {
|
|
98
|
-
try {
|
|
99
|
-
await promise
|
|
100
|
-
} catch (error: any) {
|
|
101
|
-
return error?.params || error?.extensions?.refusal?.params || {}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return {}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function roleIn(domain: any, name: string, description: string) {
|
|
108
|
-
return await getRepository(Role).save({ name, description, domain: { id: domain.id } })
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
describe('one role, no ambiguity', () => {
|
|
112
|
-
it('answers this domain own role', async () => {
|
|
113
|
-
await roleIn(child, 'approver', 'ours')
|
|
114
|
-
|
|
115
|
-
expect(await query.role('approver', context(child))).toMatchObject({ description: 'ours' })
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
it('answers a role that only the parent has', async () => {
|
|
119
|
-
/* Inheritance still works — this is the case the whole one-level scope exists for. */
|
|
120
|
-
await roleIn(parent, 'auditor', 'theirs')
|
|
121
|
-
|
|
122
|
-
expect(await query.role('auditor', context(child))).toMatchObject({ description: 'theirs' })
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
it('answers nothing for a name neither has', async () => {
|
|
126
|
-
expect(await query.role('nobody', context(child))).toBeNull()
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
it('does not reach a parent that is not ours', async () => {
|
|
130
|
-
/* A root domain inherits from nobody, and another tenant is not a parent. */
|
|
131
|
-
await roleIn(parent, 'auditor', 'theirs')
|
|
132
|
-
|
|
133
|
-
expect(await query.role('auditor', context(orphan))).toBeNull()
|
|
134
|
-
})
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
describe('the same name in both', () => {
|
|
138
|
-
it('is refused unless the operator says this domain stands its own', async () => {
|
|
139
|
-
/*
|
|
140
|
-
* ⚠ ADR-0062 decision 3, settled 2026-09-20. Until then the duplicate checks looked at this
|
|
141
|
-
* domain only and nothing refused a name already visible through the parent, so the two rows
|
|
142
|
-
* appeared silently.
|
|
143
|
-
*
|
|
144
|
-
* The refusal names the domain the other one stands in, because that is what the operator
|
|
145
|
-
* needs to decide — they may not have known the parent had it.
|
|
146
|
-
*/
|
|
147
|
-
await roleIn(parent, 'approver', 'theirs')
|
|
148
|
-
|
|
149
|
-
expect(await refusalCodeOf(mutation.createRole({ name: 'approver' } as any, context(child)))).toEqual(
|
|
150
|
-
'role-name-stands-inherited'
|
|
151
|
-
)
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
it('names the domain the other one stands in', async () => {
|
|
155
|
-
/*
|
|
156
|
-
* ⚠ What makes the refusal answerable. The operator's next move is to decide whether this
|
|
157
|
-
* domain stands its own of that name, and they cannot decide without knowing where the other
|
|
158
|
-
* one is — they may not have known the parent had it. A refusal that only said "duplicated"
|
|
159
|
-
* would leave them guessing.
|
|
160
|
-
*
|
|
161
|
-
* The value rides in `params`, not in the sentence: the sentence is built in the reader's
|
|
162
|
-
* language and the screen puts its own question together from these (ADR-0054).
|
|
163
|
-
*/
|
|
164
|
-
await roleIn(parent, 'approver', 'theirs')
|
|
165
|
-
|
|
166
|
-
expect(await refusalParamsOf(mutation.createRole({ name: 'approver' } as any, context(child)))).toMatchObject({
|
|
167
|
-
name: 'approver',
|
|
168
|
-
domain: 'system'
|
|
169
|
-
})
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
it('stands when the operator does say so, under the same name', async () => {
|
|
173
|
-
/*
|
|
174
|
-
* ⚠ The escape is not "call it something else". Standing up the same names here is the first
|
|
175
|
-
* step of moving a domain off an inherited structure (decision 2) — the child's role replaces
|
|
176
|
-
* what it inherits, and cutting the parent last leaves one name again. Requiring a different
|
|
177
|
-
* name would close that route.
|
|
178
|
-
*/
|
|
179
|
-
await roleIn(parent, 'approver', 'theirs')
|
|
180
|
-
|
|
181
|
-
const created = await mutation.createRole(
|
|
182
|
-
{ name: 'approver', description: 'ours', separately: true } as any,
|
|
183
|
-
context(child)
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
expect(created).toMatchObject({ name: 'approver', description: 'ours' })
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
it('refuses when the name stands in both, whatever the operator says', async () => {
|
|
190
|
-
/*
|
|
191
|
-
* ⚠ The state the migration of decision 2 passes through for its whole length: this domain's
|
|
192
|
-
* own role is standing beside the inherited one until the parent is cut, last. Trying to
|
|
193
|
-
* stand a second one during that window must be refused as `here` — there is already one
|
|
194
|
-
* here — and the operator's "stand it separately" must not reach it.
|
|
195
|
-
*
|
|
196
|
-
* It depends on the lookup asking this domain first. A single read over both domains with no
|
|
197
|
-
* ORDER BY can answer with the parent's row, and then the refusal is the inherited one,
|
|
198
|
-
* which `separately` is allowed to pass. That was the same defect this file already pins for
|
|
199
|
-
* `role(name)`, reintroduced in the checking path.
|
|
200
|
-
*
|
|
201
|
-
* ⚠ This case could not see that version of it. sqlite answered the combined query with this
|
|
202
|
-
* domain's row, so the outcome was right by accident — measured 2026-09-20 by putting the old
|
|
203
|
-
* shape back: 448 tests, none red. `role-name-lookup-sentinel.test.ts` reads the query before
|
|
204
|
-
* it is sent, which is where that shape is reachable from here. What this case does catch,
|
|
205
|
-
* now that the order is in our code rather than the driver's, is the order being wrong.
|
|
206
|
-
*/
|
|
207
|
-
await roleIn(parent, 'approver', 'theirs')
|
|
208
|
-
await roleIn(child, 'approver', 'ours')
|
|
209
|
-
|
|
210
|
-
expect(
|
|
211
|
-
await refusalCodeOf(mutation.createRole({ name: 'approver', separately: true } as any, context(child)))
|
|
212
|
-
).toEqual('role-name-taken-here')
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
it('refuses the same way whichever row was written first', async () => {
|
|
216
|
-
await roleIn(child, 'approver', 'ours')
|
|
217
|
-
await roleIn(parent, 'approver', 'theirs')
|
|
218
|
-
|
|
219
|
-
expect(
|
|
220
|
-
await refusalCodeOf(mutation.createRole({ name: 'approver', separately: true } as any, context(child)))
|
|
221
|
-
).toEqual('role-name-taken-here')
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
it('refuses a name this domain already has, whatever the operator says', async () => {
|
|
225
|
-
/*
|
|
226
|
-
* ⚠ Not something to opt into. Two rows in one domain under one name would be
|
|
227
|
-
* indistinguishable in every respect — nothing for a screen to show, nothing to choose
|
|
228
|
-
* between.
|
|
229
|
-
*/
|
|
230
|
-
await roleIn(child, 'approver', 'ours')
|
|
231
|
-
|
|
232
|
-
expect(
|
|
233
|
-
await refusalCodeOf(mutation.createRole({ name: 'approver', separately: true } as any, context(child)))
|
|
234
|
-
).toEqual('role-name-taken-here')
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
it('leaves a name that stands only in some third domain alone', async () => {
|
|
238
|
-
/* Another tenant's naming does not reach us. */
|
|
239
|
-
await roleIn(orphan, 'approver', 'theirs')
|
|
240
|
-
|
|
241
|
-
const created = await mutation.createRole({ name: 'approver', description: 'ours' } as any, context(child))
|
|
242
|
-
|
|
243
|
-
expect(created).toMatchObject({ description: 'ours' })
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
it('answers this domain own, never the parent', async () => {
|
|
247
|
-
/*
|
|
248
|
-
* ⚠ The case the fix is for. Before it, both rows matched one `In([own, parent])` with no
|
|
249
|
-
* ORDER BY, so the answer was whichever row the driver handed back first.
|
|
250
|
-
*/
|
|
251
|
-
await roleIn(parent, 'approver', 'theirs')
|
|
252
|
-
await roleIn(child, 'approver', 'ours')
|
|
253
|
-
|
|
254
|
-
expect(await query.role('approver', context(child))).toMatchObject({ description: 'ours' })
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
it('answers the same way whichever row was written first', async () => {
|
|
258
|
-
/* ⚠ Insertion order is the thing that must stop mattering. */
|
|
259
|
-
await roleIn(child, 'approver', 'ours')
|
|
260
|
-
await roleIn(parent, 'approver', 'theirs')
|
|
261
|
-
|
|
262
|
-
expect(await query.role('approver', context(child))).toMatchObject({ description: 'ours' })
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
it('leaves the parent domain reading its own', async () => {
|
|
266
|
-
/* Shadowing runs one way. The parent is not affected by what a child called something. */
|
|
267
|
-
await roleIn(parent, 'approver', 'theirs')
|
|
268
|
-
await roleIn(child, 'approver', 'ours')
|
|
269
|
-
|
|
270
|
-
expect(await query.role('approver', context(parent))).toMatchObject({ description: 'theirs' })
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
it('still shows both to a screen that lists them', async () => {
|
|
274
|
-
/*
|
|
275
|
-
* ⚠ Deliberate, and the reason the screen has to say where each came from. Resolving a name
|
|
276
|
-
* is not the same act as offering a list to choose from: hiding the parent's row here would
|
|
277
|
-
* silently drop a role somebody may still be assigned.
|
|
278
|
-
*/
|
|
279
|
-
await roleIn(parent, 'approver', 'theirs')
|
|
280
|
-
await roleIn(child, 'approver', 'ours')
|
|
281
|
-
|
|
282
|
-
const offered = await query.userRoles(actor.id, context(child))
|
|
283
|
-
|
|
284
|
-
expect(offered.filter(role => role.name === 'approver')).toHaveLength(2)
|
|
285
|
-
})
|
|
286
|
-
})
|
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 역할 만들기·고치기·지우기 — **실패했을 때 무엇을 말하나**를 실 DB 로 태운다.
|
|
3
|
-
*
|
|
4
|
-
* 이 셋은 권한 편집 화면이 서는 자리인데, 세 갈래의 실패가 전부 잘못된 말로 나가고 있었다.
|
|
5
|
-
*
|
|
6
|
-
* ① 부모 도메인의 역할을 고치려 하면 `role.name` 에서 TypeError → 500. 화면은 이유를 모른다.
|
|
7
|
-
* 목록은 상속으로 부모 역할까지 보여 주므로(`inherited: Include`) **실제로 자주 닿는다.**
|
|
8
|
-
* ② `privileges` 없는 patch(이름만 고치기)도 `patch.privileges.map` 에서 500.
|
|
9
|
-
* ③ `deleteRole` 의 catch 가 무엇이 났든 「사용 중이라 지울 수 없다」로 401 을 던졌다 —
|
|
10
|
-
* 못 찾은 것도 그렇게 말했다. 관리자는 있지도 않은 사용자를 떼러 갔다.
|
|
11
|
-
*
|
|
12
|
-
* 그리고 `deleteRoles`(복수)는 **도메인을 아예 안 봤다** — id 만 알면 남의 도메인 역할이 지워졌다.
|
|
13
|
-
*
|
|
14
|
-
* 지시자는 여기서 안 돈다(스키마 층이다). 게이트는 `privilege-directive.test.ts` 가 본다.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
|
|
18
|
-
import { authDataSource } from './db'
|
|
19
|
-
import { loadCompiled } from './compiled'
|
|
20
|
-
|
|
21
|
-
const { Role } = loadCompiled('service/role/role')
|
|
22
|
-
const { Privilege } = loadCompiled('service/privilege/privilege')
|
|
23
|
-
const { User } = loadCompiled('service/user/user')
|
|
24
|
-
const { RoleMutation } = loadCompiled('service/role/role-mutation')
|
|
25
|
-
|
|
26
|
-
const mutation = new RoleMutation()
|
|
27
|
-
|
|
28
|
-
let parent: any
|
|
29
|
-
let child: any
|
|
30
|
-
let stranger: any
|
|
31
|
-
let actor: any
|
|
32
|
-
let boardQuery: any
|
|
33
|
-
let boardMutation: any
|
|
34
|
-
|
|
35
|
-
async function loadDomain(id: string) {
|
|
36
|
-
return await getRepository(Domain).findOneBy({ id })
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** 리졸버가 실제로 쓰는 것만 담는다 — `tx`·`domain`·`user`·`t`·`throw`. */
|
|
40
|
-
function context(domain: any) {
|
|
41
|
-
return {
|
|
42
|
-
state: { domain, user: actor, tx: authDataSource().manager },
|
|
43
|
-
t: (key: string, params: any = {}) => `${key}${params.x ? `:${params.x}` : ''}`,
|
|
44
|
-
throw: (status: number, message: string) => {
|
|
45
|
-
throw new Error(`${status} ${message}`)
|
|
46
|
-
}
|
|
47
|
-
} as any
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
beforeAll(async () => {
|
|
51
|
-
await openAuthDatabase()
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
afterAll(async () => {
|
|
55
|
-
await closeAuthDatabase()
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
beforeEach(async () => {
|
|
59
|
-
await resetAuthDatabase()
|
|
60
|
-
|
|
61
|
-
const domainRepository = getRepository(Domain)
|
|
62
|
-
const savedParent = await domainRepository.save({ name: 'HQ', subdomain: 'hq' })
|
|
63
|
-
const savedChild = await domainRepository.save({ name: 'Plant A', subdomain: 'plant-a', parent: savedParent })
|
|
64
|
-
const savedStranger = await domainRepository.save({ name: 'Other', subdomain: 'other' })
|
|
65
|
-
|
|
66
|
-
parent = await loadDomain(savedParent.id)
|
|
67
|
-
child = await loadDomain(savedChild.id)
|
|
68
|
-
stranger = await loadDomain(savedStranger.id)
|
|
69
|
-
|
|
70
|
-
const privilegeRepository = getRepository(Privilege)
|
|
71
|
-
boardQuery = await privilegeRepository.save({ name: 'query', category: 'board' })
|
|
72
|
-
boardMutation = await privilegeRepository.save({ name: 'mutation', category: 'board' })
|
|
73
|
-
|
|
74
|
-
const salt = User.generateSalt()
|
|
75
|
-
actor = await getRepository(User).save({
|
|
76
|
-
username: 'admin',
|
|
77
|
-
email: 'admin@example.com',
|
|
78
|
-
name: 'Admin',
|
|
79
|
-
salt,
|
|
80
|
-
status: 'activated',
|
|
81
|
-
domains: [child]
|
|
82
|
-
})
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
async function makeRole(name: string, domain: any, privileges: any[] = [], users: any[] = []) {
|
|
86
|
-
return await getRepository(Role).save({ name, domain, privileges, users })
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* 거절의 이름을 본다. 영어 문장이 아니라 `code` 가 계약이다(ADR-0054) — 문장은 요청의 언어가
|
|
91
|
-
* 알려진 자리에서 만들어지므로, 문장을 시험에 박으면 번역이 붙는 날 같이 깨진다.
|
|
92
|
-
*/
|
|
93
|
-
async function refusalCodeOf(promise: Promise<unknown>): Promise<string> {
|
|
94
|
-
try {
|
|
95
|
-
await promise
|
|
96
|
-
} catch (error: any) {
|
|
97
|
-
return error?.code || error?.extensions?.refusal?.code || `not a refusal: ${error?.message}`
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return 'nothing was thrown'
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
describe('고치기', () => {
|
|
104
|
-
it('권한을 갈아 끼운다', async () => {
|
|
105
|
-
const role = await makeRole('viewer', child, [boardQuery])
|
|
106
|
-
|
|
107
|
-
await mutation.updateRole(role.id, { privileges: [{ id: boardMutation.id }] } as any, context(child))
|
|
108
|
-
|
|
109
|
-
const saved = await getRepository(Role).findOne({ where: { id: role.id }, relations: ['privileges'] })
|
|
110
|
-
expect(saved.privileges.map((p: any) => p.id)).toEqual([boardMutation.id])
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it('`privileges` 를 안 보내면 권한을 건드리지 않는다 — 이름만 고치는 호출이 있다', async () => {
|
|
114
|
-
/* 예전에는 이 호출이 `patch.privileges.map` 에서 TypeError 로 500 이었다. */
|
|
115
|
-
const role = await makeRole('viewer', child, [boardQuery])
|
|
116
|
-
|
|
117
|
-
await mutation.updateRole(role.id, { name: 'reader' } as any, context(child))
|
|
118
|
-
|
|
119
|
-
const saved = await getRepository(Role).findOne({ where: { id: role.id }, relations: ['privileges'] })
|
|
120
|
-
expect(saved.name).toBe('reader')
|
|
121
|
-
expect(saved.privileges.map((p: any) => p.id)).toEqual([boardQuery.id])
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it('부모 도메인의 역할은 어디서 고치는지 말하고 거절한다', async () => {
|
|
125
|
-
/*
|
|
126
|
-
* 목록에 보이는 것과 고칠 수 있는 것이 다르다. 예전에는 여기서 TypeError 가 나 500 이었고
|
|
127
|
-
* 화면은 「알 수 없는 오류」만 보여 줬다.
|
|
128
|
-
*/
|
|
129
|
-
const role = await makeRole('shared', parent, [boardQuery])
|
|
130
|
-
|
|
131
|
-
await expect(mutation.updateRole(role.id, { name: 'mine' } as any, context(child))).rejects.toThrow(
|
|
132
|
-
/parent domain/
|
|
133
|
-
)
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
it('같은 이름이 이미 있으면 거절한다', async () => {
|
|
137
|
-
await makeRole('viewer', child)
|
|
138
|
-
const role = await makeRole('editor', child)
|
|
139
|
-
|
|
140
|
-
expect(await refusalCodeOf(mutation.updateRole(role.id, { name: 'viewer' } as any, context(child)))).toEqual(
|
|
141
|
-
'role-name-taken-here'
|
|
142
|
-
)
|
|
143
|
-
})
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
describe('지우기 — 하나', () => {
|
|
147
|
-
it('아무도 안 가진 역할은 지운다', async () => {
|
|
148
|
-
const role = await makeRole('unused', child, [boardQuery])
|
|
149
|
-
|
|
150
|
-
expect(await mutation.deleteRole(role.id, context(child))).toBe(true)
|
|
151
|
-
expect(await getRepository(Role).findOneBy({ id: role.id })).toBeNull()
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
it('가진 사람이 있으면 그 이유로 거절한다', async () => {
|
|
155
|
-
const role = await makeRole('in-use', child, [boardQuery], [actor])
|
|
156
|
-
|
|
157
|
-
expect(await refusalCodeOf(mutation.deleteRole(role.id, context(child)))).toBe('role-in-use')
|
|
158
|
-
expect(await getRepository(Role).findOneBy({ id: role.id })).not.toBeNull()
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it('부모 도메인의 역할을 「사용 중」이라고 말하지 않는다', async () => {
|
|
162
|
-
/*
|
|
163
|
-
* 이것이 옛 구현의 가장 나쁜 점이었다 — 못 찾은 것을 「사용 중」이라고 말했으므로 관리자는
|
|
164
|
-
* 있지도 않은 사용자를 찾아 떼려 했다.
|
|
165
|
-
*/
|
|
166
|
-
const role = await makeRole('shared', parent)
|
|
167
|
-
|
|
168
|
-
await expect(mutation.deleteRole(role.id, context(child))).rejects.toThrow(/parent domain/)
|
|
169
|
-
await expect(mutation.deleteRole(role.id, context(child))).rejects.not.toThrow(/in use/)
|
|
170
|
-
})
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
describe('지우기 — 여럿', () => {
|
|
174
|
-
it('남의 도메인 역할은 id 를 알아도 안 지운다', async () => {
|
|
175
|
-
const mine = await makeRole('mine', child)
|
|
176
|
-
const theirs = await makeRole('theirs', stranger)
|
|
177
|
-
|
|
178
|
-
await mutation.deleteRoles([mine.id, theirs.id], context(child))
|
|
179
|
-
|
|
180
|
-
expect(await getRepository(Role).findOneBy({ id: mine.id })).toBeNull()
|
|
181
|
-
expect(await getRepository(Role).findOneBy({ id: theirs.id })).not.toBeNull()
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
it('★ 사용 중인 역할이 하나라도 있으면 아무것도 안 지우고 그 이름과 사람 수로 거절한다 — 하나 지우기와 같은 거절이다', async () => {
|
|
185
|
-
const unused = await makeRole('unused', child)
|
|
186
|
-
const inUse = await makeRole('in-use', child, [], [actor])
|
|
187
|
-
|
|
188
|
-
let refusal: any
|
|
189
|
-
try {
|
|
190
|
-
await mutation.deleteRoles([unused.id, inUse.id], context(child))
|
|
191
|
-
} catch (error) {
|
|
192
|
-
refusal = error
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
expect(refusal?.code).toBe('role-in-use')
|
|
196
|
-
expect(refusal?.params).toEqual({ names: 'in-use (1)' })
|
|
197
|
-
expect(await getRepository(Role).findOneBy({ id: unused.id })).not.toBeNull()
|
|
198
|
-
expect(await getRepository(Role).findOneBy({ id: inUse.id })).not.toBeNull()
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
it('아무도 안 가진 역할은 함께 지운다', async () => {
|
|
202
|
-
const one = await makeRole('one', child)
|
|
203
|
-
const two = await makeRole('two', child)
|
|
204
|
-
|
|
205
|
-
expect(await mutation.deleteRoles([one.id, two.id], context(child))).toBe(true)
|
|
206
|
-
expect(await getRepository(Role).findOneBy({ id: one.id })).toBeNull()
|
|
207
|
-
expect(await getRepository(Role).findOneBy({ id: two.id })).toBeNull()
|
|
208
|
-
})
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* 격자에서 여러 줄을 고쳐 한 번에 보내는 길.
|
|
213
|
-
*
|
|
214
|
-
* 목록 화면이 `dirtyRecords` 를 `cuFlag` 와 함께 보낸다. 여기서 지키는 것은 하나가 제일 크다 —
|
|
215
|
-
* **`privileges` 를 안 보낸 patch 가 권한을 비우지 않는 것.** 격자는 이름만 고치는데 그것이
|
|
216
|
-
* 권한을 지우면 그 역할이 아무것도 안 여는 역할이 된다.
|
|
217
|
-
*/
|
|
218
|
-
describe('격자에서 여럿 저장', () => {
|
|
219
|
-
it('`+` 는 만들고 `M` 은 고친다', async () => {
|
|
220
|
-
const existing = await makeRole('viewer', child, [boardQuery])
|
|
221
|
-
|
|
222
|
-
await mutation.updateMultipleRole(
|
|
223
|
-
[
|
|
224
|
-
{ cuFlag: '+', name: 'operator', description: '현장' },
|
|
225
|
-
{ cuFlag: 'M', id: existing.id, name: 'reader' }
|
|
226
|
-
] as any,
|
|
227
|
-
context(child)
|
|
228
|
-
)
|
|
229
|
-
|
|
230
|
-
const roles = await getRepository(Role).find({ where: { domain: { id: child.id } } })
|
|
231
|
-
expect(roles.map((r: any) => r.name).sort()).toEqual(['operator', 'reader'])
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
it('이름만 고쳐도 권한은 그대로다 — 격자는 권한을 안 보낸다', async () => {
|
|
235
|
-
/*
|
|
236
|
-
* 이것이 이 자리의 가장 조용한 결함이 될 수 있었다. 관리자가 격자에서 오타를 고쳤는데
|
|
237
|
-
* 그 역할이 아무것도 안 여는 역할이 되면, 그 역할을 가진 사람들이 다음 로그인에 막힌다.
|
|
238
|
-
*/
|
|
239
|
-
const role = await makeRole('viewer', child, [boardQuery, boardMutation])
|
|
240
|
-
|
|
241
|
-
await mutation.updateMultipleRole([{ cuFlag: 'M', id: role.id, name: 'reader' }] as any, context(child))
|
|
242
|
-
|
|
243
|
-
const saved = await getRepository(Role).findOne({ where: { id: role.id }, relations: ['privileges'] })
|
|
244
|
-
expect(saved.privileges.map((p: any) => p.id).sort()).toEqual([boardMutation.id, boardQuery.id].sort())
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
it('`cuFlag` 가 없는 줄은 아무것도 하지 않는다 — 짐작해서 만들지 않는다', async () => {
|
|
248
|
-
await mutation.updateMultipleRole([{ name: 'ghost' }] as any, context(child))
|
|
249
|
-
|
|
250
|
-
expect(await getRepository(Role).findOneBy({ name: 'ghost' })).toBeNull()
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
it('부모 도메인의 역할을 고치려 하면 어디서 고치는지 말한다', async () => {
|
|
254
|
-
const role = await makeRole('shared', parent)
|
|
255
|
-
|
|
256
|
-
await expect(
|
|
257
|
-
mutation.updateMultipleRole([{ cuFlag: 'M', id: role.id, name: 'mine' }] as any, context(child))
|
|
258
|
-
).rejects.toThrow(/parent domain/)
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
it('이름 없이 만들려 하면 거절한다', async () => {
|
|
262
|
-
await expect(mutation.updateMultipleRole([{ cuFlag: '+', name: ' ' }] as any, context(child))).rejects.toThrow()
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
it('같은 이름으로 만들려 하면 거절한다', async () => {
|
|
266
|
-
await makeRole('operator', child)
|
|
267
|
-
|
|
268
|
-
expect(
|
|
269
|
-
await refusalCodeOf(mutation.updateMultipleRole([{ cuFlag: '+', name: 'operator' }] as any, context(child)))
|
|
270
|
-
).toEqual('role-name-taken-here')
|
|
271
|
-
})
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
describe('만들기', () => {
|
|
275
|
-
it('내 도메인에 만들고 고른 권한을 붙인다', async () => {
|
|
276
|
-
const created = await mutation.createRole(
|
|
277
|
-
{ name: 'operator', privileges: [{ id: boardQuery.id }] } as any,
|
|
278
|
-
context(child)
|
|
279
|
-
)
|
|
280
|
-
|
|
281
|
-
const saved = await getRepository(Role).findOne({ where: { id: created.id }, relations: ['privileges', 'domain'] })
|
|
282
|
-
expect(saved.domain.id).toBe(child.id)
|
|
283
|
-
expect(saved.privileges.map((p: any) => p.id)).toEqual([boardQuery.id])
|
|
284
|
-
})
|
|
285
|
-
|
|
286
|
-
it('같은 도메인에 같은 이름은 안 만든다', async () => {
|
|
287
|
-
await makeRole('operator', child)
|
|
288
|
-
|
|
289
|
-
expect(await refusalCodeOf(mutation.createRole({ name: 'operator' } as any, context(child)))).toEqual(
|
|
290
|
-
'role-name-taken-here'
|
|
291
|
-
)
|
|
292
|
-
})
|
|
293
|
-
})
|