@things-factory/auth-base 10.1.215 → 10.1.223
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/config/config.development.js +2 -4
- package/config/config.production.js +1 -2
- package/dist-client/actions/auth.d.ts +1 -0
- package/dist-client/actions/auth.js +6 -0
- package/dist-client/actions/auth.js.map +1 -1
- package/dist-client/reducers/auth.d.ts +8 -0
- package/dist-client/reducers/auth.js +8 -2
- package/dist-client/reducers/auth.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/constants/error-code.d.ts +1 -0
- package/dist-server/constants/error-code.js +2 -1
- package/dist-server/constants/error-code.js.map +1 -1
- package/dist-server/controllers/auth.d.ts +0 -1
- package/dist-server/controllers/auth.js +0 -1
- package/dist-server/controllers/auth.js.map +1 -1
- package/dist-server/controllers/password-setup.d.ts +33 -0
- package/dist-server/controllers/password-setup.js +77 -0
- package/dist-server/controllers/password-setup.js.map +1 -0
- package/dist-server/controllers/reset-password.d.ts +8 -1
- package/dist-server/controllers/reset-password.js +21 -11
- package/dist-server/controllers/reset-password.js.map +1 -1
- package/dist-server/controllers/signin.js +10 -0
- package/dist-server/controllers/signin.js.map +1 -1
- package/dist-server/index.d.ts +1 -0
- package/dist-server/index.js +2 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/middlewares/jwt-authenticate-middleware.js +8 -25
- package/dist-server/middlewares/jwt-authenticate-middleware.js.map +1 -1
- package/dist-server/router/auth-public-process-router.js +3 -41
- package/dist-server/router/auth-public-process-router.js.map +1 -1
- package/dist-server/service/access-request/access-request.js +2 -2
- package/dist-server/service/access-request/access-request.js.map +1 -1
- package/dist-server/service/audit/audit-kinds.js +1 -0
- package/dist-server/service/audit/audit-kinds.js.map +1 -1
- package/dist-server/service/audit/audit-subscriber.d.ts +1 -7
- package/dist-server/service/audit/audit-subscriber.js +2 -33
- package/dist-server/service/audit/audit-subscriber.js.map +1 -1
- package/dist-server/service/audit/index.d.ts +1 -1
- package/dist-server/service/index.d.ts +2 -4
- package/dist-server/service/index.js +22 -27
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/open-fields.js +0 -2
- package/dist-server/service/open-fields.js.map +1 -1
- package/dist-server/service/role/role-mutation.js +5 -0
- package/dist-server/service/role/role-mutation.js.map +1 -1
- package/dist-server/service/role/role-types.d.ts +1 -0
- package/dist-server/service/role/role-types.js +4 -0
- package/dist-server/service/role/role-types.js.map +1 -1
- package/dist-server/service/role/role.d.ts +10 -0
- package/dist-server/service/role/role.js +5 -0
- package/dist-server/service/role/role.js.map +1 -1
- package/dist-server/service/role-template/role-template-gap-resolver.d.ts +2 -2
- package/dist-server/service/role-template/role-template-gap-resolver.js +7 -13
- package/dist-server/service/role-template/role-template-gap-resolver.js.map +1 -1
- package/dist-server/service/role-template/role-template-mutation.d.ts +7 -4
- package/dist-server/service/role-template/role-template-mutation.js +15 -15
- package/dist-server/service/role-template/role-template-mutation.js.map +1 -1
- package/dist-server/service/role-template/role-template-types.d.ts +3 -3
- package/dist-server/service/role-template/role-template-types.js.map +1 -1
- package/dist-server/service/role-template/role-template.d.ts +14 -15
- package/dist-server/service/role-template/role-template.js +8 -8
- package/dist-server/service/role-template/role-template.js.map +1 -1
- package/dist-server/service/user/user-mutation.d.ts +27 -6
- package/dist-server/service/user/user-mutation.js +26 -114
- package/dist-server/service/user/user-mutation.js.map +1 -1
- package/dist-server/service/user/user-query.d.ts +3 -4
- package/dist-server/service/user/user-query.js +7 -32
- package/dist-server/service/user/user-query.js.map +1 -1
- package/dist-server/service/user/user-types.d.ts +6 -2
- package/dist-server/service/user/user-types.js +20 -9
- package/dist-server/service/user/user-types.js.map +1 -1
- package/dist-server/service/user/user.js +17 -8
- package/dist-server/service/user/user.js.map +1 -1
- package/dist-server/service/verification-token/verification-token.d.ts +1 -0
- package/dist-server/service/verification-token/verification-token.js +1 -0
- package/dist-server/service/verification-token/verification-token.js.map +1 -1
- package/dist-server/templates/reset-password-email.d.ts +10 -3
- package/dist-server/templates/reset-password-email.js +8 -4
- package/dist-server/templates/reset-password-email.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/dist-server/utils/verification-token-rule.js +3 -1
- package/dist-server/utils/verification-token-rule.js.map +1 -1
- package/package.json +2 -2
- package/tests/add-user-gates.test.ts +34 -0
- package/tests/list-inheritance-db.test.ts +0 -15
- package/tests/lock-recovery.test.ts +2 -2
- package/tests/open-doors.test.ts +2 -5
- package/tests/password-reset-rule-db.test.ts +17 -16
- package/tests/password-setup-db.test.ts +213 -0
- package/tests/picture-reducer.test.ts +25 -0
- package/tests/role-template-gap-db.test.ts +58 -7
- package/tests/role-template-seed-db.test.ts +5 -4
- package/tests/verification-token-db.test.ts +12 -7
- package/translations/en.json +2 -2
- package/translations/es.json +2 -2
- package/translations/ja.json +2 -2
- package/translations/ko.json +2 -2
- package/translations/ms.json +2 -2
- package/translations/zh.json +2 -2
- package/dist-server/controllers/invitation.d.ts +0 -75
- package/dist-server/controllers/invitation.js +0 -195
- package/dist-server/controllers/invitation.js.map +0 -1
- package/dist-server/controllers/utils/make-invitation-token.d.ts +0 -1
- package/dist-server/controllers/utils/make-invitation-token.js +0 -9
- package/dist-server/controllers/utils/make-invitation-token.js.map +0 -1
- package/dist-server/controllers/utils/save-invitation-token.d.ts +0 -5
- package/dist-server/controllers/utils/save-invitation-token.js +0 -12
- package/dist-server/controllers/utils/save-invitation-token.js.map +0 -1
- package/dist-server/service/invitation/index.d.ts +0 -5
- package/dist-server/service/invitation/index.js +0 -9
- package/dist-server/service/invitation/index.js.map +0 -1
- package/dist-server/service/invitation/invitation-mutation.d.ts +0 -20
- package/dist-server/service/invitation/invitation-mutation.js +0 -109
- package/dist-server/service/invitation/invitation-mutation.js.map +0 -1
- package/dist-server/service/invitation/invitation-query.d.ts +0 -23
- package/dist-server/service/invitation/invitation-query.js +0 -89
- package/dist-server/service/invitation/invitation-query.js.map +0 -1
- package/dist-server/service/invitation/invitation-types.d.ts +0 -5
- package/dist-server/service/invitation/invitation-types.js +0 -21
- package/dist-server/service/invitation/invitation-types.js.map +0 -1
- package/dist-server/service/invitation/invitation.d.ts +0 -26
- package/dist-server/service/invitation/invitation.js +0 -142
- package/dist-server/service/invitation/invitation.js.map +0 -1
- package/dist-server/templates/invitation-email.d.ts +0 -6
- package/dist-server/templates/invitation-email.js +0 -82
- package/dist-server/templates/invitation-email.js.map +0 -1
- package/dist-server/utils/invitation-state.d.ts +0 -52
- package/dist-server/utils/invitation-state.js +0 -76
- package/dist-server/utils/invitation-state.js.map +0 -1
- package/tests/invitation-db.test.ts +0 -416
- package/tests/invitation-route.test.ts +0 -116
- package/tests/invitation-state.test.ts +0 -92
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-request.js","sourceRoot":"","sources":["../../../server/service/access-request/access-request.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, ID, Int, ObjectType, registerEnumType } from 'type-graphql'\n\nimport { config } from '@things-factory/env'\nimport { Domain } from '@things-factory/shell'\n\nimport { Role } from '../role/role.js'\nimport { User } from '../user/user.js'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\nexport enum AccessRequestStatus {\n PENDING = 'PENDING',\n GRANTED = 'GRANTED',\n DENIED = 'DENIED',\n WITHDRAWN = 'WITHDRAWN',\n EXPIRED = 'EXPIRED'\n}\n\nregisterEnumType(AccessRequestStatus, {\n name: 'AccessRequestStatus',\n description: 'Where an access request stands: waiting, granted, denied, withdrawn by the requester, or expired unanswered.'\n})\n\n/*\n * A person refused for want of a role asks for the privilege (ADR-0103, docs/design/access-request.md).\n *\n * ── The row is the record ────────────────────────────────────────────────\n * Who asked, for what, why, who decided, which role, why not - all here, and never deleted. A notification\n * is a courtesy on top: it is not stored per person and does nothing where FCM is not set up.\n *\n * ── At most one pending per person and privilege ─────────────────────────\n * Asking again bumps `count` and `lastRequestedAt` instead of adding a row. A partial unique index would say\n * it and is not portable across the databases this framework supports, so it is held where the request is\n * made (as `Invitation` does). The index below is for reading.\n */\n@Entity()\n@Index('ix_access_request_0', (request: AccessRequest) => [request.domain, request.state])\n@Index('ix_access_request_1', (request: AccessRequest) => [request.domain, request.requester, request.category, request.privilege])\n@ObjectType({ description: 'A request by a person refused for want of a role, for one privilege in this domain.' })\nexport class AccessRequest {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier for the access request.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { description: 'The domain the privilege is asked in.' })\n domain: Domain\n\n @RelationId((request: AccessRequest) => request.domain)\n domainId: string\n\n @ManyToOne(type => User)\n @Field(type => User, { description: 'The person who asked.' })\n requester: User\n\n @RelationId((request: AccessRequest) => request.requester)\n requesterId: string\n\n @Column()\n @Field({ description: 'The category of the privilege asked for, as the refusal carried it.' })\n category: string\n\n @Column()\n @Field({ description: 'The privilege asked for, as the refusal carried it.' })\n privilege: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The operation that was refused - shown to the person deciding.' })\n operationName?: string\n\n /*\n * The work the person asked for, by task id (ADR-0103 decision 9) - their own words for the refused privilege,\n * picked from the tasks that hold it. The request is still keyed by the refused privilege; this explains it. A task is\n * a code catalog and may be gone later: the screen then draws the id, and the review says nothing of it.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The task the requester asked for - one that holds the refused privilege (`<module>.<work>`).' })\n taskId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: \"The requester's reason.\" })\n reason?: string\n\n @Column({ type: 'int', default: 1 })\n @Field(type => Int, { description: 'How many times it was asked while pending.' })\n count: number\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When it was last asked.' })\n lastRequestedAt?: Date\n\n /* The same column shape as Invitation.status - an enum where the database has one, a string elsewhere. */\n @Column({\n type:\n DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'enum'\n : DATABASE_TYPE == 'oracle'\n ? 'varchar2'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'varchar',\n enum:\n DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? AccessRequestStatus : undefined,\n length: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? undefined : 32,\n default: AccessRequestStatus.PENDING\n })\n @Field(type => AccessRequestStatus, { description: 'Where the request stands.' })\n state: AccessRequestStatus\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When a pending request expires unanswered.' })\n expiresAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Who decided it.' })\n decidedBy?: User\n\n @RelationId((request: AccessRequest) => request.decidedBy)\n decidedById?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When it was decided.' })\n decidedAt?: Date\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: \"The decider's reason - required to deny.\" })\n decisionReason?: string\n\n @ManyToOne(type => Role, { nullable: true })\n @Field(type => Role, { nullable: true, description: 'The role the requester was put into.' })\n grantedRole?: Role\n\n @RelationId((request: AccessRequest) => request.grantedRole)\n grantedRoleId?: string\n\n @CreateDateColumn()\n @Field({ description: 'When it was first asked.' })\n createdAt: Date\n\n @UpdateDateColumn()\n @Field({ description: 'When it last changed.' })\n updatedAt: Date\n}\n"]}
|
|
1
|
+
{"version":3,"file":"access-request.js","sourceRoot":"","sources":["../../../server/service/access-request/access-request.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, ID, Int, ObjectType, registerEnumType } from 'type-graphql'\n\nimport { config } from '@things-factory/env'\nimport { Domain } from '@things-factory/shell'\n\nimport { Role } from '../role/role.js'\nimport { User } from '../user/user.js'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\nexport enum AccessRequestStatus {\n PENDING = 'PENDING',\n GRANTED = 'GRANTED',\n DENIED = 'DENIED',\n WITHDRAWN = 'WITHDRAWN',\n EXPIRED = 'EXPIRED'\n}\n\nregisterEnumType(AccessRequestStatus, {\n name: 'AccessRequestStatus',\n description: 'Where an access request stands: waiting, granted, denied, withdrawn by the requester, or expired unanswered.'\n})\n\n/*\n * A person refused for want of a role asks for the privilege (ADR-0103, docs/design/access-request.md).\n *\n * ── The row is the record ────────────────────────────────────────────────\n * Who asked, for what, why, who decided, which role, why not - all here, and never deleted. A notification\n * is a courtesy on top: it is not stored per person and does nothing where FCM is not set up.\n *\n * ── At most one pending per person and privilege ─────────────────────────\n * Asking again bumps `count` and `lastRequestedAt` instead of adding a row. A partial unique index would say\n * it and is not portable across the databases this framework supports, so it is held where the request is\n * made. The index below is for reading.\n */\n@Entity()\n@Index('ix_access_request_0', (request: AccessRequest) => [request.domain, request.state])\n@Index('ix_access_request_1', (request: AccessRequest) => [request.domain, request.requester, request.category, request.privilege])\n@ObjectType({ description: 'A request by a person refused for want of a role, for one privilege in this domain.' })\nexport class AccessRequest {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier for the access request.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { description: 'The domain the privilege is asked in.' })\n domain: Domain\n\n @RelationId((request: AccessRequest) => request.domain)\n domainId: string\n\n @ManyToOne(type => User)\n @Field(type => User, { description: 'The person who asked.' })\n requester: User\n\n @RelationId((request: AccessRequest) => request.requester)\n requesterId: string\n\n @Column()\n @Field({ description: 'The category of the privilege asked for, as the refusal carried it.' })\n category: string\n\n @Column()\n @Field({ description: 'The privilege asked for, as the refusal carried it.' })\n privilege: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The operation that was refused - shown to the person deciding.' })\n operationName?: string\n\n /*\n * The work the person asked for, by task id (ADR-0103 decision 9) - their own words for the refused privilege,\n * picked from the tasks that hold it. The request is still keyed by the refused privilege; this explains it. A task is\n * a code catalog and may be gone later: the screen then draws the id, and the review says nothing of it.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The task the requester asked for - one that holds the refused privilege (`<module>.<work>`).' })\n taskId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: \"The requester's reason.\" })\n reason?: string\n\n @Column({ type: 'int', default: 1 })\n @Field(type => Int, { description: 'How many times it was asked while pending.' })\n count: number\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When it was last asked.' })\n lastRequestedAt?: Date\n\n /* An enum where the database has one, a string elsewhere. */\n @Column({\n type:\n DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'enum'\n : DATABASE_TYPE == 'oracle'\n ? 'varchar2'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'varchar',\n enum:\n DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? AccessRequestStatus : undefined,\n length: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? undefined : 32,\n default: AccessRequestStatus.PENDING\n })\n @Field(type => AccessRequestStatus, { description: 'Where the request stands.' })\n state: AccessRequestStatus\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When a pending request expires unanswered.' })\n expiresAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Who decided it.' })\n decidedBy?: User\n\n @RelationId((request: AccessRequest) => request.decidedBy)\n decidedById?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When it was decided.' })\n decidedAt?: Date\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: \"The decider's reason - required to deny.\" })\n decisionReason?: string\n\n @ManyToOne(type => Role, { nullable: true })\n @Field(type => Role, { nullable: true, description: 'The role the requester was put into.' })\n grantedRole?: Role\n\n @RelationId((request: AccessRequest) => request.grantedRole)\n grantedRoleId?: string\n\n @CreateDateColumn()\n @Field({ description: 'When it was first asked.' })\n createdAt: Date\n\n @UpdateDateColumn()\n @Field({ description: 'When it last changed.' })\n updatedAt: Date\n}\n"]}
|
|
@@ -67,6 +67,7 @@ registerAuditKind('role.delete');
|
|
|
67
67
|
registerAuditKind('user.status', ['from', 'to']);
|
|
68
68
|
registerAuditKind('domain.owner.add');
|
|
69
69
|
registerAuditKind('domain.owner.remove');
|
|
70
|
+
/* Invitations are gone (ADR-0161 decision 6); their records stay and are still read. */
|
|
70
71
|
registerAuditKind('invitation.create');
|
|
71
72
|
registerAuditKind('invitation.status', ['from', 'to']);
|
|
72
73
|
//# sourceMappingURL=audit-kinds.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit-kinds.js","sourceRoot":"","sources":["../../../server/service/audit/audit-kinds.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The kinds of audit action, and the `detail` keys each may carry (ADR-0141 §3.4).\n *\n * The audit stores only fields it allows. The fixed columns of `AuditAction` hold no business\n * value; `detail` is the one open place, so it takes only the keys its kind registered here, and\n * a kind nobody registered is refused - a module that wants a new event says so here, at import,\n * and a typo does not become a new kind quietly.\n *\n * A key holds a security state or a count, never a business value: a user's status (`activated` →\n * `locked`) is the account's security state; a salary is not, and has no key to go in.\n */\n\nexport type AuditDetailValue = string | number | boolean | null\n\nconst KINDS = new Map<string, readonly string[]>()\n\nexport function registerAuditKind(kind: string, detail: readonly string[] = []) {\n const known = KINDS.get(kind)\n if (known && known.join() !== detail.join()) {\n throw new Error(`Audit kind \"${kind}\" is registered twice with different detail keys.`)\n }\n KINDS.set(kind, detail)\n}\n\nexport function isAuditKind(kind: string): boolean {\n return KINDS.has(kind)\n}\n\n/**\n * The detail as it may be stored: unknown kinds throw, keys the kind did not register throw, and\n * values are cut to 64 characters. Throwing is right - this is a program writing the wrong thing.\n */\nexport function allowedDetail(\n kind: string,\n detail?: Record<string, AuditDetailValue>\n): Record<string, AuditDetailValue> | undefined {\n const keys = KINDS.get(kind)\n if (!keys) {\n throw new Error(`Audit kind \"${kind}\" is not registered. Register it with registerAuditKind() first.`)\n }\n if (!detail) return undefined\n\n const kept: Record<string, AuditDetailValue> = {}\n for (const [key, value] of Object.entries(detail)) {\n if (!keys.includes(key)) {\n throw new Error(`Audit kind \"${kind}\" does not allow detail \"${key}\". Allowed: ${keys.join(', ') || 'none'}.`)\n }\n kept[key] = typeof value === 'string' ? value.slice(0, 64) : value\n }\n return Object.keys(kept).length ? kept : undefined\n}\n\n/* Doors */\nregisterAuditKind('door.check')\nregisterAuditKind('door.result')\n\n/* Signing in and out (ADR-0141 §3.3). The reason of a failure is the error code column. */\n/* `method` is password or passkey (ADR-0156 decision 4). */\nregisterAuditKind('auth.signin', ['method'])\nregisterAuditKind('auth.checkin')\nregisterAuditKind('auth.signout')\nregisterAuditKind('password.change')\nregisterAuditKind('password.reset-by-admin')\nregisterAuditKind('credential.issue')\nregisterAuditKind('webauthn.register')\nregisterAuditKind('webauthn.remove')\n\n/* Who may do what */\nregisterAuditKind('role.grant')\nregisterAuditKind('role.revoke')\nregisterAuditKind('role.privilege.add')\nregisterAuditKind('role.privilege.remove')\nregisterAuditKind('role.delete')\nregisterAuditKind('user.status', ['from', 'to'])\nregisterAuditKind('domain.owner.add')\nregisterAuditKind('domain.owner.remove')\nregisterAuditKind('invitation.create')\nregisterAuditKind('invitation.status', ['from', 'to'])\n"]}
|
|
1
|
+
{"version":3,"file":"audit-kinds.js","sourceRoot":"","sources":["../../../server/service/audit/audit-kinds.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The kinds of audit action, and the `detail` keys each may carry (ADR-0141 §3.4).\n *\n * The audit stores only fields it allows. The fixed columns of `AuditAction` hold no business\n * value; `detail` is the one open place, so it takes only the keys its kind registered here, and\n * a kind nobody registered is refused - a module that wants a new event says so here, at import,\n * and a typo does not become a new kind quietly.\n *\n * A key holds a security state or a count, never a business value: a user's status (`activated` →\n * `locked`) is the account's security state; a salary is not, and has no key to go in.\n */\n\nexport type AuditDetailValue = string | number | boolean | null\n\nconst KINDS = new Map<string, readonly string[]>()\n\nexport function registerAuditKind(kind: string, detail: readonly string[] = []) {\n const known = KINDS.get(kind)\n if (known && known.join() !== detail.join()) {\n throw new Error(`Audit kind \"${kind}\" is registered twice with different detail keys.`)\n }\n KINDS.set(kind, detail)\n}\n\nexport function isAuditKind(kind: string): boolean {\n return KINDS.has(kind)\n}\n\n/**\n * The detail as it may be stored: unknown kinds throw, keys the kind did not register throw, and\n * values are cut to 64 characters. Throwing is right - this is a program writing the wrong thing.\n */\nexport function allowedDetail(\n kind: string,\n detail?: Record<string, AuditDetailValue>\n): Record<string, AuditDetailValue> | undefined {\n const keys = KINDS.get(kind)\n if (!keys) {\n throw new Error(`Audit kind \"${kind}\" is not registered. Register it with registerAuditKind() first.`)\n }\n if (!detail) return undefined\n\n const kept: Record<string, AuditDetailValue> = {}\n for (const [key, value] of Object.entries(detail)) {\n if (!keys.includes(key)) {\n throw new Error(`Audit kind \"${kind}\" does not allow detail \"${key}\". Allowed: ${keys.join(', ') || 'none'}.`)\n }\n kept[key] = typeof value === 'string' ? value.slice(0, 64) : value\n }\n return Object.keys(kept).length ? kept : undefined\n}\n\n/* Doors */\nregisterAuditKind('door.check')\nregisterAuditKind('door.result')\n\n/* Signing in and out (ADR-0141 §3.3). The reason of a failure is the error code column. */\n/* `method` is password or passkey (ADR-0156 decision 4). */\nregisterAuditKind('auth.signin', ['method'])\nregisterAuditKind('auth.checkin')\nregisterAuditKind('auth.signout')\nregisterAuditKind('password.change')\nregisterAuditKind('password.reset-by-admin')\nregisterAuditKind('credential.issue')\nregisterAuditKind('webauthn.register')\nregisterAuditKind('webauthn.remove')\n\n/* Who may do what */\nregisterAuditKind('role.grant')\nregisterAuditKind('role.revoke')\nregisterAuditKind('role.privilege.add')\nregisterAuditKind('role.privilege.remove')\nregisterAuditKind('role.delete')\nregisterAuditKind('user.status', ['from', 'to'])\nregisterAuditKind('domain.owner.add')\nregisterAuditKind('domain.owner.remove')\n/* Invitations are gone (ADR-0161 decision 6); their records stay and are still read. */\nregisterAuditKind('invitation.create')\nregisterAuditKind('invitation.status', ['from', 'to'])\n"]}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { EntitySubscriberInterface, InsertEvent, RemoveEvent, UpdateEvent } from 'typeorm';
|
|
2
2
|
import { DomainOwner } from '../domain-owner/domain-owner.js';
|
|
3
|
-
import { Invitation } from '../invitation/invitation.js';
|
|
4
3
|
import { User } from '../user/user.js';
|
|
5
4
|
import { WebAuthCredential } from '../web-auth-credential/web-auth-credential.js';
|
|
6
5
|
/**
|
|
@@ -27,13 +26,8 @@ export declare class DomainOwnerAuditSubscriber implements EntitySubscriberInter
|
|
|
27
26
|
listenTo(): typeof DomainOwner;
|
|
28
27
|
afterInsert(event: InsertEvent<DomainOwner>): Promise<void>;
|
|
29
28
|
}
|
|
30
|
-
export declare class InvitationAuditSubscriber implements EntitySubscriberInterface<Invitation> {
|
|
31
|
-
listenTo(): typeof Invitation;
|
|
32
|
-
afterInsert(event: InsertEvent<Invitation>): Promise<void>;
|
|
33
|
-
afterUpdate(event: UpdateEvent<Invitation>): Promise<void>;
|
|
34
|
-
}
|
|
35
29
|
export declare class WebAuthCredentialAuditSubscriber implements EntitySubscriberInterface<WebAuthCredential> {
|
|
36
30
|
listenTo(): typeof WebAuthCredential;
|
|
37
31
|
afterInsert(event: InsertEvent<WebAuthCredential>): Promise<void>;
|
|
38
32
|
}
|
|
39
|
-
export declare const auditSubscribers: (typeof GrantAuditSubscriber | typeof UserAuditSubscriber | typeof DomainOwnerAuditSubscriber | typeof
|
|
33
|
+
export declare const auditSubscribers: (typeof GrantAuditSubscriber | typeof UserAuditSubscriber | typeof DomainOwnerAuditSubscriber | typeof WebAuthCredentialAuditSubscriber)[];
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.auditSubscribers = exports.WebAuthCredentialAuditSubscriber = exports.
|
|
3
|
+
exports.auditSubscribers = exports.WebAuthCredentialAuditSubscriber = exports.DomainOwnerAuditSubscriber = exports.UserAuditSubscriber = exports.GrantAuditSubscriber = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const typeorm_1 = require("typeorm");
|
|
6
6
|
const domain_owner_js_1 = require("../domain-owner/domain-owner.js");
|
|
7
|
-
const invitation_js_1 = require("../invitation/invitation.js");
|
|
8
7
|
const privilege_js_1 = require("../privilege/privilege.js");
|
|
9
8
|
const role_js_1 = require("../role/role.js");
|
|
10
9
|
const user_js_1 = require("../user/user.js");
|
|
@@ -13,7 +12,7 @@ const audit_recorder_js_1 = require("./audit-recorder.js");
|
|
|
13
12
|
/**
|
|
14
13
|
* ── Who may do what, recorded where it is saved (ADR-0141 §3.3 · §4-1) ───────
|
|
15
14
|
*
|
|
16
|
-
* A role is granted in a dozen places - the user screen, an access request,
|
|
15
|
+
* A role is granted in a dozen places - the user screen, an access request, a
|
|
17
16
|
* template, Office's role plan. Each saves through TypeORM, so the change is caught here, once, and
|
|
18
17
|
* written in the same transaction as the save: the grant and its record stand or fall together.
|
|
19
18
|
*
|
|
@@ -137,35 +136,6 @@ exports.DomainOwnerAuditSubscriber = DomainOwnerAuditSubscriber;
|
|
|
137
136
|
exports.DomainOwnerAuditSubscriber = DomainOwnerAuditSubscriber = tslib_1.__decorate([
|
|
138
137
|
(0, typeorm_1.EventSubscriber)()
|
|
139
138
|
], DomainOwnerAuditSubscriber);
|
|
140
|
-
let InvitationAuditSubscriber = class InvitationAuditSubscriber {
|
|
141
|
-
listenTo() {
|
|
142
|
-
return invitation_js_1.Invitation;
|
|
143
|
-
}
|
|
144
|
-
async afterInsert(event) {
|
|
145
|
-
const invitation = event.entity;
|
|
146
|
-
if (!invitation?.id)
|
|
147
|
-
return;
|
|
148
|
-
const domainId = idOf(invitation.domain);
|
|
149
|
-
await (0, audit_recorder_js_1.recordAuditAction)({ kind: 'invitation.create', targetKind: 'Invitation', targetIds: [invitation.id], targetRole: 'handled' }, { manager: event.manager, ...(domainId ? { domainId } : {}) });
|
|
150
|
-
}
|
|
151
|
-
async afterUpdate(event) {
|
|
152
|
-
const invitation = event.entity;
|
|
153
|
-
const before = event.databaseEntity;
|
|
154
|
-
if (!invitation?.id || !before || invitation.status === undefined || invitation.status === before.status)
|
|
155
|
-
return;
|
|
156
|
-
await (0, audit_recorder_js_1.recordAuditAction)({
|
|
157
|
-
kind: 'invitation.status',
|
|
158
|
-
targetKind: 'Invitation',
|
|
159
|
-
targetIds: [invitation.id],
|
|
160
|
-
targetRole: 'handled',
|
|
161
|
-
detail: { from: before.status ?? null, to: invitation.status }
|
|
162
|
-
}, { manager: event.manager });
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
exports.InvitationAuditSubscriber = InvitationAuditSubscriber;
|
|
166
|
-
exports.InvitationAuditSubscriber = InvitationAuditSubscriber = tslib_1.__decorate([
|
|
167
|
-
(0, typeorm_1.EventSubscriber)()
|
|
168
|
-
], InvitationAuditSubscriber);
|
|
169
139
|
let WebAuthCredentialAuditSubscriber = class WebAuthCredentialAuditSubscriber {
|
|
170
140
|
listenTo() {
|
|
171
141
|
return web_auth_credential_js_1.WebAuthCredential;
|
|
@@ -185,7 +155,6 @@ exports.auditSubscribers = [
|
|
|
185
155
|
GrantAuditSubscriber,
|
|
186
156
|
UserAuditSubscriber,
|
|
187
157
|
DomainOwnerAuditSubscriber,
|
|
188
|
-
InvitationAuditSubscriber,
|
|
189
158
|
WebAuthCredentialAuditSubscriber
|
|
190
159
|
];
|
|
191
160
|
//# sourceMappingURL=audit-subscriber.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit-subscriber.js","sourceRoot":"","sources":["../../../server/service/audit/audit-subscriber.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntitySubscriberInterface, EventSubscriber, InsertEvent, RemoveEvent, UpdateEvent } from 'typeorm'\n\nimport { DomainOwner } from '../domain-owner/domain-owner.js'\nimport { Invitation } from '../invitation/invitation.js'\nimport { Privilege } from '../privilege/privilege.js'\nimport { Role } from '../role/role.js'\nimport { User } from '../user/user.js'\nimport { WebAuthCredential } from '../web-auth-credential/web-auth-credential.js'\nimport { currentAuditScope, recordAuditAction } from './audit-recorder.js'\n\n/**\n * ── Who may do what, recorded where it is saved (ADR-0141 §3.3 · §4-1) ───────\n *\n * A role is granted in a dozen places - the user screen, an access request, an invitation, a\n * template, Office's role plan. Each saves through TypeORM, so the change is caught here, once, and\n * written in the same transaction as the save: the grant and its record stand or fall together.\n *\n * A write that bypasses the entity manager's events (a query builder, `delete()`) is not seen here;\n * those few paths record themselves.\n */\n\nconst idOf = (one: any): string | undefined => (typeof one === 'string' ? one : one?.id)\n\n/** The id a junction row holds for `target`, read from what the event carries. */\nfunction junctionIdOf(event: InsertEvent<any> | RemoveEvent<any>, target: Function): string | undefined {\n const source = (event as any).entityId ?? event.entity ?? (event as RemoveEvent<any>).databaseEntity\n if (!source) return undefined\n const column = event.metadata.columns.find(one => one.referencedColumn?.entityMetadata.target === target)\n if (!column) return undefined\n const value = column.getEntityValue(source) ?? source[column.propertyName] ?? source[column.databaseName]\n return idOf(value)\n}\n\n/** The domain of a role, read in the same transaction - a role being deleted has none left to read. */\nasync function domainOfRole(event: InsertEvent<any> | RemoveEvent<any>, roleId: string) {\n const role = await event.manager.getRepository(Role).findOne({ where: { id: roleId }, relations: ['domain'] })\n return role?.domain?.id\n}\n\n/**\n * ── Grants are junction rows ──────────────────────────────────────────────\n *\n * A role given to a user is a `users_roles` row; a privilege given to a role, a `roles_privileges`\n * row. When only those change, TypeORM updates no entity and sends no update event - it inserts and\n * removes junction rows, and those it does announce. So grants are read here, from the junction, for\n * every path that saves a user's roles or a role's privileges, a new user's or role's included.\n *\n * This listens to every entity (a junction has no class to name) and leaves at once on anything\n * that is not one of the two junctions.\n */\n@EventSubscriber()\nexport class GrantAuditSubscriber implements EntitySubscriberInterface<any> {\n async afterInsert(event: InsertEvent<any>) {\n await this.record(event, 'insert')\n }\n\n async afterRemove(event: RemoveEvent<any>) {\n await this.record(event, 'remove')\n }\n\n private async record(event: InsertEvent<any> | RemoveEvent<any>, change: 'insert' | 'remove') {\n if (!event.metadata.isJunction) return\n\n const roleId = junctionIdOf(event, Role)\n const userId = junctionIdOf(event, User)\n const privilegeId = junctionIdOf(event, Privilege)\n if (!roleId) return\n\n const domainId = await domainOfRole(event, roleId)\n const where = { manager: event.manager, ...(domainId ? { domainId } : {}) }\n\n if (userId) {\n await recordAuditAction(\n {\n kind: change === 'insert' ? 'role.grant' : 'role.revoke',\n targetKind: 'User',\n targetIds: [userId],\n targetRole: 'handled',\n objectKind: 'Role',\n objectId: roleId\n },\n where\n )\n } else if (privilegeId) {\n await recordAuditAction(\n {\n kind: change === 'insert' ? 'role.privilege.add' : 'role.privilege.remove',\n targetKind: 'Role',\n targetIds: [roleId],\n targetRole: 'handled',\n objectKind: 'Privilege',\n objectId: privilegeId\n },\n where\n )\n }\n }\n}\n\n@EventSubscriber()\nexport class UserAuditSubscriber implements EntitySubscriberInterface<User> {\n listenTo() {\n return User\n }\n\n async afterUpdate(event: UpdateEvent<User>) {\n const user = event.entity as User | undefined\n const before = event.databaseEntity as User | undefined\n const userId = user?.id ?? before?.id\n if (!user || !before || !userId) return\n\n const target = { targetKind: 'User', targetIds: [userId], targetRole: 'handled' as const }\n\n if (user.status !== undefined && user.status !== before.status) {\n await recordAuditAction(\n { kind: 'user.status', ...target, detail: { from: before.status ?? null, to: user.status } },\n { manager: event.manager }\n )\n }\n\n if (user.password !== undefined && user.password !== before.password) {\n /* One's own change, or somebody else setting it - the actor tells which. */\n const actorId = currentAuditScope()?.context?.state?.user?.id\n const kind = actorId && actorId !== userId ? 'password.reset-by-admin' : 'password.change'\n await recordAuditAction({ kind, ...target }, { manager: event.manager })\n }\n\n if (user.credentialSerial !== undefined && user.credentialSerial !== before.credentialSerial) {\n await recordAuditAction({ kind: 'credential.issue', ...target }, { manager: event.manager })\n }\n }\n}\n\n@EventSubscriber()\nexport class DomainOwnerAuditSubscriber implements EntitySubscriberInterface<DomainOwner> {\n listenTo() {\n return DomainOwner\n }\n\n async afterInsert(event: InsertEvent<DomainOwner>) {\n const owner = event.entity\n const userId = idOf(owner?.user)\n const domainId = idOf(owner?.domain)\n if (!userId) return\n\n await recordAuditAction(\n { kind: 'domain.owner.add', targetKind: 'User', targetIds: [userId], targetRole: 'handled' },\n { manager: event.manager, ...(domainId ? { domainId } : {}) }\n )\n }\n}\n\n@EventSubscriber()\nexport class InvitationAuditSubscriber implements EntitySubscriberInterface<Invitation> {\n listenTo() {\n return Invitation\n }\n\n async afterInsert(event: InsertEvent<Invitation>) {\n const invitation = event.entity\n if (!invitation?.id) return\n const domainId = idOf((invitation as any).domain)\n\n await recordAuditAction(\n { kind: 'invitation.create', targetKind: 'Invitation', targetIds: [invitation.id], targetRole: 'handled' },\n { manager: event.manager, ...(domainId ? { domainId } : {}) }\n )\n }\n\n async afterUpdate(event: UpdateEvent<Invitation>) {\n const invitation = event.entity as Invitation | undefined\n const before = event.databaseEntity as Invitation | undefined\n if (!invitation?.id || !before || invitation.status === undefined || invitation.status === before.status) return\n\n await recordAuditAction(\n {\n kind: 'invitation.status',\n targetKind: 'Invitation',\n targetIds: [invitation.id],\n targetRole: 'handled',\n detail: { from: before.status ?? null, to: invitation.status }\n },\n { manager: event.manager }\n )\n }\n}\n\n@EventSubscriber()\nexport class WebAuthCredentialAuditSubscriber implements EntitySubscriberInterface<WebAuthCredential> {\n listenTo() {\n return WebAuthCredential\n }\n\n async afterInsert(event: InsertEvent<WebAuthCredential>) {\n const userId = idOf(event.entity?.user)\n if (!userId) return\n\n await recordAuditAction(\n { kind: 'webauthn.register', targetKind: 'User', targetIds: [userId], targetRole: 'handled' },\n { manager: event.manager, actorId: userId }\n )\n }\n}\n\nexport const auditSubscribers = [\n GrantAuditSubscriber,\n UserAuditSubscriber,\n DomainOwnerAuditSubscriber,\n InvitationAuditSubscriber,\n WebAuthCredentialAuditSubscriber\n]\n"]}
|
|
1
|
+
{"version":3,"file":"audit-subscriber.js","sourceRoot":"","sources":["../../../server/service/audit/audit-subscriber.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntitySubscriberInterface, EventSubscriber, InsertEvent, RemoveEvent, UpdateEvent } from 'typeorm'\n\nimport { DomainOwner } from '../domain-owner/domain-owner.js'\nimport { Privilege } from '../privilege/privilege.js'\nimport { Role } from '../role/role.js'\nimport { User } from '../user/user.js'\nimport { WebAuthCredential } from '../web-auth-credential/web-auth-credential.js'\nimport { currentAuditScope, recordAuditAction } from './audit-recorder.js'\n\n/**\n * ── Who may do what, recorded where it is saved (ADR-0141 §3.3 · §4-1) ───────\n *\n * A role is granted in a dozen places - the user screen, an access request, a\n * template, Office's role plan. Each saves through TypeORM, so the change is caught here, once, and\n * written in the same transaction as the save: the grant and its record stand or fall together.\n *\n * A write that bypasses the entity manager's events (a query builder, `delete()`) is not seen here;\n * those few paths record themselves.\n */\n\nconst idOf = (one: any): string | undefined => (typeof one === 'string' ? one : one?.id)\n\n/** The id a junction row holds for `target`, read from what the event carries. */\nfunction junctionIdOf(event: InsertEvent<any> | RemoveEvent<any>, target: Function): string | undefined {\n const source = (event as any).entityId ?? event.entity ?? (event as RemoveEvent<any>).databaseEntity\n if (!source) return undefined\n const column = event.metadata.columns.find(one => one.referencedColumn?.entityMetadata.target === target)\n if (!column) return undefined\n const value = column.getEntityValue(source) ?? source[column.propertyName] ?? source[column.databaseName]\n return idOf(value)\n}\n\n/** The domain of a role, read in the same transaction - a role being deleted has none left to read. */\nasync function domainOfRole(event: InsertEvent<any> | RemoveEvent<any>, roleId: string) {\n const role = await event.manager.getRepository(Role).findOne({ where: { id: roleId }, relations: ['domain'] })\n return role?.domain?.id\n}\n\n/**\n * ── Grants are junction rows ──────────────────────────────────────────────\n *\n * A role given to a user is a `users_roles` row; a privilege given to a role, a `roles_privileges`\n * row. When only those change, TypeORM updates no entity and sends no update event - it inserts and\n * removes junction rows, and those it does announce. So grants are read here, from the junction, for\n * every path that saves a user's roles or a role's privileges, a new user's or role's included.\n *\n * This listens to every entity (a junction has no class to name) and leaves at once on anything\n * that is not one of the two junctions.\n */\n@EventSubscriber()\nexport class GrantAuditSubscriber implements EntitySubscriberInterface<any> {\n async afterInsert(event: InsertEvent<any>) {\n await this.record(event, 'insert')\n }\n\n async afterRemove(event: RemoveEvent<any>) {\n await this.record(event, 'remove')\n }\n\n private async record(event: InsertEvent<any> | RemoveEvent<any>, change: 'insert' | 'remove') {\n if (!event.metadata.isJunction) return\n\n const roleId = junctionIdOf(event, Role)\n const userId = junctionIdOf(event, User)\n const privilegeId = junctionIdOf(event, Privilege)\n if (!roleId) return\n\n const domainId = await domainOfRole(event, roleId)\n const where = { manager: event.manager, ...(domainId ? { domainId } : {}) }\n\n if (userId) {\n await recordAuditAction(\n {\n kind: change === 'insert' ? 'role.grant' : 'role.revoke',\n targetKind: 'User',\n targetIds: [userId],\n targetRole: 'handled',\n objectKind: 'Role',\n objectId: roleId\n },\n where\n )\n } else if (privilegeId) {\n await recordAuditAction(\n {\n kind: change === 'insert' ? 'role.privilege.add' : 'role.privilege.remove',\n targetKind: 'Role',\n targetIds: [roleId],\n targetRole: 'handled',\n objectKind: 'Privilege',\n objectId: privilegeId\n },\n where\n )\n }\n }\n}\n\n@EventSubscriber()\nexport class UserAuditSubscriber implements EntitySubscriberInterface<User> {\n listenTo() {\n return User\n }\n\n async afterUpdate(event: UpdateEvent<User>) {\n const user = event.entity as User | undefined\n const before = event.databaseEntity as User | undefined\n const userId = user?.id ?? before?.id\n if (!user || !before || !userId) return\n\n const target = { targetKind: 'User', targetIds: [userId], targetRole: 'handled' as const }\n\n if (user.status !== undefined && user.status !== before.status) {\n await recordAuditAction(\n { kind: 'user.status', ...target, detail: { from: before.status ?? null, to: user.status } },\n { manager: event.manager }\n )\n }\n\n if (user.password !== undefined && user.password !== before.password) {\n /* One's own change, or somebody else setting it - the actor tells which. */\n const actorId = currentAuditScope()?.context?.state?.user?.id\n const kind = actorId && actorId !== userId ? 'password.reset-by-admin' : 'password.change'\n await recordAuditAction({ kind, ...target }, { manager: event.manager })\n }\n\n if (user.credentialSerial !== undefined && user.credentialSerial !== before.credentialSerial) {\n await recordAuditAction({ kind: 'credential.issue', ...target }, { manager: event.manager })\n }\n }\n}\n\n@EventSubscriber()\nexport class DomainOwnerAuditSubscriber implements EntitySubscriberInterface<DomainOwner> {\n listenTo() {\n return DomainOwner\n }\n\n async afterInsert(event: InsertEvent<DomainOwner>) {\n const owner = event.entity\n const userId = idOf(owner?.user)\n const domainId = idOf(owner?.domain)\n if (!userId) return\n\n await recordAuditAction(\n { kind: 'domain.owner.add', targetKind: 'User', targetIds: [userId], targetRole: 'handled' },\n { manager: event.manager, ...(domainId ? { domainId } : {}) }\n )\n }\n}\n\n@EventSubscriber()\nexport class WebAuthCredentialAuditSubscriber implements EntitySubscriberInterface<WebAuthCredential> {\n listenTo() {\n return WebAuthCredential\n }\n\n async afterInsert(event: InsertEvent<WebAuthCredential>) {\n const userId = idOf(event.entity?.user)\n if (!userId) return\n\n await recordAuditAction(\n { kind: 'webauthn.register', targetKind: 'User', targetIds: [userId], targetRole: 'handled' },\n { manager: event.manager, actorId: userId }\n )\n }\n}\n\nexport const auditSubscribers = [\n GrantAuditSubscriber,\n UserAuditSubscriber,\n DomainOwnerAuditSubscriber,\n WebAuthCredentialAuditSubscriber\n]\n"]}
|
|
@@ -3,4 +3,4 @@ import { AuditQuery } from './audit-query.js';
|
|
|
3
3
|
import { AuditRequest } from './audit-request.js';
|
|
4
4
|
export declare const entities: (typeof AuditAction | typeof AuditRequest)[];
|
|
5
5
|
export declare const resolvers: (typeof AuditQuery)[];
|
|
6
|
-
export declare const subscribers: (typeof import("./audit-subscriber.js").GrantAuditSubscriber | typeof import("./audit-subscriber.js").UserAuditSubscriber | typeof import("./audit-subscriber.js").DomainOwnerAuditSubscriber | typeof import("./audit-subscriber.js").
|
|
6
|
+
export declare const subscribers: (typeof import("./audit-subscriber.js").GrantAuditSubscriber | typeof import("./audit-subscriber.js").UserAuditSubscriber | typeof import("./audit-subscriber.js").DomainOwnerAuditSubscriber | typeof import("./audit-subscriber.js").WebAuthCredentialAuditSubscriber)[];
|
|
@@ -14,7 +14,6 @@ export * from './role-template/role-template-types.js';
|
|
|
14
14
|
export * from './user/user.js';
|
|
15
15
|
export * from './partner/partner.js';
|
|
16
16
|
export * from './granted-role/granted-role.js';
|
|
17
|
-
export * from './invitation/invitation.js';
|
|
18
17
|
export * from './app-binding/app-binding.js';
|
|
19
18
|
export * from './password-history/password-history.js';
|
|
20
19
|
export * from './verification-token/verification-token.js';
|
|
@@ -31,15 +30,14 @@ export * from './app-binding/app-binding-types.js';
|
|
|
31
30
|
export * from './appliance/appliance-types.js';
|
|
32
31
|
export * from './application/application-types.js';
|
|
33
32
|
export * from './domain-generator/domain-generator-types.js';
|
|
34
|
-
export * from './invitation/invitation-types.js';
|
|
35
33
|
export * from './partner/partner-types.js';
|
|
36
34
|
export * from './privilege/privilege-types.js';
|
|
37
35
|
export * from './role/role-types.js';
|
|
38
36
|
export * from './user/user-types.js';
|
|
39
37
|
export * from './domain-link/domain-link-types.js';
|
|
40
38
|
export * from './access-request/access-request-types.js';
|
|
41
|
-
export declare const entities: (typeof import("./web-auth-credential/web-auth-credential.js").WebAuthCredential | typeof import("./user/user.js").User | typeof import("./auth-provider/auth-provider.js").AuthProvider | typeof import("./users-auth-providers/users-auth-providers.js").UsersAuthProviders | typeof import("./domain-owner/domain-owner.js").DomainOwner | typeof import("./granted-role/granted-role.js").GrantedRole | typeof import("./role/role.js").Role | typeof import("./privilege/privilege.js").Privilege | typeof import("./audit/audit-action.js").AuditAction | typeof import("./audit/audit-request.js").AuditRequest | typeof import("./
|
|
42
|
-
export declare const subscribers: (typeof import("./audit/audit-subscriber.js").GrantAuditSubscriber | typeof import("./audit/audit-subscriber.js").UserAuditSubscriber | typeof import("./audit/audit-subscriber.js").DomainOwnerAuditSubscriber | typeof import("./audit/audit-subscriber.js").
|
|
39
|
+
export declare const entities: (typeof import("./web-auth-credential/web-auth-credential.js").WebAuthCredential | typeof import("./user/user.js").User | typeof import("./auth-provider/auth-provider.js").AuthProvider | typeof import("./users-auth-providers/users-auth-providers.js").UsersAuthProviders | typeof import("./domain-owner/domain-owner.js").DomainOwner | typeof import("./granted-role/granted-role.js").GrantedRole | typeof import("./role/role.js").Role | typeof import("./privilege/privilege.js").Privilege | typeof import("./audit/audit-action.js").AuditAction | typeof import("./audit/audit-request.js").AuditRequest | typeof import("./verification-token/verification-token.js").VerificationToken | typeof import("./verification-token/verification-token.js").VerificationTokenType | typeof import("./password-history/password-history.js").PasswordHistory | typeof import("./application/application.js").Application | typeof import("./access-request/access-request.js").AccessRequest | typeof import("./appliance/appliance.js").Appliance | typeof import("./partner/partner.js").Partner | typeof import("./domain-link/domain-link.js").DomainLink)[];
|
|
40
|
+
export declare const subscribers: (typeof import("./audit/audit-subscriber.js").GrantAuditSubscriber | typeof import("./audit/audit-subscriber.js").UserAuditSubscriber | typeof import("./audit/audit-subscriber.js").DomainOwnerAuditSubscriber | typeof import("./audit/audit-subscriber.js").WebAuthCredentialAuditSubscriber)[];
|
|
43
41
|
export declare const schema: {
|
|
44
42
|
typeDefs: {
|
|
45
43
|
privilegeDirectiveTypeDefs: import("graphql").DocumentNode;
|
|
@@ -11,21 +11,20 @@ const index_js_5 = require("./appliance/index.js");
|
|
|
11
11
|
const index_js_6 = require("./application/index.js");
|
|
12
12
|
const index_js_7 = require("./domain-generator/index.js");
|
|
13
13
|
const index_js_8 = require("./granted-role/index.js");
|
|
14
|
-
const index_js_9 = require("./
|
|
15
|
-
const index_js_10 = require("./audit/index.js");
|
|
14
|
+
const index_js_9 = require("./audit/index.js");
|
|
16
15
|
const audit_target_js_1 = require("./audit/audit-target.js");
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
16
|
+
const index_js_10 = require("./partner/index.js");
|
|
17
|
+
const index_js_11 = require("./password-history/index.js");
|
|
18
|
+
const index_js_12 = require("./privilege/index.js");
|
|
20
19
|
const privilege_directive_js_1 = require("./privilege/privilege-directive.js");
|
|
21
20
|
const open_fields_js_1 = require("./open-fields.js");
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
21
|
+
const index_js_13 = require("./role/index.js");
|
|
22
|
+
const index_js_14 = require("./role-template/index.js");
|
|
23
|
+
const index_js_15 = require("./user/index.js");
|
|
24
|
+
const index_js_16 = require("./verification-token/index.js");
|
|
25
|
+
const index_js_17 = require("./web-auth-credential/index.js");
|
|
26
|
+
const index_js_18 = require("./domain-link/index.js");
|
|
27
|
+
const index_js_19 = require("./domain-owner/index.js");
|
|
29
28
|
/* EXPORT ENTITY TYPES */
|
|
30
29
|
tslib_1.__exportStar(require("./users-auth-providers/users-auth-providers.js"), exports);
|
|
31
30
|
tslib_1.__exportStar(require("./auth-provider/auth-provider.js"), exports);
|
|
@@ -46,7 +45,6 @@ tslib_1.__exportStar(require("./role-template/role-template-types.js"), exports)
|
|
|
46
45
|
tslib_1.__exportStar(require("./user/user.js"), exports);
|
|
47
46
|
tslib_1.__exportStar(require("./partner/partner.js"), exports);
|
|
48
47
|
tslib_1.__exportStar(require("./granted-role/granted-role.js"), exports);
|
|
49
|
-
tslib_1.__exportStar(require("./invitation/invitation.js"), exports);
|
|
50
48
|
tslib_1.__exportStar(require("./app-binding/app-binding.js"), exports);
|
|
51
49
|
tslib_1.__exportStar(require("./password-history/password-history.js"), exports);
|
|
52
50
|
tslib_1.__exportStar(require("./verification-token/verification-token.js"), exports);
|
|
@@ -70,7 +68,6 @@ tslib_1.__exportStar(require("./app-binding/app-binding-types.js"), exports);
|
|
|
70
68
|
tslib_1.__exportStar(require("./appliance/appliance-types.js"), exports);
|
|
71
69
|
tslib_1.__exportStar(require("./application/application-types.js"), exports);
|
|
72
70
|
tslib_1.__exportStar(require("./domain-generator/domain-generator-types.js"), exports);
|
|
73
|
-
tslib_1.__exportStar(require("./invitation/invitation-types.js"), exports);
|
|
74
71
|
tslib_1.__exportStar(require("./partner/partner-types.js"), exports);
|
|
75
72
|
tslib_1.__exportStar(require("./privilege/privilege-types.js"), exports);
|
|
76
73
|
tslib_1.__exportStar(require("./role/role-types.js"), exports);
|
|
@@ -83,22 +80,21 @@ exports.entities = [
|
|
|
83
80
|
...index_js_3.entities,
|
|
84
81
|
...index_js_6.entities,
|
|
85
82
|
...index_js_5.entities,
|
|
83
|
+
...index_js_12.entities,
|
|
86
84
|
...index_js_13.entities,
|
|
87
|
-
...
|
|
88
|
-
...
|
|
89
|
-
...index_js_11.entities,
|
|
85
|
+
...index_js_15.entities,
|
|
86
|
+
...index_js_10.entities,
|
|
90
87
|
...index_js_8.entities,
|
|
88
|
+
...index_js_11.entities,
|
|
89
|
+
...index_js_16.entities,
|
|
91
90
|
...index_js_9.entities,
|
|
92
|
-
...index_js_12.entities,
|
|
93
91
|
...index_js_17.entities,
|
|
94
|
-
...index_js_10.entities,
|
|
95
92
|
...index_js_18.entities,
|
|
96
93
|
...index_js_19.entities,
|
|
97
|
-
...index_js_20.entities,
|
|
98
94
|
...index_js_1.entities
|
|
99
95
|
];
|
|
100
96
|
/* The audit of who may do what, written where it is saved (ADR-0141 §3.3). */
|
|
101
|
-
exports.subscribers = [...
|
|
97
|
+
exports.subscribers = [...index_js_9.subscribers];
|
|
102
98
|
exports.schema = {
|
|
103
99
|
typeDefs: {
|
|
104
100
|
privilegeDirectiveTypeDefs: privilege_directive_js_1.privilegeDirectiveTypeDefs,
|
|
@@ -112,20 +108,19 @@ exports.schema = {
|
|
|
112
108
|
...index_js_3.resolvers,
|
|
113
109
|
...index_js_6.resolvers,
|
|
114
110
|
...index_js_5.resolvers,
|
|
111
|
+
...index_js_12.resolvers,
|
|
115
112
|
...index_js_13.resolvers,
|
|
116
113
|
...index_js_14.resolvers,
|
|
117
114
|
...index_js_15.resolvers,
|
|
118
|
-
...
|
|
119
|
-
...index_js_11.resolvers,
|
|
115
|
+
...index_js_10.resolvers,
|
|
120
116
|
...index_js_8.resolvers,
|
|
121
|
-
...index_js_9.resolvers,
|
|
122
117
|
...index_js_4.resolvers,
|
|
123
118
|
...index_js_7.resolvers,
|
|
124
|
-
...
|
|
119
|
+
...index_js_9.resolvers,
|
|
120
|
+
...index_js_18.resolvers,
|
|
125
121
|
...index_js_19.resolvers,
|
|
126
|
-
...index_js_20.resolvers,
|
|
127
122
|
...index_js_1.resolvers,
|
|
128
|
-
...
|
|
123
|
+
...index_js_17.resolvers
|
|
129
124
|
],
|
|
130
125
|
directives: {
|
|
131
126
|
privilege: privilege_directive_js_1.privilegeDirectiveResolver
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,mCAAmC;AACnC,wDAAkH;AAClH,8DAGwC;AACxC,uDAA+G;AAC/G,qDAAwE;AACxE,mDAAqG;AACrG,qDAA2G;AAC3G,0DAAkF;AAClF,sDAA2G;AAC3G
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,mCAAmC;AACnC,wDAAkH;AAClH,8DAGwC;AACxC,uDAA+G;AAC/G,qDAAwE;AACxE,mDAAqG;AACrG,qDAA2G;AAC3G,0DAAkF;AAClF,sDAA2G;AAC3G,+CAIyB;AACzB,6DAAsE;AACtE,kDAA+F;AAC/F,2DAAiF;AACjF,oDAAqG;AACrG,+EAA2G;AAC3G,qDAA6C;AAC7C,+CAAsF;AACtF,wDAA6E;AAC7E,+CAAsF;AACtF,6DAAqF;AACrF,8DAA+H;AAC/H,sDAAwG;AACxG,uDAA4G;AAE5G,yBAAyB;AACzB,yFAA8D;AAC9D,2EAAgD;AAChD,uEAA4C;AAC5C,mEAAwC;AACxC,mEAAwC;AACxC,yDAA8B;AAC9B,wEAA6C;AAC7C,gHAAgH;AAChH,6EAAqF;AAA5E,0HAAA,gBAAgB,OAAA;AACzB,2FAA2F;AAC3F,6EAAuE;AAA9D,4HAAA,kBAAkB,OAAA;AAG3B,2EAAgD;AAChD,uEAA4C;AAC5C,iFAAsD;AACtD,yDAA8B;AAC9B,+DAAoC;AACpC,yEAA8C;AAC9C,uEAA4C;AAC5C,iFAAsD;AACtD,qFAA0D;AAC1D,mEAAwC;AACxC,kEAAuC;AACvC,yDAA0D;AAAjD,mHAAA,iBAAiB,OAAA;AAC1B,+DAAiH;AAAxG,sHAAA,iBAAiB,OAAA;AAAE,oHAAA,eAAe,OAAA;AAAE,+GAAA,UAAU,OAAA;AACvD,iEAAmF;AAA1E,uHAAA,iBAAiB,OAAA;AAAE,yHAAA,mBAAmB,OAAA;AAC/C,uFAA4D;AAC5D,uEAA4C;AAC5C,yEAA8C;AAC9C,6EAAkD;AAElD,kBAAkB;AAClB,6EAAkD;AAClD,yEAA8C;AAC9C,6EAAkD;AAClD,uFAA4D;AAC5D,qEAA0C;AAC1C,yEAA8C;AAC9C,+DAAoC;AACpC,+DAAoC;AACpC,6EAAkD;AAClD,mFAAwD;AAE3C,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,GAAG,mBAA0B;IAC7B,GAAG,mBAAoB;IACvB,GAAG,mBAAmB;IACtB,GAAG,mBAAiB;IACpB,GAAG,oBAAiB;IACpB,GAAG,oBAAY;IACf,GAAG,oBAAY;IACf,GAAG,oBAAe;IAClB,GAAG,mBAAmB;IACtB,GAAG,oBAAuB;IAC1B,GAAG,oBAAyB;IAC5B,GAAG,mBAAa;IAChB,GAAG,oBAAyB;IAC5B,GAAG,oBAAkB;IACrB,GAAG,oBAAmB;IACtB,GAAG,mBAAqB;CACzB,CAAA;AAED,8EAA8E;AACjE,QAAA,WAAW,GAAG,CAAC,GAAG,sBAAgB,CAAC,CAAA;AAEnC,QAAA,MAAM,GAAG;IACpB,QAAQ,EAAE;QACR,0BAA0B,EAA1B,mDAA0B;QAC1B,4BAA4B,EAA5B,8CAA4B;KAC7B;IAED,6FAA6F;IAC7F,UAAU,EAAV,2BAAU;IAEV,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,oBAA2B;QAC9B,GAAG,oBAAqB;QACxB,GAAG,oBAAoB;QACvB,GAAG,oBAAkB;QACrB,GAAG,qBAAkB;QACrB,GAAG,qBAAa;QAChB,GAAG,qBAAqB;QACxB,GAAG,qBAAa;QAChB,GAAG,qBAAgB;QACnB,GAAG,oBAAmB;QACtB,GAAG,oBAAkB;QACrB,GAAG,oBAAuB;QAC1B,GAAG,oBAAc;QACjB,GAAG,qBAAkB;QACrB,GAAG,qBAAoB;QACvB,GAAG,oBAAsB;QACzB,GAAG,qBAA0B;KAC9B;IACD,UAAU,EAAE;QACV,SAAS,EAAE,mDAA0B;KACtC;CACF,CAAA","sourcesContent":["/* IMPORT ENTITIES AND RESOLVERS */\nimport { entities as AccessRequestEntities, resolvers as AccessRequestResolvers } from './access-request/index.js'\nimport {\n entities as UsersAuthProvidersEntities,\n resolvers as UsersAuthProvidersResolvers\n} from './users-auth-providers/index.js'\nimport { entities as AuthProviderEntities, resolvers as AuthProviderResolvers } from './auth-provider/index.js'\nimport { resolvers as AppbindingResolver } from './app-binding/index.js'\nimport { entities as ApplianceEntities, resolvers as ApplianceResolvers } from './appliance/index.js'\nimport { entities as ApplicationEntities, resolvers as ApplicationResolvers } from './application/index.js'\nimport { resolvers as DomainGeneratorResolver } from './domain-generator/index.js'\nimport { entities as GrantedRoleEntities, resolvers as GrantedRoleResolver } from './granted-role/index.js'\nimport {\n entities as AuditEntities,\n resolvers as AuditResolvers,\n subscribers as AuditSubscribers\n} from './audit/index.js'\nimport { auditTargetDirectiveTypeDefs } from './audit/audit-target.js'\nimport { entities as PartnerEntities, resolvers as PartnerResolvers } from './partner/index.js'\nimport { entities as PasswordHistoryEntities } from './password-history/index.js'\nimport { entities as PrivilegeEntities, resolvers as PrivilegeResolvers } from './privilege/index.js'\nimport { privilegeDirectiveResolver, privilegeDirectiveTypeDefs } from './privilege/privilege-directive.js'\nimport { openFields } from './open-fields.js'\nimport { entities as RoleEntities, resolvers as RoleResolvers } from './role/index.js'\nimport { resolvers as RoleTemplateResolvers } from './role-template/index.js'\nimport { entities as UserEntities, resolvers as UserResolvers } from './user/index.js'\nimport { entities as VerificationTokenEntities } from './verification-token/index.js'\nimport { entities as WebAuthCredentialEntities, resolvers as WebAuthCredentialResolvers } from './web-auth-credential/index.js'\nimport { entities as DomainLinkEntities, resolvers as DomainLinkResolver } from './domain-link/index.js'\nimport { entities as DomainOwnerEntities, resolvers as DomainOwnerResolvers } from './domain-owner/index.js'\n\n/* EXPORT ENTITY TYPES */\nexport * from './users-auth-providers/users-auth-providers.js'\nexport * from './auth-provider/auth-provider.js'\nexport * from './application/application.js'\nexport * from './appliance/appliance.js'\nexport * from './privilege/privilege.js'\nexport * from './role/role.js'\nexport * from './privilege/privilege-axis.js'\n/* What a field asks for, by name — for another entrance to the same door (operato-twin ADR-0072 decision 5). */\nexport { privilegeOfField, type FieldGate } from './privilege/privilege-directive.js'\n/* Other entrances report AUTH's refusal without copying its cause or localized wording. */\nexport { privilegeRejection } from './privilege/privilege-rejection.js'\n/* How a module declares the fields it leaves open — see `privilege/open-field-rule.ts`. */\nexport { type OpenFieldsDeclaration } from './privilege/open-field-rule.js'\nexport * from './role-template/role-template.js'\nexport * from './role-template/role-task.js'\nexport * from './role-template/role-template-types.js'\nexport * from './user/user.js'\nexport * from './partner/partner.js'\nexport * from './granted-role/granted-role.js'\nexport * from './app-binding/app-binding.js'\nexport * from './password-history/password-history.js'\nexport * from './verification-token/verification-token.js'\nexport * from './audit/audit-request.js'\nexport * from './audit/audit-action.js'\nexport { registerAuditKind } from './audit/audit-kinds.js'\nexport { recordAuditAction, noteAuditAction, flushAudit, type AuditActionInput } from './audit/audit-recorder.js'\nexport { purgeExpiredAudit, startAuditRetention } from './audit/audit-retention.js'\nexport * from './web-auth-credential/web-auth-credential.js'\nexport * from './domain-link/domain-link.js'\nexport * from './domain-owner/domain-owner.js'\nexport * from './access-request/access-request.js'\n\n/* EXPORT TYPES */\nexport * from './app-binding/app-binding-types.js'\nexport * from './appliance/appliance-types.js'\nexport * from './application/application-types.js'\nexport * from './domain-generator/domain-generator-types.js'\nexport * from './partner/partner-types.js'\nexport * from './privilege/privilege-types.js'\nexport * from './role/role-types.js'\nexport * from './user/user-types.js'\nexport * from './domain-link/domain-link-types.js'\nexport * from './access-request/access-request-types.js'\n\nexport const entities = [\n /* ENTITIES */\n ...UsersAuthProvidersEntities,\n ...AuthProviderEntities,\n ...ApplicationEntities,\n ...ApplianceEntities,\n ...PrivilegeEntities,\n ...RoleEntities,\n ...UserEntities,\n ...PartnerEntities,\n ...GrantedRoleEntities,\n ...PasswordHistoryEntities,\n ...VerificationTokenEntities,\n ...AuditEntities,\n ...WebAuthCredentialEntities,\n ...DomainLinkEntities,\n ...DomainOwnerEntities,\n ...AccessRequestEntities\n]\n\n/* The audit of who may do what, written where it is saved (ADR-0141 §3.3). */\nexport const subscribers = [...AuditSubscribers]\n\nexport const schema = {\n typeDefs: {\n privilegeDirectiveTypeDefs,\n auditTargetDirectiveTypeDefs\n },\n\n /* The fields that carry no @privilege, and why — `open-fields.ts`, read by the directive. */\n openFields,\n\n resolverClasses: [\n /* RESOLVER CLASSES */\n ...UsersAuthProvidersResolvers,\n ...AuthProviderResolvers,\n ...ApplicationResolvers,\n ...ApplianceResolvers,\n ...PrivilegeResolvers,\n ...RoleResolvers,\n ...RoleTemplateResolvers,\n ...UserResolvers,\n ...PartnerResolvers,\n ...GrantedRoleResolver,\n ...AppbindingResolver,\n ...DomainGeneratorResolver,\n ...AuditResolvers,\n ...DomainLinkResolver,\n ...DomainOwnerResolvers,\n ...AccessRequestResolvers,\n ...WebAuthCredentialResolvers\n ],\n directives: {\n privilege: privilegeDirectiveResolver\n }\n}\n"]}
|
|
@@ -15,8 +15,6 @@ exports.openFields = {
|
|
|
15
15
|
'Query.hasPrivilege': 'whether the caller has one — a gate here would need the answer to itself',
|
|
16
16
|
'Query.domainsWithPrivilege': 'the domains where the caller has one',
|
|
17
17
|
'Query.checkUserBelongsDomain': 'whether the caller belongs to this domain',
|
|
18
|
-
'Query.checkResettablePasswordToDefault': "about the caller's own password",
|
|
19
|
-
'Query.checkDefaultPassword': 'whether the caller is still on the default password',
|
|
20
18
|
'Query.passwordRule': "the installation's password policy. The change-password screen needs it before the caller has any privilege — and after a forced reset they may have none",
|
|
21
19
|
'Query.authProviderTypes': "which provider types this build supports. A catalogue, not a tenant's configuration",
|
|
22
20
|
'Query.myAccessRequests': "the caller's own access requests",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"open-fields.js","sourceRoot":"","sources":["../../server/service/open-fields.ts"],"names":[],"mappings":";;;AAEA;;;;;;GAMG;AACU,QAAA,UAAU,GAA0B;IAC/C,QAAQ,EAAE;QACR,sBAAsB,EAAE,4DAA4D;QACpF,eAAe,EAAE,wBAAwB;QACzC,oBAAoB,EAAE,0EAA0E;QAChG,4BAA4B,EAAE,sCAAsC;QACpE,8BAA8B,EAAE,2CAA2C;QAC3E,
|
|
1
|
+
{"version":3,"file":"open-fields.js","sourceRoot":"","sources":["../../server/service/open-fields.ts"],"names":[],"mappings":";;;AAEA;;;;;;GAMG;AACU,QAAA,UAAU,GAA0B;IAC/C,QAAQ,EAAE;QACR,sBAAsB,EAAE,4DAA4D;QACpF,eAAe,EAAE,wBAAwB;QACzC,oBAAoB,EAAE,0EAA0E;QAChG,4BAA4B,EAAE,sCAAsC;QACpE,8BAA8B,EAAE,2CAA2C;QAC3E,oBAAoB,EAClB,2JAA2J;QAC7J,yBAAyB,EAAE,qFAAqF;QAChH,wBAAwB,EAAE,kCAAkC;QAC5D,4BAA4B,EAAE,oFAAoF;QAClH,iCAAiC,EAAE,qFAAqF;QACxH,qBAAqB,EAAE,qHAAqH;QAC5I,wBAAwB,EACtB,6LAA6L;QAC/L,gCAAgC,EAAE,8DAA8D;QAChG,4BAA4B,EAAE,iDAAiD;QAC/E,oCAAoC,EAAE,oDAAoD;QAC1F,oCAAoC,EAAE,yGAAyG;KAChJ;CACF,CAAA","sourcesContent":["import type { OpenFieldsDeclaration } from './privilege/open-field-rule.js'\n\n/**\n * auth-base's root fields that declare no `@privilege` — every one of them, and why.\n *\n * There is no `notYetDeclared` here. The twelve that were open without a reason were closed on\n * 2026-09-20 (`c02ee9bdc1`); what is left answers about the caller, or about the installation\n * rather than anybody's data. An addition to this list needs its reason written first.\n */\nexport const openFields: OpenFieldsDeclaration = {\n byDesign: {\n 'Query.myAuditActions': \"the caller's own actions - the actor is the session's user\",\n 'Query.myRoles': \"the caller's own roles\",\n 'Query.hasPrivilege': 'whether the caller has one — a gate here would need the answer to itself',\n 'Query.domainsWithPrivilege': 'the domains where the caller has one',\n 'Query.checkUserBelongsDomain': 'whether the caller belongs to this domain',\n 'Query.passwordRule':\n \"the installation's password policy. The change-password screen needs it before the caller has any privilege — and after a forced reset they may have none\",\n 'Query.authProviderTypes': \"which provider types this build supports. A catalogue, not a tenant's configuration\",\n 'Query.myAccessRequests': \"the caller's own access requests\",\n 'Query.mayChangeParentRoles': \"whether the caller may give the parent domain's roles here - about the caller only\",\n 'Query.accessRequestDeciderCount': 'how many people an access request from the caller would reach - a number, never who',\n 'Query.tasksGranting': 'the tasks a refused person may ask for - the code catalog of work only, nothing of the domain (ADR-0103 decision 9)',\n 'Mutation.requestAccess':\n 'asked by someone refused for want of a role - a gate here would need the privilege being asked for. The door asks only for the caller and judges again that a role would open it (ADR-0103)',\n 'Mutation.withdrawAccessRequest': \"withdraws the caller's own pending request and nobody else's\",\n 'Query.myWebAuthCredentials': \"the caller's own passkeys (ADR-0156 decision 6)\",\n 'Mutation.updateMyWebAuthCredential': \"renames the caller's own passkey and nobody else's\",\n 'Mutation.deleteMyWebAuthCredential': \"removes the caller's own passkey and nobody else's - losing a device must not wait for an administrator\"\n }\n}\n"]}
|
|
@@ -7,6 +7,7 @@ const type_graphql_1 = require("type-graphql");
|
|
|
7
7
|
const shell_1 = require("@things-factory/shell");
|
|
8
8
|
const refuse_role_name_js_1 = require("../../utils/refuse-role-name.js");
|
|
9
9
|
const privilege_js_1 = require("../privilege/privilege.js");
|
|
10
|
+
const role_template_js_1 = require("../role-template/role-template.js");
|
|
10
11
|
const user_js_1 = require("../user/user.js");
|
|
11
12
|
const role_js_1 = require("./role.js");
|
|
12
13
|
const audit_recorder_js_1 = require("../audit/audit-recorder.js");
|
|
@@ -67,6 +68,10 @@ let RoleMutation = class RoleMutation {
|
|
|
67
68
|
if (description) {
|
|
68
69
|
role.description = description;
|
|
69
70
|
}
|
|
71
|
+
/* A base template is one this installation declares (ADR-0075 decision 1, amended 2026-10-26). */
|
|
72
|
+
if (patch.templateId && !(0, role_template_js_1.allRoleTemplates)().some(template => template.id === patch.templateId)) {
|
|
73
|
+
throw new Error(`There is no role template "${patch.templateId}" in this installation.`);
|
|
74
|
+
}
|
|
70
75
|
return await roleRepository.save({
|
|
71
76
|
...role,
|
|
72
77
|
...patch,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role-mutation.js","sourceRoot":"","sources":["../../../server/service/role/role-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import { In } from 'typeorm'\nimport { Arg, Ctx, Directive, Mutation, Resolver, UseMiddleware } from 'type-graphql'\n\nimport { getRepository, SpeakRefusals } from '@things-factory/shell'\n\nimport { refuseRoleNameIfStanding } from '../../utils/refuse-role-name.js'\n\nimport { Privilege } from '../privilege/privilege.js'\nimport { User } from '../user/user.js'\nimport { Role } from './role.js'\nimport { recordAuditAction } from '../audit/audit-recorder.js'\nimport { NewRole, RolePatch } from './role-types.js'\n\n/*\n * ── Why this class passes through `SpeakRefusals` ───────────────────────────\n * `refuseRoleNameIfStanding` throws a `Refusal` — a name and values rather than a sentence\n * (ADR-0054), so the wording is made where the request's language is known. This middleware is\n * the exit that turns it into one. The older `new Error('...')` sentences in here are untouched\n * and pass through it unchanged; moving them is a separate job.\n */\n@Resolver(Role)\n@UseMiddleware(SpeakRefusals)\nexport class RoleMutation {\n /*\n * 선언이 `User` 였다 — 돌려주는 것은 `Role` 인데(아래 TS 반환형도 그렇다) 스키마에는 사용자로\n * 적혀 있었다. 두 이름에 겹치는 칸(`id`·`name`·`description`)만 물어서 지금까지 드러나지\n * 않았고, `email` 을 물으면 오류 없이 빈 값이 온다. 부르는 자리는 하나뿐이고 겹치는 칸만\n * 쓴다(`role-list-page.ts` 의 `ox-record-creator`).\n */\n @Directive('@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Role, { description: 'To create new role' })\n async createRole(@Arg('role') role: NewRole, @Ctx() context: ResolverContext): Promise<Role> {\n const { domain, user } = context.state\n const roleName = role.name.trim()\n\n await refuseRoleNameIfStanding({ name: roleName, domain, separately: role.separately })\n\n if (role.privileges && role.privileges.length) {\n role.privileges = await getRepository(Privilege).findByIds(role.privileges.map(privilege => privilege.id))\n }\n\n if (role.users && role.users.length) {\n role.users = await getRepository(User).findByIds(role.users.map(user => user.id))\n }\n\n return await getRepository(Role).save({\n domain,\n updater: user,\n creator: user,\n ...role\n })\n }\n\n @Directive('@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Role, { description: 'To modify role information' })\n async updateRole(\n @Arg('id') id: string,\n @Arg('patch') patch: RolePatch,\n @Ctx() context: ResolverContext\n ): Promise<Role> {\n const { domain, user } = context.state\n const { name, description, privileges } = patch\n const roleRepository = getRepository(Role)\n const role: Role = await roleRepository.findOne({\n where: { domain: { id: domain.id }, id },\n relations: ['privileges', 'creator', 'updater']\n })\n\n /*\n * **못 찾았으면 그렇게 말한다.** 이 범위는 `domain.id` 하나다 — 목록은 상속으로 부모 도메인의\n * 역할까지 보여 주므로(`inherited: Include`) 관리자가 그것을 고치려 하면 여기로 온다. 예전에는\n * 바로 아래 `role.name` 에서 TypeError 가 나 500 이 되었고, 화면은 왜 안 되는지 알 수 없었다.\n */\n if (!role) {\n throw new Error(\n context.t('error.failed to find x', { x: context.t('label.role') }) +\n ' — a role defined in a parent domain can only be edited in that domain.'\n )\n }\n\n if (name && role.name !== name) {\n const roleName = name.trim()\n\n await refuseRoleNameIfStanding({ name: roleName, domain, separately: patch.separately })\n\n role.name = roleName\n }\n\n if (description) {\n role.description = description\n }\n\n return await roleRepository.save({\n ...role,\n ...patch,\n /*\n * `privileges` 를 안 보낸 patch 는 **권한을 건드리지 않는다는 뜻**이다. 예전에는 여기서\n * `patch.privileges.map` 이 TypeError 를 냈다 — 이름만 고치려는 호출이 500 이었다.\n */\n privileges: privileges\n ? await getRepository(Privilege).findBy({ id: In(privileges.map(privilege => privilege.id)) })\n : role.privileges,\n updater: user\n } as any)\n }\n\n /**\n * 격자에서 고친 여러 줄을 한 번에 저장한다.\n *\n * 이 저장소의 목록 화면 관례다 — `ox-grist` 의 `dirtyRecords` 를 `cuFlag` 와 함께 보내면\n * `+` 는 만들고 `M` 은 고친다(`updateMultipleAuthProvider`·`updateMultipleUser`·\n * `updateMultipleDomainLink` 가 같은 모양). 역할에만 이 자리가 없어서 이름·설명을 격자에서\n * 고칠 수 없었다.\n *\n * **`privileges` 는 여기서 안 건드린다.** 격자는 이름과 설명만 고치고, 권한은 전용 화면이\n * 쓴다(`updateRole`). 안 보낸 것을 「비우라」로 읽으면 격자에서 이름만 고친 순간 그 역할이\n * 아무것도 안 여는 역할이 된다.\n *\n * 지우기는 이 자리에 없다 — 격자의 삭제는 `deleteRoles` 가 받고, 그쪽은 **쓰이는 역할을\n * 남긴다**는 규칙을 따로 갖는다.\n */\n @Directive('@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [Role], { description: 'To create or update multiple roles at once' })\n async updateMultipleRole(\n @Arg('patches', type => [RolePatch]) patches: RolePatch[],\n @Ctx() context: ResolverContext\n ): Promise<Role[]> {\n const { domain, user, tx } = context.state\n const roleRepository = tx.getRepository(Role)\n const results: Role[] = []\n\n /* `cuFlag` 가 없는 줄은 무엇을 하라는 것인지 알 수 없다 — 짐작해서 만들거나 고치지 않는다. */\n const flagOf = (patch: RolePatch) => (patch.cuFlag || '').toUpperCase()\n\n for (const patch of patches.filter(patch => flagOf(patch) === '+')) {\n const name = patch.name?.trim()\n if (!name) {\n throw new Error(context.t('error.value is empty', { value: context.t('field.name') }))\n }\n\n await refuseRoleNameIfStanding({ name, domain, separately: patch.separately, tx })\n\n results.push(\n await roleRepository.save({\n domain,\n creator: user,\n updater: user,\n name,\n description: patch.description\n })\n )\n }\n\n for (const patch of patches.filter(patch => flagOf(patch) === 'M')) {\n const role = await roleRepository.findOne({ where: { domain: { id: domain.id }, id: patch.id } })\n\n /* 목록은 상속으로 부모 도메인 역할까지 보여 준다 — 그것을 고치려 하면 여기로 온다. */\n if (!role) {\n throw new Error(\n context.t('error.failed to find x', { x: context.t('label.role') }) +\n ' — a role defined in a parent domain can only be edited in that domain.'\n )\n }\n\n const name = patch.name?.trim()\n if (name && name !== role.name) {\n await refuseRoleNameIfStanding({ name, domain, separately: patch.separately, tx })\n\n role.name = name\n }\n\n if (patch.description !== undefined) {\n role.description = patch.description\n }\n\n results.push(await roleRepository.save({ ...role, updater: user }))\n }\n\n return results\n }\n\n @Directive('@privilege(category: \"user\", privilege: \"mutation\")')\n @Directive('@transaction')\n @Mutation(returns => Boolean, { description: 'To delete role' })\n async deleteRole(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Boolean> {\n const { tx, domain } = context.state\n let role: Role = await tx\n .getRepository(Role)\n .findOne({ where: { domain: { id: domain.id }, id }, relations: ['users', 'privileges'] })\n\n /*\n * **세 가지 실패가 한 문장으로 나가고 있었다.** 아래 try 의 catch 가 무엇이 났든\n * 「사용 중이라 지울 수 없다」로 401 을 던졌다 — 못 찾은 것도(부모 도메인의 역할이 그렇다),\n * DB 가 실패한 것도 그렇게 말했다. 관리자는 있지도 않은 사용자를 떼러 갔다.\n *\n * 그래서 판단할 수 있는 둘을 먼저 가른다.\n */\n if (!role) {\n throw new Error(\n context.t('error.failed to find x', { x: context.t('label.role') }) +\n ' — a role defined in a parent domain can only be deleted in that domain.'\n )\n }\n\n if (role.users.length) {\n throw new Error('cannot delete role because it is currently in use')\n }\n\n try {\n role.privileges = []\n\n role = await tx.getRepository(Role).save(role)\n\n await tx.getRepository(Role).delete(role.id)\n\n /* `delete()` passes no subscriber - the deletion records itself, in the same transaction (ADR-0141 §4-1). */\n await recordAuditAction({ kind: 'role.delete', targetKind: 'Role', targetIds: [role.id], targetRole: 'handled' }, { manager: tx })\n\n return true\n } catch (e) {\n /* 여기까지 온 것은 지우는 동작 자체의 실패다 — 그 말을 그대로 올린다. */\n context.throw(500, e.message)\n }\n }\n\n @Directive('@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Boolean, { description: 'To delete multiple roles' })\n async deleteRoles(@Arg('ids', type => [String]) ids: string[], @Ctx() context: ResolverContext) {\n const { domain } = context.state\n\n /*\n * **도메인을 봤어야 했다.** 예전에는 받은 id 를 그대로 지웠다 — `user:mutation` 을 가진\n * 사람이 남의 도메인 역할을 id 만으로 지울 수 있었다. `deleteRole`(단수)은 처음부터\n * 도메인으로 좁혀 찾았으므로, 둘의 규칙이 달랐다.\n *\n * 그리고 **사용 중인 역할은 남긴다** — 단수 쪽과 같은 규칙이다. 지운 것과 남은 것을 세어\n * 돌려주지는 않는다(반환형이 `Boolean` 이다). 목록이 다시 읽으면 무엇이 남았는지 보인다.\n */\n const roles = await getRepository(Role).find({\n where: { domain: { id: domain.id }, id: In(ids) },\n relations: ['users']\n })\n\n const deletable = roles.filter(role => !role.users?.length).map(role => role.id)\n if (deletable.length) {\n /* The deletion and its record stand or fall together (ADR-0141 §4-1). */\n await getRepository(Role).manager.transaction(async manager => {\n await manager.getRepository(Role).delete(deletable)\n await recordAuditAction({ kind: 'role.delete', targetKind: 'Role', targetIds: deletable, targetRole: 'handled' }, { manager })\n })\n }\n\n return true\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"role-mutation.js","sourceRoot":"","sources":["../../../server/service/role/role-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import { In } from 'typeorm'\nimport { Arg, Ctx, Directive, Mutation, Resolver, UseMiddleware } from 'type-graphql'\n\nimport { getRepository, SpeakRefusals } from '@things-factory/shell'\n\nimport { refuseRoleNameIfStanding } from '../../utils/refuse-role-name.js'\n\nimport { Privilege } from '../privilege/privilege.js'\nimport { allRoleTemplates } from '../role-template/role-template.js'\nimport { User } from '../user/user.js'\nimport { Role } from './role.js'\nimport { recordAuditAction } from '../audit/audit-recorder.js'\nimport { NewRole, RolePatch } from './role-types.js'\n\n/*\n * ── Why this class passes through `SpeakRefusals` ───────────────────────────\n * `refuseRoleNameIfStanding` throws a `Refusal` — a name and values rather than a sentence\n * (ADR-0054), so the wording is made where the request's language is known. This middleware is\n * the exit that turns it into one. The older `new Error('...')` sentences in here are untouched\n * and pass through it unchanged; moving them is a separate job.\n */\n@Resolver(Role)\n@UseMiddleware(SpeakRefusals)\nexport class RoleMutation {\n /*\n * 선언이 `User` 였다 — 돌려주는 것은 `Role` 인데(아래 TS 반환형도 그렇다) 스키마에는 사용자로\n * 적혀 있었다. 두 이름에 겹치는 칸(`id`·`name`·`description`)만 물어서 지금까지 드러나지\n * 않았고, `email` 을 물으면 오류 없이 빈 값이 온다. 부르는 자리는 하나뿐이고 겹치는 칸만\n * 쓴다(`role-list-page.ts` 의 `ox-record-creator`).\n */\n @Directive('@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Role, { description: 'To create new role' })\n async createRole(@Arg('role') role: NewRole, @Ctx() context: ResolverContext): Promise<Role> {\n const { domain, user } = context.state\n const roleName = role.name.trim()\n\n await refuseRoleNameIfStanding({ name: roleName, domain, separately: role.separately })\n\n if (role.privileges && role.privileges.length) {\n role.privileges = await getRepository(Privilege).findByIds(role.privileges.map(privilege => privilege.id))\n }\n\n if (role.users && role.users.length) {\n role.users = await getRepository(User).findByIds(role.users.map(user => user.id))\n }\n\n return await getRepository(Role).save({\n domain,\n updater: user,\n creator: user,\n ...role\n })\n }\n\n @Directive('@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Role, { description: 'To modify role information' })\n async updateRole(\n @Arg('id') id: string,\n @Arg('patch') patch: RolePatch,\n @Ctx() context: ResolverContext\n ): Promise<Role> {\n const { domain, user } = context.state\n const { name, description, privileges } = patch\n const roleRepository = getRepository(Role)\n const role: Role = await roleRepository.findOne({\n where: { domain: { id: domain.id }, id },\n relations: ['privileges', 'creator', 'updater']\n })\n\n /*\n * **못 찾았으면 그렇게 말한다.** 이 범위는 `domain.id` 하나다 — 목록은 상속으로 부모 도메인의\n * 역할까지 보여 주므로(`inherited: Include`) 관리자가 그것을 고치려 하면 여기로 온다. 예전에는\n * 바로 아래 `role.name` 에서 TypeError 가 나 500 이 되었고, 화면은 왜 안 되는지 알 수 없었다.\n */\n if (!role) {\n throw new Error(\n context.t('error.failed to find x', { x: context.t('label.role') }) +\n ' — a role defined in a parent domain can only be edited in that domain.'\n )\n }\n\n if (name && role.name !== name) {\n const roleName = name.trim()\n\n await refuseRoleNameIfStanding({ name: roleName, domain, separately: patch.separately })\n\n role.name = roleName\n }\n\n if (description) {\n role.description = description\n }\n\n /* A base template is one this installation declares (ADR-0075 decision 1, amended 2026-10-26). */\n if (patch.templateId && !allRoleTemplates().some(template => template.id === patch.templateId)) {\n throw new Error(`There is no role template \"${patch.templateId}\" in this installation.`)\n }\n\n return await roleRepository.save({\n ...role,\n ...patch,\n /*\n * `privileges` 를 안 보낸 patch 는 **권한을 건드리지 않는다는 뜻**이다. 예전에는 여기서\n * `patch.privileges.map` 이 TypeError 를 냈다 — 이름만 고치려는 호출이 500 이었다.\n */\n privileges: privileges\n ? await getRepository(Privilege).findBy({ id: In(privileges.map(privilege => privilege.id)) })\n : role.privileges,\n updater: user\n } as any)\n }\n\n /**\n * 격자에서 고친 여러 줄을 한 번에 저장한다.\n *\n * 이 저장소의 목록 화면 관례다 — `ox-grist` 의 `dirtyRecords` 를 `cuFlag` 와 함께 보내면\n * `+` 는 만들고 `M` 은 고친다(`updateMultipleAuthProvider`·`updateMultipleUser`·\n * `updateMultipleDomainLink` 가 같은 모양). 역할에만 이 자리가 없어서 이름·설명을 격자에서\n * 고칠 수 없었다.\n *\n * **`privileges` 는 여기서 안 건드린다.** 격자는 이름과 설명만 고치고, 권한은 전용 화면이\n * 쓴다(`updateRole`). 안 보낸 것을 「비우라」로 읽으면 격자에서 이름만 고친 순간 그 역할이\n * 아무것도 안 여는 역할이 된다.\n *\n * 지우기는 이 자리에 없다 — 격자의 삭제는 `deleteRoles` 가 받고, 그쪽은 **쓰이는 역할을\n * 남긴다**는 규칙을 따로 갖는다.\n */\n @Directive('@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [Role], { description: 'To create or update multiple roles at once' })\n async updateMultipleRole(\n @Arg('patches', type => [RolePatch]) patches: RolePatch[],\n @Ctx() context: ResolverContext\n ): Promise<Role[]> {\n const { domain, user, tx } = context.state\n const roleRepository = tx.getRepository(Role)\n const results: Role[] = []\n\n /* `cuFlag` 가 없는 줄은 무엇을 하라는 것인지 알 수 없다 — 짐작해서 만들거나 고치지 않는다. */\n const flagOf = (patch: RolePatch) => (patch.cuFlag || '').toUpperCase()\n\n for (const patch of patches.filter(patch => flagOf(patch) === '+')) {\n const name = patch.name?.trim()\n if (!name) {\n throw new Error(context.t('error.value is empty', { value: context.t('field.name') }))\n }\n\n await refuseRoleNameIfStanding({ name, domain, separately: patch.separately, tx })\n\n results.push(\n await roleRepository.save({\n domain,\n creator: user,\n updater: user,\n name,\n description: patch.description\n })\n )\n }\n\n for (const patch of patches.filter(patch => flagOf(patch) === 'M')) {\n const role = await roleRepository.findOne({ where: { domain: { id: domain.id }, id: patch.id } })\n\n /* 목록은 상속으로 부모 도메인 역할까지 보여 준다 — 그것을 고치려 하면 여기로 온다. */\n if (!role) {\n throw new Error(\n context.t('error.failed to find x', { x: context.t('label.role') }) +\n ' — a role defined in a parent domain can only be edited in that domain.'\n )\n }\n\n const name = patch.name?.trim()\n if (name && name !== role.name) {\n await refuseRoleNameIfStanding({ name, domain, separately: patch.separately, tx })\n\n role.name = name\n }\n\n if (patch.description !== undefined) {\n role.description = patch.description\n }\n\n results.push(await roleRepository.save({ ...role, updater: user }))\n }\n\n return results\n }\n\n @Directive('@privilege(category: \"user\", privilege: \"mutation\")')\n @Directive('@transaction')\n @Mutation(returns => Boolean, { description: 'To delete role' })\n async deleteRole(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Boolean> {\n const { tx, domain } = context.state\n let role: Role = await tx\n .getRepository(Role)\n .findOne({ where: { domain: { id: domain.id }, id }, relations: ['users', 'privileges'] })\n\n /*\n * **세 가지 실패가 한 문장으로 나가고 있었다.** 아래 try 의 catch 가 무엇이 났든\n * 「사용 중이라 지울 수 없다」로 401 을 던졌다 — 못 찾은 것도(부모 도메인의 역할이 그렇다),\n * DB 가 실패한 것도 그렇게 말했다. 관리자는 있지도 않은 사용자를 떼러 갔다.\n *\n * 그래서 판단할 수 있는 둘을 먼저 가른다.\n */\n if (!role) {\n throw new Error(\n context.t('error.failed to find x', { x: context.t('label.role') }) +\n ' — a role defined in a parent domain can only be deleted in that domain.'\n )\n }\n\n if (role.users.length) {\n throw new Error('cannot delete role because it is currently in use')\n }\n\n try {\n role.privileges = []\n\n role = await tx.getRepository(Role).save(role)\n\n await tx.getRepository(Role).delete(role.id)\n\n /* `delete()` passes no subscriber - the deletion records itself, in the same transaction (ADR-0141 §4-1). */\n await recordAuditAction({ kind: 'role.delete', targetKind: 'Role', targetIds: [role.id], targetRole: 'handled' }, { manager: tx })\n\n return true\n } catch (e) {\n /* 여기까지 온 것은 지우는 동작 자체의 실패다 — 그 말을 그대로 올린다. */\n context.throw(500, e.message)\n }\n }\n\n @Directive('@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Boolean, { description: 'To delete multiple roles' })\n async deleteRoles(@Arg('ids', type => [String]) ids: string[], @Ctx() context: ResolverContext) {\n const { domain } = context.state\n\n /*\n * **도메인을 봤어야 했다.** 예전에는 받은 id 를 그대로 지웠다 — `user:mutation` 을 가진\n * 사람이 남의 도메인 역할을 id 만으로 지울 수 있었다. `deleteRole`(단수)은 처음부터\n * 도메인으로 좁혀 찾았으므로, 둘의 규칙이 달랐다.\n *\n * 그리고 **사용 중인 역할은 남긴다** — 단수 쪽과 같은 규칙이다. 지운 것과 남은 것을 세어\n * 돌려주지는 않는다(반환형이 `Boolean` 이다). 목록이 다시 읽으면 무엇이 남았는지 보인다.\n */\n const roles = await getRepository(Role).find({\n where: { domain: { id: domain.id }, id: In(ids) },\n relations: ['users']\n })\n\n const deletable = roles.filter(role => !role.users?.length).map(role => role.id)\n if (deletable.length) {\n /* The deletion and its record stand or fall together (ADR-0141 §4-1). */\n await getRepository(Role).manager.transaction(async manager => {\n await manager.getRepository(Role).delete(deletable)\n await recordAuditAction({ kind: 'role.delete', targetKind: 'Role', targetIds: deletable, targetRole: 'handled' }, { manager })\n })\n }\n\n return true\n }\n}\n"]}
|