@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,190 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deleting an application has to stop the clients bound to it.
|
|
3
|
-
*
|
|
4
|
-
* ── The asymmetry this came from ────────────────────────────────────────────
|
|
5
|
-
* An appliance and an application both have a shadow `User` standing in for the machine, and
|
|
6
|
-
* that user is the thing a token names. `deleteAppliance` deletes it along with the appliance.
|
|
7
|
-
* `deleteApplication` deleted only the application row, and the shadow users stayed behind:
|
|
8
|
-
*
|
|
9
|
-
* ACTIVATED, so `User.checkAuth` passes every token naming them — it looks the user up by the
|
|
10
|
-
* id in the token and never asks whether the application still exists
|
|
11
|
-
*
|
|
12
|
-
* holding a refresh token in `password`, which is what `/oauth2/refresh-token` finds a binding
|
|
13
|
-
* by, so each refresh mints another year of them
|
|
14
|
-
*
|
|
15
|
-
* So the screen said the application was gone and its clients kept working, with no way left on
|
|
16
|
-
* any screen to reach them — the list they would have appeared in is keyed on an application
|
|
17
|
-
* that no longer exists.
|
|
18
|
-
*
|
|
19
|
-
* ⚠ sqlite (`tests/db.ts`).
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
|
|
23
|
-
import { loadCompiled } from './compiled'
|
|
24
|
-
|
|
25
|
-
const { Application } = loadCompiled('service/application/application')
|
|
26
|
-
const { User, UserStatus } = loadCompiled('service/user/user')
|
|
27
|
-
const { ApplicationMutation } = loadCompiled('service/application/application-mutation')
|
|
28
|
-
|
|
29
|
-
const mutation = new ApplicationMutation()
|
|
30
|
-
|
|
31
|
-
let domain: any
|
|
32
|
-
let other: any
|
|
33
|
-
let actor: any
|
|
34
|
-
|
|
35
|
-
function context(d: any = domain) {
|
|
36
|
-
return {
|
|
37
|
-
state: { domain: d, user: actor, tx: undefined },
|
|
38
|
-
throw: (code: number, message: string) => {
|
|
39
|
-
const error: any = new Error(message)
|
|
40
|
-
error.status = code
|
|
41
|
-
throw error
|
|
42
|
-
}
|
|
43
|
-
} as any
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function makeApplication(name: string, d: any = domain) {
|
|
47
|
-
return await mutation.createApplication(
|
|
48
|
-
{
|
|
49
|
-
name,
|
|
50
|
-
email: `${name}@acme.z`,
|
|
51
|
-
url: `https://${name}.example.com`,
|
|
52
|
-
redirectUrl: `https://${name}.example.com/callback`,
|
|
53
|
-
type: 'OTHERS'
|
|
54
|
-
},
|
|
55
|
-
context(d)
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** A binding, as the OAuth2 exchange leaves one: a shadow user holding a refresh token. */
|
|
60
|
-
async function bind(application: any, label: string, d: any = domain) {
|
|
61
|
-
const appuser = await getRepository(User).save({
|
|
62
|
-
username: `${label}@acme`,
|
|
63
|
-
email: `${label}@acme`,
|
|
64
|
-
name: label,
|
|
65
|
-
userType: 'application',
|
|
66
|
-
reference: application.id,
|
|
67
|
-
status: UserStatus.ACTIVATED,
|
|
68
|
-
domains: [d]
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
const refreshToken = Application.generateRefreshToken(d, appuser, application, 'admin')
|
|
72
|
-
|
|
73
|
-
return await getRepository(User).save({ ...appuser, password: refreshToken })
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function bindingsOf(applicationId: string) {
|
|
77
|
-
return await getRepository(User).findBy({ reference: applicationId, userType: 'application' })
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
beforeAll(async () => {
|
|
81
|
-
await openAuthDatabase()
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
afterAll(async () => {
|
|
85
|
-
await closeAuthDatabase()
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
beforeEach(async () => {
|
|
89
|
-
await resetAuthDatabase()
|
|
90
|
-
|
|
91
|
-
domain = await getRepository(Domain).save({ name: 'acme', subdomain: 'acme' })
|
|
92
|
-
other = await getRepository(Domain).save({ name: 'beta', subdomain: 'beta' })
|
|
93
|
-
actor = await getRepository(User).save({
|
|
94
|
-
username: 'operator@acme.z',
|
|
95
|
-
email: 'operator@acme.z',
|
|
96
|
-
name: 'operator',
|
|
97
|
-
status: UserStatus.ACTIVATED,
|
|
98
|
-
domains: [domain]
|
|
99
|
-
})
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
describe('deleting an application', () => {
|
|
103
|
-
it('takes its bindings with it', async () => {
|
|
104
|
-
/*
|
|
105
|
-
* ⚠ The case. A binding left behind is a live credential with no screen to reach it from —
|
|
106
|
-
* the list it would appear in is keyed on an application that is gone.
|
|
107
|
-
*/
|
|
108
|
-
const application = await makeApplication('billing')
|
|
109
|
-
await bind(application, 'binding-a')
|
|
110
|
-
await bind(application, 'binding-b')
|
|
111
|
-
|
|
112
|
-
await mutation.deleteApplication(application.id, context())
|
|
113
|
-
|
|
114
|
-
expect(await bindingsOf(application.id)).toHaveLength(0)
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
it('leaves no token that checkAuth would still accept', async () => {
|
|
118
|
-
/*
|
|
119
|
-
* ⚠ Why the row mattering is not enough to say. `checkAuth` looks the subject up by the id
|
|
120
|
-
* baked into the token and never asks whether the application exists, so a surviving user
|
|
121
|
-
* authenticates whatever happened to the application row.
|
|
122
|
-
*/
|
|
123
|
-
const application = await makeApplication('billing')
|
|
124
|
-
const binding = await bind(application, 'binding-a')
|
|
125
|
-
|
|
126
|
-
await mutation.deleteApplication(application.id, context())
|
|
127
|
-
|
|
128
|
-
await expect(
|
|
129
|
-
User.checkAuth({ id: binding.id, userType: 'application', domain: { subdomain: 'acme' } })
|
|
130
|
-
).rejects.toThrow()
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
it('deletes the application row itself', async () => {
|
|
134
|
-
const application = await makeApplication('billing')
|
|
135
|
-
|
|
136
|
-
await mutation.deleteApplication(application.id, context())
|
|
137
|
-
|
|
138
|
-
expect(await getRepository(Application).findOneBy({ id: application.id })).toBeNull()
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
it('leaves another application and its bindings alone', async () => {
|
|
142
|
-
const going = await makeApplication('billing')
|
|
143
|
-
const staying = await makeApplication('shipping')
|
|
144
|
-
|
|
145
|
-
await bind(going, 'binding-a')
|
|
146
|
-
const kept = await bind(staying, 'binding-b')
|
|
147
|
-
|
|
148
|
-
await mutation.deleteApplication(going.id, context())
|
|
149
|
-
|
|
150
|
-
expect(await bindingsOf(staying.id)).toHaveLength(1)
|
|
151
|
-
expect(await getRepository(User).findOneBy({ id: kept.id })).toMatchObject({ status: UserStatus.ACTIVATED })
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
it('does not reach into another tenant', async () => {
|
|
155
|
-
/*
|
|
156
|
-
* The delete is scoped by domain, so an id from elsewhere matches nothing. The bindings must
|
|
157
|
-
* not be cleared on the way to finding that out.
|
|
158
|
-
*/
|
|
159
|
-
const theirs = await makeApplication('billing', other)
|
|
160
|
-
await bind(theirs, 'binding-theirs', other)
|
|
161
|
-
|
|
162
|
-
await mutation.deleteApplication(theirs.id, context(domain))
|
|
163
|
-
|
|
164
|
-
expect(await getRepository(Application).findOneBy({ id: theirs.id })).not.toBeNull()
|
|
165
|
-
expect(await bindingsOf(theirs.id)).toHaveLength(1)
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
it('does not touch an appliance that happens to carry the same reference', async () => {
|
|
169
|
-
/*
|
|
170
|
-
* `reference` is a plain column shared by both kinds of shadow user, so the delete has to
|
|
171
|
-
* name `userType` as well. Two ids never collide in practice; the assertion is about the
|
|
172
|
-
* filter, not about the odds.
|
|
173
|
-
*/
|
|
174
|
-
const application = await makeApplication('billing')
|
|
175
|
-
|
|
176
|
-
const applianceUser = await getRepository(User).save({
|
|
177
|
-
username: 'device@acme',
|
|
178
|
-
email: 'device@acme',
|
|
179
|
-
name: 'device',
|
|
180
|
-
userType: 'appliance',
|
|
181
|
-
reference: application.id,
|
|
182
|
-
status: UserStatus.ACTIVATED,
|
|
183
|
-
domains: [domain]
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
await mutation.deleteApplication(application.id, context())
|
|
187
|
-
|
|
188
|
-
expect(await getRepository(User).findOneBy({ id: applianceUser.id })).not.toBeNull()
|
|
189
|
-
})
|
|
190
|
-
})
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* What an application's tokens carry in their payload.
|
|
3
|
-
*
|
|
4
|
-
* ── Why this is worth a file of its own ─────────────────────────────────────
|
|
5
|
-
* An application has two 32-character hex strings and they are opposite kinds of thing:
|
|
6
|
-
*
|
|
7
|
-
* appKey the client id. Public. It is what a token says it was issued to, and what the
|
|
8
|
-
* OAuth2 refresh exchange looks the application up by.
|
|
9
|
-
* appSecret the client secret. It is what `/oauth2/access-token` compares against to decide
|
|
10
|
-
* the caller is that client.
|
|
11
|
-
*
|
|
12
|
-
* `Application.sign` takes the first in a positional slot, and a JWT payload is not encrypted —
|
|
13
|
-
* anyone holding the token reads it. So putting the second one in that slot hands the client
|
|
14
|
-
* secret to whoever holds the token, and nothing about the call site looks wrong at a glance:
|
|
15
|
-
* both values are strings on the same object.
|
|
16
|
-
*
|
|
17
|
-
* That is exactly what `renewApplicationAccessToken` did. These cases pin the axis the fix
|
|
18
|
-
* splits — what the payload names — rather than the shape of the call.
|
|
19
|
-
*
|
|
20
|
-
* ── What these do not cover ─────────────────────────────────────────────────
|
|
21
|
-
* The tokens issued before the fix. They carry the secret and they keep carrying it, and the
|
|
22
|
-
* decision is to leave them (2026-09-20 사용자 결정: 이미 발행된 토큰은 무시한다). So there is
|
|
23
|
-
* no audit here and no rule for finding them — only that nothing new goes out that way.
|
|
24
|
-
*
|
|
25
|
-
* ⚠ sqlite (`tests/db.ts`).
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
|
|
29
|
-
import { loadCompiled } from './compiled'
|
|
30
|
-
|
|
31
|
-
const jwt = require('jsonwebtoken')
|
|
32
|
-
|
|
33
|
-
const { Application } = loadCompiled('service/application/application')
|
|
34
|
-
const { User, UserStatus } = loadCompiled('service/user/user')
|
|
35
|
-
const { ApplicationMutation } = loadCompiled('service/application/application-mutation')
|
|
36
|
-
|
|
37
|
-
const mutation = new ApplicationMutation()
|
|
38
|
-
|
|
39
|
-
let domain: any
|
|
40
|
-
let actor: any
|
|
41
|
-
|
|
42
|
-
function context(d: any = domain) {
|
|
43
|
-
return {
|
|
44
|
-
state: { domain: d, user: actor, tx: undefined },
|
|
45
|
-
throw: (code: number, message: string) => {
|
|
46
|
-
const error: any = new Error(message)
|
|
47
|
-
error.status = code
|
|
48
|
-
throw error
|
|
49
|
-
}
|
|
50
|
-
} as any
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** An application plus the shadow user a binding is, which the OAuth2 exchange creates. */
|
|
54
|
-
async function bindApplication(name: string) {
|
|
55
|
-
const application = await mutation.createApplication(
|
|
56
|
-
{
|
|
57
|
-
name,
|
|
58
|
-
email: `${name}@acme.z`,
|
|
59
|
-
url: `https://${name}.example.com`,
|
|
60
|
-
redirectUrl: `https://${name}.example.com/callback`,
|
|
61
|
-
type: 'OTHERS'
|
|
62
|
-
},
|
|
63
|
-
context()
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
const appuser = await getRepository(User).save({
|
|
67
|
-
username: `${application.appKey}@acme`,
|
|
68
|
-
email: `${application.appKey}@acme`,
|
|
69
|
-
name,
|
|
70
|
-
userType: 'application',
|
|
71
|
-
reference: application.id,
|
|
72
|
-
status: UserStatus.ACTIVATED,
|
|
73
|
-
domains: [domain]
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
return { application, appuser }
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
beforeAll(async () => {
|
|
80
|
-
await openAuthDatabase()
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
afterAll(async () => {
|
|
84
|
-
await closeAuthDatabase()
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
beforeEach(async () => {
|
|
88
|
-
await resetAuthDatabase()
|
|
89
|
-
|
|
90
|
-
domain = await getRepository(Domain).save({ name: 'acme', subdomain: 'acme' })
|
|
91
|
-
actor = await getRepository(User).save({
|
|
92
|
-
username: 'operator@acme.z',
|
|
93
|
-
email: 'operator@acme.z',
|
|
94
|
-
name: 'operator',
|
|
95
|
-
status: UserStatus.ACTIVATED,
|
|
96
|
-
domains: [domain]
|
|
97
|
-
})
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
describe('the two strings are not interchangeable', () => {
|
|
101
|
-
it('gives an application a key and a secret that differ', async () => {
|
|
102
|
-
/* If these were ever the same value the rest of this file would pass for the wrong reason. */
|
|
103
|
-
const { application } = await bindApplication('billing')
|
|
104
|
-
|
|
105
|
-
expect(application.appKey).toBeTruthy()
|
|
106
|
-
expect(application.appSecret).toBeTruthy()
|
|
107
|
-
expect(application.appKey).not.toEqual(application.appSecret)
|
|
108
|
-
})
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
describe('renewing an application access token', () => {
|
|
112
|
-
it('names the application by its key', async () => {
|
|
113
|
-
const { application, appuser } = await bindApplication('billing')
|
|
114
|
-
|
|
115
|
-
const { accessToken } = await mutation.renewApplicationAccessToken(appuser.id, context(), 'admin')
|
|
116
|
-
|
|
117
|
-
expect(jwt.decode(accessToken).application.appKey).toEqual(application.appKey)
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('does not put the client secret in the access token', async () => {
|
|
121
|
-
/*
|
|
122
|
-
* ⚠ The defect. A JWT payload is base64, not encryption — whoever holds the token reads it.
|
|
123
|
-
* With the secret and the key, which is public, a holder can call `/oauth2/access-token` as
|
|
124
|
-
* that client.
|
|
125
|
-
*/
|
|
126
|
-
const { application, appuser } = await bindApplication('billing')
|
|
127
|
-
|
|
128
|
-
const { accessToken } = await mutation.renewApplicationAccessToken(appuser.id, context(), 'admin')
|
|
129
|
-
|
|
130
|
-
expect(JSON.stringify(jwt.decode(accessToken))).not.toContain(application.appSecret)
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
it('does not put it in the refresh token either', async () => {
|
|
134
|
-
/*
|
|
135
|
-
* ⚠ The longer-lived half. The access token is 30 days; this one is a year, and it is the
|
|
136
|
-
* one handed to the client to keep.
|
|
137
|
-
*/
|
|
138
|
-
const { application, appuser } = await bindApplication('billing')
|
|
139
|
-
|
|
140
|
-
const { refreshToken } = await mutation.renewApplicationAccessToken(appuser.id, context(), 'admin')
|
|
141
|
-
|
|
142
|
-
expect(jwt.decode(refreshToken).application.appKey).toEqual(application.appKey)
|
|
143
|
-
expect(JSON.stringify(jwt.decode(refreshToken))).not.toContain(application.appSecret)
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('does not leave it in the row it stores', async () => {
|
|
147
|
-
/*
|
|
148
|
-
* The refresh token is saved on the shadow user's `password`, so a payload carrying the
|
|
149
|
-
* secret writes the secret into a second place as well.
|
|
150
|
-
*/
|
|
151
|
-
const { application, appuser } = await bindApplication('billing')
|
|
152
|
-
|
|
153
|
-
await mutation.renewApplicationAccessToken(appuser.id, context(), 'admin')
|
|
154
|
-
|
|
155
|
-
const stored = await getRepository(User).findOneBy({ id: appuser.id })
|
|
156
|
-
|
|
157
|
-
expect(stored.password).toBeTruthy()
|
|
158
|
-
expect(JSON.stringify(jwt.decode(stored.password))).not.toContain(application.appSecret)
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it('mints a refresh token the OAuth2 exchange can resolve back to the application', async () => {
|
|
162
|
-
/*
|
|
163
|
-
* ⚠ The second face of the same defect, and the one an operator would notice. The refresh
|
|
164
|
-
* exchange reads `application.appKey` out of the token and does `findOneBy({ appKey })`
|
|
165
|
-
* (`router/oauth2/oauth2-server.ts`). A payload naming the secret finds no row, so the
|
|
166
|
-
* exchange answers "application is not exist" and the client cannot refresh at all.
|
|
167
|
-
*/
|
|
168
|
-
const { application, appuser } = await bindApplication('billing')
|
|
169
|
-
|
|
170
|
-
const { refreshToken } = await mutation.renewApplicationAccessToken(appuser.id, context(), 'admin')
|
|
171
|
-
const named = jwt.decode(refreshToken).application.appKey
|
|
172
|
-
|
|
173
|
-
expect(await getRepository(Application).findOneBy({ appKey: named })).toMatchObject({ id: application.id })
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('does not name one application with the key of another', async () => {
|
|
177
|
-
const a = await bindApplication('billing')
|
|
178
|
-
const b = await bindApplication('shipping')
|
|
179
|
-
|
|
180
|
-
const renewed = await mutation.renewApplicationAccessToken(a.appuser.id, context(), 'admin')
|
|
181
|
-
|
|
182
|
-
expect(jwt.decode(renewed.accessToken).application.appKey).toEqual(a.application.appKey)
|
|
183
|
-
expect(jwt.decode(renewed.accessToken).application.appKey).not.toEqual(b.application.appKey)
|
|
184
|
-
})
|
|
185
|
-
})
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Putting a person into roles - one function, the domain's scope, ids found again (ADR-0103 decision 2).
|
|
3
|
-
*
|
|
4
|
-
* `updateUserRoles` concatenated the client's `selectedRoles` onto the user as sent and removed every held role
|
|
5
|
-
* whose id was in `availableRoles`. A role of another tenant went in; another tenant's role id in
|
|
6
|
-
* `availableRoles` took that role away from the person in the other domain. The access request grants through
|
|
7
|
-
* the same function, so both doors are held here.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
|
|
11
|
-
import { loadCompiled } from './compiled'
|
|
12
|
-
|
|
13
|
-
const { Role } = loadCompiled('service/role/role')
|
|
14
|
-
const { GrantedRole } = loadCompiled('service/granted-role/granted-role')
|
|
15
|
-
const { User } = loadCompiled('service/user/user')
|
|
16
|
-
const { UserMutation } = loadCompiled('service/user/user-mutation')
|
|
17
|
-
const { assignRoles } = loadCompiled('utils/assignable-roles')
|
|
18
|
-
const { DomainOwner } = loadCompiled('service/domain-owner/domain-owner')
|
|
19
|
-
const { Privilege } = loadCompiled('service/privilege/privilege')
|
|
20
|
-
|
|
21
|
-
let parent: any
|
|
22
|
-
let child: any
|
|
23
|
-
let stranger: any
|
|
24
|
-
|
|
25
|
-
async function loadDomain(id: string) {
|
|
26
|
-
return await getRepository(Domain).findOneBy({ id })
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function makeUser(domains: any[], roles: any[]) {
|
|
30
|
-
const tag = Math.random().toString(36).slice(2, 8)
|
|
31
|
-
return await getRepository(User).save({
|
|
32
|
-
username: `u-${tag}`,
|
|
33
|
-
email: `u-${tag}@example.com`,
|
|
34
|
-
name: 'Kim',
|
|
35
|
-
salt: User.generateSalt(),
|
|
36
|
-
status: 'activated',
|
|
37
|
-
domains,
|
|
38
|
-
roles
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function heldRoleNames(user: any) {
|
|
43
|
-
const read = await getRepository(User).findOne({ where: { id: user.id }, relations: ['roles'] })
|
|
44
|
-
return read.roles.map((role: any) => role.name).sort()
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function updateUserRoles(user: any, available: any[], selected: any[], domain: any) {
|
|
48
|
-
return new UserMutation().updateUserRoles(
|
|
49
|
-
user.username,
|
|
50
|
-
available.map(role => ({ id: role.id })),
|
|
51
|
-
selected.map(role => ({ id: role.id })),
|
|
52
|
-
{ state: { domain }, t: (key: string) => key } as any
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
beforeAll(async () => {
|
|
57
|
-
await openAuthDatabase()
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
afterAll(async () => {
|
|
61
|
-
await closeAuthDatabase()
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
beforeEach(async () => {
|
|
65
|
-
await resetAuthDatabase()
|
|
66
|
-
|
|
67
|
-
const domainRepository = getRepository(Domain)
|
|
68
|
-
const savedParent = await domainRepository.save({ name: 'HQ', subdomain: 'hq' })
|
|
69
|
-
const savedChild = await domainRepository.save({ name: 'Plant A', subdomain: 'plant-a', parent: savedParent })
|
|
70
|
-
const savedStranger = await domainRepository.save({ name: 'Other', subdomain: 'other' })
|
|
71
|
-
|
|
72
|
-
parent = await loadDomain(savedParent.id)
|
|
73
|
-
child = await loadDomain(savedChild.id)
|
|
74
|
-
stranger = await loadDomain(savedStranger.id)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
describe('what a domain can put a person into', () => {
|
|
78
|
-
it('its own role, its parent role and a role granted to it - all three', async () => {
|
|
79
|
-
const own = await getRepository(Role).save({ name: 'operator', domain: child })
|
|
80
|
-
const inherited = await getRepository(Role).save({ name: 'viewer', domain: parent })
|
|
81
|
-
const granted = await getRepository(Role).save({ name: 'partner', domain: stranger })
|
|
82
|
-
await getRepository(GrantedRole).save({ role: granted, domain: child })
|
|
83
|
-
const user = await makeUser([child], [])
|
|
84
|
-
|
|
85
|
-
const parentOwner = await makeUser([parent], [])
|
|
86
|
-
await getRepository(DomainOwner).save({ domain: parent, user: parentOwner })
|
|
87
|
-
const { added } = await assignRoles(user, { add: [own.id, inherited.id, granted.id] }, child, undefined, parentOwner)
|
|
88
|
-
|
|
89
|
-
expect(added.map((role: any) => role.name).sort()).toEqual(['operator', 'partner', 'viewer'])
|
|
90
|
-
expect(await heldRoleNames(user)).toEqual(['operator', 'partner', 'viewer'])
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
it('★ another tenant\'s role is refused by name, and nothing is saved', async () => {
|
|
94
|
-
const own = await getRepository(Role).save({ name: 'operator', domain: child })
|
|
95
|
-
const foreign = await getRepository(Role).save({ name: 'auditor', domain: stranger })
|
|
96
|
-
const user = await makeUser([child], [])
|
|
97
|
-
|
|
98
|
-
await expect(assignRoles(user, { add: [own.id, foreign.id] }, child)).rejects.toMatchObject({
|
|
99
|
-
code: 'role-not-assignable-here',
|
|
100
|
-
params: { roles: foreign.id }
|
|
101
|
-
})
|
|
102
|
-
expect(await heldRoleNames(user)).toEqual([])
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
it('a role already held is not added twice and is not reported as added', async () => {
|
|
106
|
-
const own = await getRepository(Role).save({ name: 'operator', domain: child })
|
|
107
|
-
const user = await makeUser([child], [own])
|
|
108
|
-
|
|
109
|
-
const { added } = await assignRoles(user, { add: [own.id] }, child)
|
|
110
|
-
|
|
111
|
-
expect(added).toEqual([])
|
|
112
|
-
expect(await heldRoleNames(user)).toEqual(['operator'])
|
|
113
|
-
})
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
describe('updateUserRoles goes through the same function', () => {
|
|
117
|
-
it('★ a foreign role in selectedRoles is refused - it used to be attached as sent', async () => {
|
|
118
|
-
const foreign = await getRepository(Role).save({ name: 'auditor', domain: stranger })
|
|
119
|
-
const user = await makeUser([child], [])
|
|
120
|
-
|
|
121
|
-
/* The method is called directly, so the Refusal arrives before SpeakRefusals turns it into a sentence. */
|
|
122
|
-
await expect(updateUserRoles(user, [], [foreign], child)).rejects.toMatchObject({ code: 'role-not-assignable-here' })
|
|
123
|
-
expect(await heldRoleNames(user)).toEqual([])
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
it('★ a foreign role in availableRoles stays with the person - this domain cannot take it', async () => {
|
|
127
|
-
const own = await getRepository(Role).save({ name: 'operator', domain: child })
|
|
128
|
-
const theirs = await getRepository(Role).save({ name: 'auditor', domain: stranger })
|
|
129
|
-
const user = await makeUser([child, stranger], [own, theirs])
|
|
130
|
-
|
|
131
|
-
await updateUserRoles(user, [own, theirs], [], child)
|
|
132
|
-
|
|
133
|
-
expect(await heldRoleNames(user)).toEqual(['auditor'])
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
it('within the scope it replaces as before - remove the shown, add the chosen', async () => {
|
|
137
|
-
const a = await getRepository(Role).save({ name: 'a', domain: child })
|
|
138
|
-
const b = await getRepository(Role).save({ name: 'b', domain: child })
|
|
139
|
-
const user = await makeUser([child], [a])
|
|
140
|
-
|
|
141
|
-
await updateUserRoles(user, [a, b], [b], child)
|
|
142
|
-
|
|
143
|
-
expect(await heldRoleNames(user)).toEqual(['b'])
|
|
144
|
-
})
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
describe('the parent domain\'s roles - only someone who decides there (ADR-0103 decision 8)', () => {
|
|
148
|
-
let parentRole: any
|
|
149
|
-
let childAdmin: any
|
|
150
|
-
let user: any
|
|
151
|
-
|
|
152
|
-
beforeEach(async () => {
|
|
153
|
-
const grant = await getRepository(Privilege).save({ name: 'mutation', category: 'user' })
|
|
154
|
-
const childAdmins = await getRepository(Role).save({ name: 'child-admins', domain: child, privileges: [grant] })
|
|
155
|
-
parentRole = await getRepository(Role).save({ name: 'hq-approver', domain: parent })
|
|
156
|
-
childAdmin = await makeUser([child], [childAdmins])
|
|
157
|
-
user = await makeUser([child], [])
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
it('★ a child domain\'s decider cannot give the parent\'s role - refused by name', async () => {
|
|
161
|
-
await expect(assignRoles(user, { add: [parentRole.id] }, child, undefined, childAdmin)).rejects.toMatchObject({
|
|
162
|
-
code: 'role-belongs-to-parent',
|
|
163
|
-
params: { roles: 'hq-approver' }
|
|
164
|
-
})
|
|
165
|
-
expect(await heldRoleNames(user)).toEqual([])
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
it('★ nor take it away', async () => {
|
|
169
|
-
const holder = await makeUser([child], [parentRole])
|
|
170
|
-
await expect(assignRoles(holder, { remove: [parentRole.id] }, child, undefined, childAdmin)).rejects.toMatchObject({ code: 'role-belongs-to-parent' })
|
|
171
|
-
expect(await heldRoleNames(holder)).toEqual(['hq-approver'])
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
it('★ the parent\'s owner can - from the child', async () => {
|
|
175
|
-
const parentOwner = await makeUser([parent], [])
|
|
176
|
-
await getRepository(DomainOwner).save({ domain: parent, user: parentOwner })
|
|
177
|
-
await assignRoles(user, { add: [parentRole.id] }, child, undefined, parentOwner)
|
|
178
|
-
expect(await heldRoleNames(user)).toEqual(['hq-approver'])
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('★ a holder of the role-giving privilege in the parent can', async () => {
|
|
182
|
-
const grant = await getRepository(Privilege).findOneBy({ name: 'mutation', category: 'user' })
|
|
183
|
-
const hqAdmins = await getRepository(Role).save({ name: 'hq-admins', domain: parent, privileges: [grant] })
|
|
184
|
-
const hqAdmin = await makeUser([parent, child], [hqAdmins])
|
|
185
|
-
await assignRoles(user, { add: [parentRole.id] }, child, undefined, hqAdmin)
|
|
186
|
-
expect(await heldRoleNames(user)).toEqual(['hq-approver'])
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
it('a parent role left as it was is no change - the user screen sends every shown role, nothing is refused', async () => {
|
|
190
|
-
const own = await getRepository(Role).save({ name: 'operator', domain: child })
|
|
191
|
-
const holder = await makeUser([child], [parentRole])
|
|
192
|
-
await assignRoles(holder, { add: [parentRole.id, own.id], remove: [parentRole.id, own.id] }, child, undefined, childAdmin)
|
|
193
|
-
expect(await heldRoleNames(holder)).toEqual(['hq-approver', 'operator'])
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
it('★ updateUserRoles carries the caller - a child admin adding the parent\'s role is refused', async () => {
|
|
197
|
-
const mutation = new UserMutation()
|
|
198
|
-
await expect(
|
|
199
|
-
mutation.updateUserRoles(user.username, [{ id: parentRole.id }], [{ id: parentRole.id }], { state: { domain: child, user: childAdmin }, t: (k: string) => k } as any)
|
|
200
|
-
).rejects.toMatchObject({ code: 'role-belongs-to-parent' })
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
it('★ updateUserRoles carries the caller - the parent\'s owner adding it from the child passes', async () => {
|
|
204
|
-
const parentOwner = await makeUser([parent, child], [])
|
|
205
|
-
await getRepository(DomainOwner).save({ domain: parent, user: parentOwner })
|
|
206
|
-
await new UserMutation().updateUserRoles(user.username, [{ id: parentRole.id }], [{ id: parentRole.id }], {
|
|
207
|
-
state: { domain: child, user: parentOwner },
|
|
208
|
-
t: (k: string) => k
|
|
209
|
-
} as any)
|
|
210
|
-
expect(await heldRoleNames(user)).toEqual(['hq-approver'])
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
it('a granted role is the granting domain\'s delegation - a child decider assigns it', async () => {
|
|
214
|
-
const partnerRole = await getRepository(Role).save({ name: 'partner', domain: stranger })
|
|
215
|
-
await getRepository(GrantedRole).save({ role: partnerRole, domain: child })
|
|
216
|
-
await assignRoles(user, { add: [partnerRole.id] }, child, undefined, childAdmin)
|
|
217
|
-
expect(await heldRoleNames(user)).toEqual(['partner'])
|
|
218
|
-
})
|
|
219
|
-
})
|