@things-factory/auth-base 10.1.31 → 10.1.34
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/dist-server/controllers/invitation.d.ts +47 -10
- package/dist-server/controllers/invitation.js +159 -113
- package/dist-server/controllers/invitation.js.map +1 -1
- package/dist-server/controllers/profile.d.ts +1 -0
- package/dist-server/router/auth-public-process-router.js +36 -13
- package/dist-server/router/auth-public-process-router.js.map +1 -1
- package/dist-server/router/oauth2/oauth2-router.js +7 -2
- package/dist-server/router/oauth2/oauth2-router.js.map +1 -1
- package/dist-server/router/oauth2/oauth2-server.js +4 -4
- package/dist-server/router/oauth2/oauth2-server.js.map +1 -1
- package/dist-server/service/app-binding/app-binding-mutation.d.ts +27 -0
- package/dist-server/service/app-binding/app-binding-mutation.js +39 -6
- package/dist-server/service/app-binding/app-binding-mutation.js.map +1 -1
- package/dist-server/service/app-binding/app-binding-query.js +14 -7
- package/dist-server/service/app-binding/app-binding-query.js.map +1 -1
- package/dist-server/service/app-binding/app-binding.d.ts +9 -0
- package/dist-server/service/app-binding/app-binding.js +10 -1
- package/dist-server/service/app-binding/app-binding.js.map +1 -1
- package/dist-server/service/appliance/appliance-mutation.js +34 -1
- package/dist-server/service/appliance/appliance-mutation.js.map +1 -1
- package/dist-server/service/appliance/appliance-query.d.ts +2 -0
- package/dist-server/service/appliance/appliance-query.js +48 -0
- package/dist-server/service/appliance/appliance-query.js.map +1 -1
- package/dist-server/service/appliance/appliance.d.ts +1 -0
- package/dist-server/service/appliance/appliance.js +33 -3
- package/dist-server/service/appliance/appliance.js.map +1 -1
- package/dist-server/service/application/application-mutation.js +51 -6
- package/dist-server/service/application/application-mutation.js.map +1 -1
- package/dist-server/service/application/application.d.ts +9 -3
- package/dist-server/service/application/application.js +24 -8
- package/dist-server/service/application/application.js.map +1 -1
- package/dist-server/service/auth-provider/auth-provider-mutation.js +5 -0
- package/dist-server/service/auth-provider/auth-provider-mutation.js.map +1 -1
- package/dist-server/service/domain-generator/domain-generator-mutation.js +3 -0
- package/dist-server/service/domain-generator/domain-generator-mutation.js.map +1 -1
- package/dist-server/service/invitation/invitation-mutation.d.ts +17 -15
- package/dist-server/service/invitation/invitation-mutation.js +83 -56
- package/dist-server/service/invitation/invitation-mutation.js.map +1 -1
- package/dist-server/service/invitation/invitation-query.d.ts +20 -5
- package/dist-server/service/invitation/invitation-query.js +60 -19
- package/dist-server/service/invitation/invitation-query.js.map +1 -1
- package/dist-server/service/invitation/invitation.d.ts +14 -2
- package/dist-server/service/invitation/invitation.js +77 -8
- package/dist-server/service/invitation/invitation.js.map +1 -1
- package/dist-server/service/login-history/login-history-query.js +3 -0
- package/dist-server/service/login-history/login-history-query.js.map +1 -1
- package/dist-server/service/role/role-mutation.js +14 -15
- package/dist-server/service/role/role-mutation.js.map +1 -1
- package/dist-server/service/role/role-query.js +26 -4
- package/dist-server/service/role/role-query.js.map +1 -1
- package/dist-server/service/role/role-types.d.ts +2 -0
- package/dist-server/service/role/role-types.js +8 -0
- package/dist-server/service/role/role-types.js.map +1 -1
- package/dist-server/service/role-template/role-template-mutation.d.ts +17 -1
- package/dist-server/service/role-template/role-template-mutation.js +14 -3
- package/dist-server/service/role-template/role-template-mutation.js.map +1 -1
- package/dist-server/service/user/user-mutation.js +1 -0
- package/dist-server/service/user/user-mutation.js.map +1 -1
- package/dist-server/service/user/user.d.ts +1 -0
- package/dist-server/service/user/user.js +19 -4
- package/dist-server/service/user/user.js.map +1 -1
- package/dist-server/templates/invitation-email.d.ts +2 -1
- package/dist-server/templates/invitation-email.js +16 -4
- package/dist-server/templates/invitation-email.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/dist-server/utils/credential-at-rest.d.ts +28 -0
- package/dist-server/utils/credential-at-rest.js +36 -0
- package/dist-server/utils/credential-at-rest.js.map +1 -0
- package/dist-server/utils/credential-features.d.ts +35 -0
- package/dist-server/utils/credential-features.js +45 -0
- package/dist-server/utils/credential-features.js.map +1 -0
- package/dist-server/utils/credential-serial-rule.d.ts +41 -0
- package/dist-server/utils/credential-serial-rule.js +43 -0
- package/dist-server/utils/credential-serial-rule.js.map +1 -0
- package/dist-server/utils/invitation-state.d.ts +52 -0
- package/dist-server/utils/invitation-state.js +76 -0
- package/dist-server/utils/invitation-state.js.map +1 -0
- package/dist-server/utils/refuse-role-name.d.ts +42 -0
- package/dist-server/utils/refuse-role-name.js +76 -0
- package/dist-server/utils/refuse-role-name.js.map +1 -0
- package/dist-server/utils/role-name-standing.d.ts +58 -0
- package/dist-server/utils/role-name-standing.js +72 -0
- package/dist-server/utils/role-name-standing.js.map +1 -0
- package/package.json +4 -4
- package/tests/app-binding-delete-db.test.ts +188 -0
- package/tests/appliance-credential-state-db.test.ts +207 -0
- package/tests/appliance-delete-db.test.ts +119 -0
- package/tests/appliance-revoke-db.test.ts +73 -28
- package/tests/appliance-schema.test.ts +129 -0
- package/tests/application-delete-db.test.ts +190 -0
- package/tests/application-token-payload-db.test.ts +185 -0
- package/tests/checkin-privilege-seam-db.test.ts +158 -0
- package/tests/credential-at-rest-db.test.ts +246 -0
- package/tests/credential-features-off-db.test.ts +189 -0
- package/tests/credential-serial-rule.test.ts +84 -0
- package/tests/invitation-db.test.ts +416 -0
- package/tests/invitation-state.test.ts +92 -0
- package/tests/open-doors.test.ts +132 -0
- package/tests/role-inheritance-db.test.ts +286 -0
- package/tests/role-mutation-db.test.ts +21 -7
- package/tests/role-name-lookup-sentinel.test.ts +79 -0
- package/tests/role-name-standing.test.ts +83 -0
- package/tests/token-issuance.test.ts +7 -3
- package/translations/en.json +2 -0
- package/translations/ja.json +2 -0
- package/translations/ko.json +2 -0
- package/translations/ms.json +2 -0
- package/translations/zh.json +2 -0
- package/dist-server/controllers/checkin.d.ts +0 -4
- package/dist-server/controllers/checkin.js +0 -20
- package/dist-server/controllers/checkin.js.map +0 -1
|
@@ -5,7 +5,26 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const typeorm_1 = require("typeorm");
|
|
6
6
|
const type_graphql_1 = require("type-graphql");
|
|
7
7
|
const graphql_scalars_1 = require("graphql-scalars");
|
|
8
|
+
const env_1 = require("@things-factory/env");
|
|
9
|
+
const shell_1 = require("@things-factory/shell");
|
|
10
|
+
const role_js_1 = require("../role/role.js");
|
|
8
11
|
const user_js_1 = require("../user/user.js");
|
|
12
|
+
const invitation_state_js_1 = require("../../utils/invitation-state.js");
|
|
13
|
+
const ORMCONFIG = env_1.config.get('ormconfig', {});
|
|
14
|
+
const DATABASE_TYPE = ORMCONFIG.type;
|
|
15
|
+
/*
|
|
16
|
+
* ── Why there is no unique index on (domain, email) ─────────────────────────
|
|
17
|
+
* There was one on `(email, reference, type)`, which meant a person could be invited to a
|
|
18
|
+
* domain **once, ever**. That was consistent with a table whose rows were deleted on accept —
|
|
19
|
+
* it held pending invitations and nothing else. Now the rows stay, so "invited last year, left,
|
|
20
|
+
* invited again" has to be expressible, and a unique constraint across the history forbids it.
|
|
21
|
+
*
|
|
22
|
+
* What still has to hold is **at most one pending invitation** per person and domain. A partial
|
|
23
|
+
* unique index would say that, and partial indexes are not portable across the databases this
|
|
24
|
+
* framework supports (`reference_typeorm_column_type_portability`). So it is enforced where the
|
|
25
|
+
* invitation is created, inside the same transaction, and the index below is for reading the
|
|
26
|
+
* list.
|
|
27
|
+
*/
|
|
9
28
|
let Invitation = class Invitation {
|
|
10
29
|
};
|
|
11
30
|
exports.Invitation = Invitation;
|
|
@@ -20,20 +39,72 @@ tslib_1.__decorate([
|
|
|
20
39
|
tslib_1.__metadata("design:type", String)
|
|
21
40
|
], Invitation.prototype, "email", void 0);
|
|
22
41
|
tslib_1.__decorate([
|
|
23
|
-
(0, typeorm_1.
|
|
24
|
-
(0, type_graphql_1.Field)({ description: '
|
|
42
|
+
(0, typeorm_1.ManyToOne)(type => shell_1.Domain),
|
|
43
|
+
(0, type_graphql_1.Field)(type => shell_1.Domain, { description: 'The domain the user is invited to join.' }),
|
|
44
|
+
tslib_1.__metadata("design:type", shell_1.Domain)
|
|
45
|
+
], Invitation.prototype, "domain", void 0);
|
|
46
|
+
tslib_1.__decorate([
|
|
47
|
+
(0, typeorm_1.RelationId)((invitation) => invitation.domain),
|
|
48
|
+
tslib_1.__metadata("design:type", String)
|
|
49
|
+
], Invitation.prototype, "domainId", void 0);
|
|
50
|
+
tslib_1.__decorate([
|
|
51
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
52
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Deprecated. The domain the invitation is to is `domain`.' }),
|
|
25
53
|
tslib_1.__metadata("design:type", String)
|
|
26
54
|
], Invitation.prototype, "reference", void 0);
|
|
27
55
|
tslib_1.__decorate([
|
|
28
|
-
(0, typeorm_1.Column)(),
|
|
29
|
-
(0, type_graphql_1.Field)({ description: '
|
|
56
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
57
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Deprecated. See `reference`.' }),
|
|
30
58
|
tslib_1.__metadata("design:type", String)
|
|
31
59
|
], Invitation.prototype, "type", void 0);
|
|
60
|
+
tslib_1.__decorate([
|
|
61
|
+
(0, typeorm_1.ManyToMany)(type => role_js_1.Role),
|
|
62
|
+
(0, typeorm_1.JoinTable)({ name: 'invitations_roles' }),
|
|
63
|
+
(0, type_graphql_1.Field)(type => [role_js_1.Role], { nullable: true, description: 'Roles to grant when the invitation is accepted.' }),
|
|
64
|
+
tslib_1.__metadata("design:type", Array)
|
|
65
|
+
], Invitation.prototype, "roles", void 0);
|
|
66
|
+
tslib_1.__decorate([
|
|
67
|
+
(0, typeorm_1.Column)({
|
|
68
|
+
type: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
|
|
69
|
+
? 'enum'
|
|
70
|
+
: DATABASE_TYPE == 'oracle'
|
|
71
|
+
? 'varchar2'
|
|
72
|
+
: DATABASE_TYPE == 'mssql'
|
|
73
|
+
? 'nvarchar'
|
|
74
|
+
: 'varchar',
|
|
75
|
+
enum: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
|
|
76
|
+
? invitation_state_js_1.InvitationStatus
|
|
77
|
+
: undefined,
|
|
78
|
+
length: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? undefined : 32,
|
|
79
|
+
default: invitation_state_js_1.InvitationStatus.PENDING
|
|
80
|
+
}),
|
|
81
|
+
(0, type_graphql_1.Field)(type => String, { description: 'pending, accepted, cancelled or expired.' }),
|
|
82
|
+
tslib_1.__metadata("design:type", String)
|
|
83
|
+
], Invitation.prototype, "status", void 0);
|
|
84
|
+
tslib_1.__decorate([
|
|
85
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
86
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'When this invitation stops being acceptable.' }),
|
|
87
|
+
tslib_1.__metadata("design:type", Date)
|
|
88
|
+
], Invitation.prototype, "expiresAt", void 0);
|
|
89
|
+
tslib_1.__decorate([
|
|
90
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
91
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'When the invitation was accepted.' }),
|
|
92
|
+
tslib_1.__metadata("design:type", Date
|
|
93
|
+
/*
|
|
94
|
+
* ⚠ Never returned to a client. The token is what opens the door, and an invitation list is a
|
|
95
|
+
* screen an administrator reads — printing it there would hand every pending invitation to
|
|
96
|
+
* whoever can see the list. `invitation-query.ts` does not resolve it.
|
|
97
|
+
*/
|
|
98
|
+
)
|
|
99
|
+
], Invitation.prototype, "acceptedAt", void 0);
|
|
32
100
|
tslib_1.__decorate([
|
|
33
101
|
(0, typeorm_1.Column)(),
|
|
34
|
-
(0, type_graphql_1.Field)({ description: 'The unique token used to accept the invitation.' }),
|
|
35
102
|
tslib_1.__metadata("design:type", String)
|
|
36
103
|
], Invitation.prototype, "token", void 0);
|
|
104
|
+
tslib_1.__decorate([
|
|
105
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'pending, accepted, cancelled or expired — read as of now.' }),
|
|
106
|
+
tslib_1.__metadata("design:type", String)
|
|
107
|
+
], Invitation.prototype, "state", void 0);
|
|
37
108
|
tslib_1.__decorate([
|
|
38
109
|
(0, typeorm_1.CreateDateColumn)({ nullable: true }),
|
|
39
110
|
(0, type_graphql_1.Field)({ nullable: true, description: 'The timestamp when the invitation was created.' }),
|
|
@@ -64,9 +135,7 @@ tslib_1.__decorate([
|
|
|
64
135
|
], Invitation.prototype, "updaterId", void 0);
|
|
65
136
|
exports.Invitation = Invitation = tslib_1.__decorate([
|
|
66
137
|
(0, typeorm_1.Entity)(),
|
|
67
|
-
(0, typeorm_1.Index)('ix_invitation_0', (invitation) => [invitation.
|
|
68
|
-
unique: true
|
|
69
|
-
}),
|
|
138
|
+
(0, typeorm_1.Index)('ix_invitation_0', (invitation) => [invitation.domain, invitation.email]),
|
|
70
139
|
(0, typeorm_1.Index)('ix_invitation_1', (invitation) => [invitation.token], { unique: true }),
|
|
71
140
|
(0, type_graphql_1.ObjectType)({ description: 'Represents an invitation for a user to join a system or a specific resource.' })
|
|
72
141
|
], Invitation);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invitation.js","sourceRoot":"","sources":["../../../server/service/invitation/invitation.ts"],"names":[],"mappings":";;;;AAAA,qCASgB;AAChB,+CAAoD;AACpD,qDAAqD;AACrD,6CAAsC;AAQ/B,IAAM,UAAU,GAAhB,MAAM,UAAU;CA0CtB,CAAA;AA1CY,gCAAU;AAGZ;IAFR,IAAA,gCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAE,EAAE,EAAE,WAAW,EAAE,uCAAuC,EAAE,CAAC;;sCACzD;AAInB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,qCAAmB,EAAE,EAAE,WAAW,EAAE,oDAAoD,EAAE,CAAC;;yCAC7F;AAIb;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,qFAAqF,EAAE,CAAC;;6CAC7F;AAIjB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,uEAAuE,EAAE,CAAC;;wCACpF;AAIZ;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,iDAAiD,EAAE,CAAC;;yCAC7D;AAIb;IAFC,IAAA,0BAAgB,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACpC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,gDAAgD,EAAE,CAAC;sCAC9E,IAAI;6CAAA;AAIf;IAFC,IAAA,0BAAgB,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACpC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,qDAAqD,EAAE,CAAC;sCACnF,IAAI;6CAAA;AAIf;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,cAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,sCAAsC,EAAE,CAAC;sCACtE,cAAI;2CAAA;AAGb;IADC,IAAA,oBAAU,EAAC,CAAC,UAAsB,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;;6CAC1C;AAIjB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,cAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,2CAA2C,EAAE,CAAC;sCAC3E,cAAI;2CAAA;AAGb;IADC,IAAA,oBAAU,EAAC,CAAC,UAAsB,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;;6CAC1C;qBAzCN,UAAU;IANtB,IAAA,gBAAM,GAAE;IACR,IAAA,eAAK,EAAC,iBAAiB,EAAE,CAAC,UAAsB,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,EAAE,UAAU,CAAC,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE;QAC/G,MAAM,EAAE,IAAI;KACb,CAAC;IACD,IAAA,eAAK,EAAC,iBAAiB,EAAE,CAAC,UAAsB,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC1F,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,8EAA8E,EAAE,CAAC;GAC/F,UAAU,CA0CtB","sourcesContent":["import {\n CreateDateColumn,\n UpdateDateColumn,\n Entity,\n Index,\n Column,\n ManyToOne,\n RelationId,\n PrimaryGeneratedColumn\n} from 'typeorm'\nimport { ObjectType, Field, ID } from 'type-graphql'\nimport { GraphQLEmailAddress } from 'graphql-scalars'\nimport { User } from '../user/user.js'\n\n@Entity()\n@Index('ix_invitation_0', (invitation: Invitation) => [invitation.email, invitation.reference, invitation.type], {\n unique: true\n})\n@Index('ix_invitation_1', (invitation: Invitation) => [invitation.token], { unique: true })\n@ObjectType({ description: 'Represents an invitation for a user to join a system or a specific resource.' })\nexport class Invitation {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier for the invitation.' })\n readonly id: string\n\n @Column()\n @Field(type => GraphQLEmailAddress, { description: 'The email address to which the invitation is sent.' })\n email: string\n\n @Column()\n @Field({ description: 'A reference to the resource the user is invited to (e.g., company ID, bizplace ID).' })\n reference: string // company, bizplace\n\n @Column()\n @Field({ description: 'The type of resource the invitation is for (e.g., company, bizplace).' })\n type: string // company, bizplace, .. should think whether this column needs.\n\n @Column()\n @Field({ description: 'The unique token used to accept the invitation.' })\n token: string\n\n @CreateDateColumn({ nullable: true })\n @Field({ nullable: true, description: 'The timestamp when the invitation was created.' })\n createdAt: Date\n\n @UpdateDateColumn({ nullable: true })\n @Field({ nullable: true, description: 'The timestamp when the invitation was last updated.' })\n updatedAt: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field({ nullable: true, description: 'The user who created the invitation.' })\n creator: User\n\n @RelationId((invitation: Invitation) => invitation.creator)\n creatorId: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field({ nullable: true, description: 'The user who last updated the invitation.' })\n updater: User\n\n @RelationId((invitation: Invitation) => invitation.updater)\n updaterId: string\n}\n"]}
|
|
1
|
+
{"version":3,"file":"invitation.js","sourceRoot":"","sources":["../../../server/service/invitation/invitation.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n CreateDateColumn,\n UpdateDateColumn,\n Entity,\n Index,\n Column,\n JoinTable,\n ManyToMany,\n ManyToOne,\n RelationId,\n PrimaryGeneratedColumn\n} from 'typeorm'\nimport { ObjectType, Field, ID } from 'type-graphql'\nimport { GraphQLEmailAddress } from 'graphql-scalars'\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'\nimport { InvitationStatus } from '../../utils/invitation-state.js'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\n/*\n * ── Why there is no unique index on (domain, email) ─────────────────────────\n * There was one on `(email, reference, type)`, which meant a person could be invited to a\n * domain **once, ever**. That was consistent with a table whose rows were deleted on accept —\n * it held pending invitations and nothing else. Now the rows stay, so \"invited last year, left,\n * invited again\" has to be expressible, and a unique constraint across the history forbids it.\n *\n * What still has to hold is **at most one pending invitation** per person and domain. A partial\n * unique index would say that, and partial indexes are not portable across the databases this\n * framework supports (`reference_typeorm_column_type_portability`). So it is enforced where the\n * invitation is created, inside the same transaction, and the index below is for reading the\n * list.\n */\n@Entity()\n@Index('ix_invitation_0', (invitation: Invitation) => [invitation.domain, invitation.email])\n@Index('ix_invitation_1', (invitation: Invitation) => [invitation.token], { unique: true })\n@ObjectType({ description: 'Represents an invitation for a user to join a system or a specific resource.' })\nexport class Invitation {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier for the invitation.' })\n readonly id: string\n\n @Column()\n @Field(type => GraphQLEmailAddress, { description: 'The email address to which the invitation is sent.' })\n email: string\n\n /*\n * The domain this invitation is to.\n *\n * ⚠ `reference` below used to carry this. Every live use read it as a domain id\n * (`Domain.findOneBy({ id: reference })`), and the line above the first of them said\n * `// TODO reference should not be a domain.id (security reason)` — so the code was doing the\n * thing its own author had written down as wrong. The column says it now, which is what that\n * note was asking for, and it is what the list is scoped by.\n */\n @ManyToOne(type => Domain)\n @Field(type => Domain, { description: 'The domain the user is invited to join.' })\n domain: Domain\n\n @RelationId((invitation: Invitation) => invitation.domain)\n domainId: string\n\n /** ⚠ Superseded by `domain`. Kept nullable for rows written before that column existed. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Deprecated. The domain the invitation is to is `domain`.' })\n reference?: string\n\n /** ⚠ Superseded by `domain`. Kept nullable for rows written before that column existed. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Deprecated. See `reference`.' })\n type?: string\n\n /*\n * The roles this person gets on arrival.\n *\n * Without them somebody accepts an invitation, signs in, and has no screens at all — the\n * `user-invite` screen already decided that roles are chosen where the person is invited, for\n * exactly that reason. An invitation is that place for the people who arrive this way.\n */\n @ManyToMany(type => Role)\n @JoinTable({ name: 'invitations_roles' })\n @Field(type => [Role], { nullable: true, description: 'Roles to grant when the invitation is accepted.' })\n roles?: Role[]\n\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'\n ? InvitationStatus\n : undefined,\n length: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? undefined : 32,\n default: InvitationStatus.PENDING\n })\n @Field(type => String, { description: 'pending, accepted, cancelled or expired.' })\n status: InvitationStatus\n\n /*\n * ⚠ Nullable only because rows written before this column existed have nothing here, and those\n * read as expired (`utils/invitation-state.ts`). Everything written from now on sets it.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When this invitation stops being acceptable.' })\n expiresAt?: Date\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When the invitation was accepted.' })\n acceptedAt?: Date\n\n /*\n * ⚠ Never returned to a client. The token is what opens the door, and an invitation list is a\n * screen an administrator reads — printing it there would hand every pending invitation to\n * whoever can see the list. `invitation-query.ts` does not resolve it.\n */\n @Column()\n token: string\n\n /* Resolved against the clock in `invitation-query.ts`, not stored on its own. */\n @Field({ nullable: true, description: 'pending, accepted, cancelled or expired — read as of now.' })\n state?: string\n\n @CreateDateColumn({ nullable: true })\n @Field({ nullable: true, description: 'The timestamp when the invitation was created.' })\n createdAt: Date\n\n @UpdateDateColumn({ nullable: true })\n @Field({ nullable: true, description: 'The timestamp when the invitation was last updated.' })\n updatedAt: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field({ nullable: true, description: 'The user who created the invitation.' })\n creator: User\n\n @RelationId((invitation: Invitation) => invitation.creator)\n creatorId: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field({ nullable: true, description: 'The user who last updated the invitation.' })\n updater: User\n\n @RelationId((invitation: Invitation) => invitation.updater)\n updaterId: string\n}\n"]}
|
|
@@ -8,6 +8,8 @@ const user_js_1 = require("../user/user.js");
|
|
|
8
8
|
const login_history_js_1 = require("./login-history.js");
|
|
9
9
|
const login_history_type_js_1 = require("./login-history-type.js");
|
|
10
10
|
let LoginHistoryQuery = class LoginHistoryQuery {
|
|
11
|
+
/* Everybody's sign-ins. `myLoginHistories` below is the one that answers about the caller,
|
|
12
|
+
and it exists precisely because this one should not be open. */
|
|
11
13
|
async loginHistories(params, context) {
|
|
12
14
|
const { domain } = context.state;
|
|
13
15
|
const queryBuilder = (0, shell_1.getQueryBuilderFromListParams)({
|
|
@@ -38,6 +40,7 @@ let LoginHistoryQuery = class LoginHistoryQuery {
|
|
|
38
40
|
};
|
|
39
41
|
exports.LoginHistoryQuery = LoginHistoryQuery;
|
|
40
42
|
tslib_1.__decorate([
|
|
43
|
+
(0, type_graphql_1.Directive)('@privilege(category: "user", privilege: "query", domainOwnerGranted: true, superUserGranted: true)'),
|
|
41
44
|
(0, type_graphql_1.Query)(returns => login_history_type_js_1.LoginHistoryList, { description: 'To fetch multiple LoginHistories' }),
|
|
42
45
|
tslib_1.__param(0, (0, type_graphql_1.Args)(type => shell_1.ListParam)),
|
|
43
46
|
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-history-query.js","sourceRoot":"","sources":["../../../server/service/login-history/login-history-query.ts"],"names":[],"mappings":";;;;AAAA,+
|
|
1
|
+
{"version":3,"file":"login-history-query.js","sourceRoot":"","sources":["../../../server/service/login-history/login-history-query.ts"],"names":[],"mappings":";;;;AAAA,+CAA8F;AAE9F,iDAAuG;AAEvG,6CAAsC;AACtC,yDAAiD;AACjD,mEAA0D;AAGnD,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAC5B;sEACkE;IAG5D,AAAN,KAAK,CAAC,cAAc,CACO,MAAiB,EACnC,OAAwB;QAE/B,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAEhC,MAAM,YAAY,GAAG,IAAA,qCAA6B,EAAC;YACjD,MAAM;YACN,MAAM;YACN,UAAU,EAAE,MAAM,IAAA,qBAAa,EAAC,+BAAY,CAAC;SAC9C,CAAC,CAAA;QAEF,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,YAAY,CAAC,eAAe,EAAE,CAAA;QAC3D,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;IACzB,CAAC;IAGK,AAAN,KAAK,CAAC,gBAAgB,CAAe,KAAa,EAAS,OAAwB;QACjF,MAAM,IAAI,GAAS,OAAO,CAAC,KAAK,CAAC,IAAI,CAAA;QACrC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAA;QACtC,CAAC;QAED,OAAO,MAAM,IAAA,qBAAa,EAAC,+BAAY,CAAC,CAAC,IAAI,CAAC;YAC5C,KAAK,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE;YACtC,IAAI,EAAE,KAAK,IAAI,EAAE;YACjB,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE;SAC9B,CAAC,CAAA;IACJ,CAAC;IAGK,AAAN,KAAK,CAAC,YAAY,CAAS,YAA0B;QACnD,OAAO,MAAM,IAAA,qBAAa,EAAC,cAAM,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,YAAY,CAAC,cAAc,EAAE,CAAC,CAAA;IACnF,CAAC;IAGK,AAAN,KAAK,CAAC,UAAU,CAAS,YAA0B;QACjD,OAAO,MAAM,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,YAAY,CAAC,YAAY,EAAE,CAAC,CAAA;IAC/E,CAAC;CACF,CAAA;AA5CY,8CAAiB;AAKtB;IAFL,IAAA,wBAAS,EAAC,oGAAoG,CAAC;IAC/G,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,wCAAgB,EAAE,EAAE,WAAW,EAAE,kCAAkC,EAAE,CAAC;IAErF,mBAAA,IAAA,mBAAI,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAS,CAAC,CAAA;IACvB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;6CAD2B,iBAAS;;uDAa3C;AAGK;IADL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC,+BAAY,CAAC,CAAC;IACT,mBAAA,IAAA,kBAAG,EAAC,OAAO,CAAC,CAAA;IAAiB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;yDAWzD;AAGK;IADL,IAAA,4BAAa,GAAE;IACI,mBAAA,IAAA,mBAAI,GAAE,CAAA;;6CAAe,+BAAY;;qDAEpD;AAGK;IADL,IAAA,4BAAa,GAAE;IACE,mBAAA,IAAA,mBAAI,GAAE,CAAA;;6CAAe,+BAAY;;mDAElD;4BA3CU,iBAAiB;IAD7B,IAAA,uBAAQ,EAAC,+BAAY,CAAC;GACV,iBAAiB,CA4C7B","sourcesContent":["import { Arg, Args, Ctx, Directive, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'\n\nimport { User } from '../user/user.js'\nimport { LoginHistory } from './login-history.js'\nimport { LoginHistoryList } from './login-history-type.js'\n\n@Resolver(LoginHistory)\nexport class LoginHistoryQuery {\n /* Everybody's sign-ins. `myLoginHistories` below is the one that answers about the caller,\n and it exists precisely because this one should not be open. */\n @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => LoginHistoryList, { description: 'To fetch multiple LoginHistories' })\n async loginHistories(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<LoginHistoryList> {\n const { domain } = context.state\n\n const queryBuilder = getQueryBuilderFromListParams({\n domain,\n params,\n repository: await getRepository(LoginHistory)\n })\n\n const [items, total] = await queryBuilder.getManyAndCount()\n return { items, total }\n }\n\n @Query(returns => [LoginHistory])\n async myLoginHistories(@Arg('limit') limit: number, @Ctx() context: ResolverContext): Promise<LoginHistory[]> {\n const user: User = context.state.user\n if (!user) {\n throw new Error('No user specified')\n }\n\n return await getRepository(LoginHistory).find({\n where: { accessUser: { id: user.id } },\n take: limit || 10,\n order: { accessedAt: 'DESC' }\n })\n }\n\n @FieldResolver()\n async accessDomain(@Root() loginHistory: LoginHistory) {\n return await getRepository(Domain).findOneBy({ id: loginHistory.accessDomainId })\n }\n\n @FieldResolver()\n async accessUser(@Root() loginHistory: LoginHistory) {\n return await getRepository(User).findOneBy({ id: loginHistory.accessUserId })\n }\n}\n"]}
|
|
@@ -5,10 +5,18 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const typeorm_1 = require("typeorm");
|
|
6
6
|
const type_graphql_1 = require("type-graphql");
|
|
7
7
|
const shell_1 = require("@things-factory/shell");
|
|
8
|
+
const refuse_role_name_js_1 = require("../../utils/refuse-role-name.js");
|
|
8
9
|
const privilege_js_1 = require("../privilege/privilege.js");
|
|
9
10
|
const user_js_1 = require("../user/user.js");
|
|
10
11
|
const role_js_1 = require("./role.js");
|
|
11
12
|
const role_types_js_1 = require("./role-types.js");
|
|
13
|
+
/*
|
|
14
|
+
* ── Why this class passes through `SpeakRefusals` ───────────────────────────
|
|
15
|
+
* `refuseRoleNameIfStanding` throws a `Refusal` — a name and values rather than a sentence
|
|
16
|
+
* (ADR-0054), so the wording is made where the request's language is known. This middleware is
|
|
17
|
+
* the exit that turns it into one. The older `new Error('...')` sentences in here are untouched
|
|
18
|
+
* and pass through it unchanged; moving them is a separate job.
|
|
19
|
+
*/
|
|
12
20
|
let RoleMutation = class RoleMutation {
|
|
13
21
|
/*
|
|
14
22
|
* 선언이 `User` 였다 — 돌려주는 것은 `Role` 인데(아래 TS 반환형도 그렇다) 스키마에는 사용자로
|
|
@@ -19,10 +27,7 @@ let RoleMutation = class RoleMutation {
|
|
|
19
27
|
async createRole(role, context) {
|
|
20
28
|
const { domain, user } = context.state;
|
|
21
29
|
const roleName = role.name.trim();
|
|
22
|
-
|
|
23
|
-
if (existsRole) {
|
|
24
|
-
throw new Error('role name is duplicated');
|
|
25
|
-
}
|
|
30
|
+
await (0, refuse_role_name_js_1.refuseRoleNameIfStanding)({ name: roleName, domain, separately: role.separately });
|
|
26
31
|
if (role.privileges && role.privileges.length) {
|
|
27
32
|
role.privileges = await (0, shell_1.getRepository)(privilege_js_1.Privilege).findByIds(role.privileges.map(privilege => privilege.id));
|
|
28
33
|
}
|
|
@@ -55,10 +60,7 @@ let RoleMutation = class RoleMutation {
|
|
|
55
60
|
}
|
|
56
61
|
if (name && role.name !== name) {
|
|
57
62
|
const roleName = name.trim();
|
|
58
|
-
|
|
59
|
-
if (existsRole) {
|
|
60
|
-
throw new Error('role name is duplicated');
|
|
61
|
-
}
|
|
63
|
+
await (0, refuse_role_name_js_1.refuseRoleNameIfStanding)({ name: roleName, domain, separately: patch.separately });
|
|
62
64
|
role.name = roleName;
|
|
63
65
|
}
|
|
64
66
|
if (description) {
|
|
@@ -103,9 +105,7 @@ let RoleMutation = class RoleMutation {
|
|
|
103
105
|
if (!name) {
|
|
104
106
|
throw new Error(context.t('error.value is empty', { value: context.t('field.name') }));
|
|
105
107
|
}
|
|
106
|
-
|
|
107
|
-
throw new Error('role name is duplicated');
|
|
108
|
-
}
|
|
108
|
+
await (0, refuse_role_name_js_1.refuseRoleNameIfStanding)({ name, domain, separately: patch.separately, tx });
|
|
109
109
|
results.push(await roleRepository.save({
|
|
110
110
|
domain,
|
|
111
111
|
creator: user,
|
|
@@ -123,9 +123,7 @@ let RoleMutation = class RoleMutation {
|
|
|
123
123
|
}
|
|
124
124
|
const name = patch.name?.trim();
|
|
125
125
|
if (name && name !== role.name) {
|
|
126
|
-
|
|
127
|
-
throw new Error('role name is duplicated');
|
|
128
|
-
}
|
|
126
|
+
await (0, refuse_role_name_js_1.refuseRoleNameIfStanding)({ name, domain, separately: patch.separately, tx });
|
|
129
127
|
role.name = name;
|
|
130
128
|
}
|
|
131
129
|
if (patch.description !== undefined) {
|
|
@@ -236,6 +234,7 @@ tslib_1.__decorate([
|
|
|
236
234
|
tslib_1.__metadata("design:returntype", Promise)
|
|
237
235
|
], RoleMutation.prototype, "deleteRoles", null);
|
|
238
236
|
exports.RoleMutation = RoleMutation = tslib_1.__decorate([
|
|
239
|
-
(0, type_graphql_1.Resolver)(role_js_1.Role)
|
|
237
|
+
(0, type_graphql_1.Resolver)(role_js_1.Role),
|
|
238
|
+
(0, type_graphql_1.UseMiddleware)(shell_1.SpeakRefusals)
|
|
240
239
|
], RoleMutation);
|
|
241
240
|
//# sourceMappingURL=role-mutation.js.map
|
|
@@ -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 } from 'type-graphql'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { Privilege } from '../privilege/privilege.js'\nimport { User } from '../user/user.js'\nimport { Role } from './role.js'\nimport { NewRole, RolePatch } from './role-types.js'\n\n@Resolver(Role)\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 const existsRole: Role = await getRepository(Role).findOne({ where: { name: roleName, domain: { id: domain.id } } })\n\n if (existsRole) {\n throw new Error('role name is duplicated')\n }\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 const existsRole: Role = await roleRepository.findOne({ where: { name: roleName, domain: { id: domain.id } } })\n if (existsRole) {\n throw new Error('role name is duplicated')\n }\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 if (await roleRepository.findOne({ where: { name, domain: { id: domain.id } } })) {\n throw new Error('role name is duplicated')\n }\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 if (await roleRepository.findOne({ where: { name, domain: { id: domain.id } } })) {\n throw new Error('role name is duplicated')\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 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 await getRepository(Role).delete(deletable)\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 { User } from '../user/user.js'\nimport { Role } from './role.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 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 await getRepository(Role).delete(deletable)\n }\n\n return true\n }\n}\n"]}
|
|
@@ -10,12 +10,34 @@ const user_js_1 = require("../user/user.js");
|
|
|
10
10
|
const role_js_1 = require("./role.js");
|
|
11
11
|
const role_types_js_1 = require("./role-types.js");
|
|
12
12
|
let RoleQuery = class RoleQuery {
|
|
13
|
+
/*
|
|
14
|
+
* A role by name, from this domain or the one it inherits from.
|
|
15
|
+
*
|
|
16
|
+
* ── Why this is two lookups and not one `In(...)` ───────────────────────
|
|
17
|
+
* A name can match twice: the four duplicate checks in `role-mutation.ts` ask only about
|
|
18
|
+
* this domain, so a role may be created with the same name as one that is already visible
|
|
19
|
+
* through the parent. Measured 2026-09-20 — `createRole` accepts it, and both rows then
|
|
20
|
+
* come back from every list.
|
|
21
|
+
*
|
|
22
|
+
* With `In([own, parent])` there is no `ORDER BY`, so which of the two answered was whatever
|
|
23
|
+
* the driver returned first. Not wrong on a particular day, just unruled: the same call could
|
|
24
|
+
* answer the parent's role on one installation and this domain's on another, and a role is
|
|
25
|
+
* what a privilege check reads.
|
|
26
|
+
*
|
|
27
|
+
* This domain's own comes first. Both readings of what a duplicate should mean point the same
|
|
28
|
+
* way — if the platform ends up forbidding the name, the rows that already exist still have
|
|
29
|
+
* to resolve somewhere, and if shadowing becomes the official meaning this is what it means.
|
|
30
|
+
* ⚠ It is **not** a ruling on that (ADR-0062 decision 3, still with the architect). It is the
|
|
31
|
+
* deterministic reading of either.
|
|
32
|
+
*/
|
|
13
33
|
async role(name, context) {
|
|
14
34
|
const { domain } = context.state;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
35
|
+
const repository = (0, shell_1.getRepository)(role_js_1.Role);
|
|
36
|
+
const own = await repository.findOneBy({ domain: { id: domain.id }, name });
|
|
37
|
+
if (own || !domain.parentId) {
|
|
38
|
+
return own;
|
|
39
|
+
}
|
|
40
|
+
return await repository.findOneBy({ domain: { id: domain.parentId }, name });
|
|
19
41
|
}
|
|
20
42
|
async roles(params, context) {
|
|
21
43
|
const { domain } = context.state;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role-query.js","sourceRoot":"","sources":["../../../server/service/role/role-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { In } from 'typeorm'\nimport { Arg, Args, Ctx, Directive, FieldResolver, Int, Query, Resolver, Root } from 'type-graphql'\nimport { SelectQueryBuilder } from 'typeorm'\n\nimport { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'\n\nimport { Privilege } from '../privilege/privilege.js'\nimport { User } from '../user/user.js'\nimport { Role } from './role.js'\nimport { RoleList, RolePrivilege, UserRole, RoleBrief } from './role-types.js'\n\n@Resolver(Role)\nexport class RoleQuery {\n @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => Role, { description: 'To fetch role' })\n async role(@Arg('name') name: string, @Ctx() context: ResolverContext): Promise<Role> {\n const { domain } = context.state\n return await getRepository(Role).findOneBy({\n domain: { id: In([domain.id, domain.parentId].filter(Boolean)) },\n name\n })\n }\n\n @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => RoleList, { description: 'To fetch multiple users' })\n async roles(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<RoleList> {\n const { domain } = context.state\n\n const [items, total] = await getQueryBuilderFromListParams({\n repository: getRepository(Role),\n params,\n domain,\n searchables: ['name', 'description']\n }).getManyAndCount()\n\n return { items, total }\n }\n\n /**\n * 한 역할의 권한 — **전체 목록과 이 역할이 가진 것을 한 번에** 돌려준다.\n *\n * 권한 편집 화면이 필요한 것은 「무엇이 있나」와 「이 역할이 무엇을 갖나」 둘인데, 그것을\n * 질의 둘로 가르면 화면이 둘을 짝지어야 하고 그 짝짓기가 역할 **이름**으로 이루어졌다\n * (옛 `role-privilege-editor` — 같은 이름의 부모 도메인 역할과 헷갈렸다). 여기서 `roleId` 로\n * 짝지어 주면 화면이 그 일을 안 한다.\n *\n * ── 원시 SQL 을 걷어냈다 ────────────────────────────────────────────────────\n * 셋을 동시에 잃고 있었다.\n *\n * ① `roleId` 를 문자열로 이어 붙였다. `user:query` 를 가진 사람이 임의의 SQL 을 넣을 수 있다.\n * ② `CASE WHEN … THEN true` 는 방언이다 — MSSQL 에 boolean 리터럴이 없다. 이 저장소는\n * DB 5종을 지원한다.\n * ③ 도메인을 안 봤다. 남의 도메인 역할의 `roleId` 로도 답했다.\n *\n * 권한 마스터는 전체 배포에 84행 규모(도메인마다 늘지 않는다)이므로 관계를 그대로 읽는다.\n */\n @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => [RolePrivilege], { description: 'To fetch privileges of a role' })\n async rolePrivileges(@Arg('roleId') roleId: string, @Ctx() context: ResolverContext): Promise<RolePrivilege[]> {\n const { domain } = context.state\n\n /* 상속으로 보이는 부모 도메인 역할까지가 범위다 — `role` 질의와 같은 규칙. */\n const role = await getRepository(Role).findOne({\n where: {\n id: roleId,\n domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }\n },\n relations: ['privileges']\n })\n\n if (!role) {\n return []\n }\n\n const assigned = new Set((role.privileges || []).map(privilege => privilege.id))\n\n return (await getRepository(Privilege).find()).map(({ id, name, category, description }) => ({\n id,\n name,\n category,\n description,\n assigned: assigned.has(id)\n }))\n }\n\n /**\n * 한 사람의 역할 — **부여할 수 있는 전체와 이 사람이 가진 것을 한 번에.**\n *\n * `rolePrivileges` 와 같은 모양이고, **같은 결함을 갖고 있었다.** 한 자리를 고쳤으면 두 번째\n * 자리도 재야 한다는 것이 이 파일에서 그대로 났다.\n *\n * ① `UR.users_id` 자리에 `userId` 를 문자열로 이어 붙였다. `user:query` 를 가진\n * 사람이 임의의 SQL 을 넣을 수 있다.\n * ② `CASE WHEN … THEN true` 는 방언이다 — MSSQL 에 boolean 리터럴이 없다(DB 5종 지원).\n * ③ `FROM roles` — **도메인을 안 봤다.** 남의 도메인 역할까지 전부 돌려줬다.\n *\n * ③ 을 고치면서 범위를 `role`·`rolePrivileges` 와 같게 뒀다 — 내 도메인과 부모 1단.\n * 그것이 실제로 부여할 수 있는 범위다.\n *\n * 지금 이 질의를 부르는 화면은 없다(옛 `user-role-editor` 는 `user(email:)` 로 읽었다).\n * 그래서 범위를 좁히는 것이 깨뜨릴 소비처가 없다.\n */\n @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => [UserRole], { description: 'To fetch roles of a user' })\n async userRoles(@Arg('userId') userId: string, @Ctx() context: ResolverContext): Promise<UserRole[]> {\n const { domain } = context.state\n\n const user = await getRepository(User).findOne({ where: { id: userId }, relations: ['roles'] })\n const assigned = new Set((user?.roles || []).map(role => role.id))\n\n const roles = await getRepository(Role).find({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) } }\n })\n\n return roles.map(({ id, name, description }) => ({ id, name, description, assigned: assigned.has(id) }))\n }\n\n @Query(returns => [RoleBrief], { description: 'To fetch roles of current user (id, name only)' })\n async myRoles(@Ctx() context: ResolverContext): Promise<RoleBrief[]> {\n const { user, domain } = context.state\n\n const roles = await getRepository(Role)\n .createQueryBuilder('ROLE')\n .leftJoin('ROLE.users', 'USER')\n .leftJoin('ROLE.domain', 'ROLE_DOMAIN')\n .where('USER.id = :userId', { userId: user.id })\n .andWhere('ROLE_DOMAIN.id In(:...domainIds)', { domainIds: [domain.id, domain.parentId].filter(Boolean) })\n .select(['ROLE.id', 'ROLE.name'])\n .getMany()\n\n return roles\n }\n\n /*\n * `checkRolePreset` stood here and has been removed. It read `config.get('rolePreset')`\n * and returned it unchanged, declared as `[Role]`.\n *\n * Nothing set that config key and nothing called the query, across every repository on\n * this machine — and it carried no `@privilege`, so any signed-in user could read\n * whatever an installation later put there. Empty today, quietly public the day someone\n * fills it. Role templates (`service/role-template/`) do what this was reaching for, as\n * a boot registry rather than a config blob.\n *\n * auth-base ships to npm, so consumers outside this repository cannot be counted. The\n * removal is recorded in the changelog — that is the only channel they have.\n */\n\n @FieldResolver(type => Domain)\n async domain(@Root() role: Role) {\n return await getRepository(Domain).findOneBy({ id: role.domainId })\n }\n\n /**\n * Role의 users 필드를 위한 리졸버\n * 사용자 조회 권한이 있는 경우에만 접근 가능\n */\n @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @FieldResolver(type => [User])\n async users(@Root() role: Role, @Ctx() context: ResolverContext) {\n const roleData = await getRepository(Role).findOne({\n where: { id: role.id },\n relations: ['users']\n })\n\n return roleData?.users || []\n }\n\n /**\n * 이 역할이 여는 권한이 **몇 개인가.**\n *\n * ── 왜 수만 따로 있나 ──────────────────────────────────────────────────────\n * 목록 화면이 「이 역할이 무엇을 여나」를 말해야 하는데, 그것을 위해 권한 **행**을 받고 있었다.\n *\n * ```\n * [확인함] roles(limit:20) 권한 포함 5,731 bytes · 69ms\n * [확인함] roles(limit:20) 권한 없이 518 bytes · 7ms → 11.1배 · 10배\n * ```\n *\n * 역할 2개에 권한 행 60개를 받아서 화면에는 잘린 코드 용어 셋을 그렸다. 역할 40개 ×\n * 권한 100개면 한 페이지에 4,000행이다. **`privileges` 필드 리졸버가 역할마다 한 번씩 도는\n * 것**까지 함께 없어지는 것이 더 큰 쪽이다.\n *\n * 그래서 목록은 이 칸을 쓴다 — 행을 안 받고 수를 받는다. 권한을 실제로 고치는 화면은\n * `rolePrivileges(roleId:)` 로 따로 읽는다(전체 목록 + 이 역할이 가진 것을 한 번에).\n *\n * ── 왜 관계를 세지 않고 질의로 세나 ────────────────────────────────────────\n * `relation(...).loadMany()` 는 행을 다 읽고 길이를 재는 것이라 여기서 고치려는 것을 그대로\n * 다시 한다. `getCount()` 는 DB 가 센다.\n *\n * 조인 테이블의 주인은 `Privilege` 쪽이지만(`privilege.ts:72` 의 `@JoinTable`), 양쪽이 관계를\n * 선언하고 있으므로 어느 쪽에서 조인해도 같다.\n */\n @FieldResolver(type => Int)\n async privilegeCount(@Root() role: Role): Promise<number> {\n return await getRepository(Privilege)\n .createQueryBuilder('PRIVILEGE')\n .innerJoin('PRIVILEGE.roles', 'ROLE')\n .where('ROLE.id = :id', { id: role.id })\n .getCount()\n }\n\n @FieldResolver(type => [Privilege])\n async privileges(@Root() role: Role) {\n return (\n await getRepository(Role).findOne({\n where: { id: role.id },\n relations: ['privileges']\n })\n ).privileges\n }\n\n @FieldResolver(type => User)\n async updater(@Root() role: Role): Promise<User> {\n return await getRepository(User).findOneBy({ id: role.updaterId })\n }\n\n @FieldResolver(type => User)\n async creator(@Root() role: Role): Promise<User> {\n return await getRepository(User).findOneBy({ id: role.creatorId })\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"role-query.js","sourceRoot":"","sources":["../../../server/service/role/role-query.ts"],"names":[],"mappings":";;;;AAAA,qCAA4B;AAC5B,+CAAmG;AAGnG,iDAAuG;AAEvG,4DAAqD;AACrD,6CAAsC;AACtC,uCAAgC;AAChC,mDAA8E;AAGvE,IAAM,SAAS,GAAf,MAAM,SAAS;IACpB;;;;;;;;;;;;;;;;;;;OAmBG;IAGG,AAAN,KAAK,CAAC,IAAI,CAAc,IAAY,EAAS,OAAwB;QACnE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAChC,MAAM,UAAU,GAAG,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAA;QAEtC,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;QAE3E,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YAC5B,OAAO,GAAG,CAAA;QACZ,CAAC;QAED,OAAO,MAAM,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;IAC9E,CAAC;IAIK,AAAN,KAAK,CAAC,KAAK,CAA0B,MAAiB,EAAS,OAAwB;QACrF,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAEhC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,IAAA,qCAA6B,EAAC;YACzD,UAAU,EAAE,IAAA,qBAAa,EAAC,cAAI,CAAC;YAC/B,MAAM;YACN,MAAM;YACN,WAAW,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC;SACrC,CAAC,CAAC,eAAe,EAAE,CAAA;QAEpB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;IACzB,CAAC;IAED;;;;;;;;;;;;;;;;;OAiBG;IAGG,AAAN,KAAK,CAAC,cAAc,CAAgB,MAAc,EAAS,OAAwB;QACjF,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAEhC,mDAAmD;QACnD,MAAM,IAAI,GAAG,MAAM,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAC,OAAO,CAAC;YAC7C,KAAK,EAAE;gBACL,EAAE,EAAE,MAAM;gBACV,MAAM,EAAE,EAAE,EAAE,EAAE,IAAA,YAAE,EAAC,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE;aACjE;YACD,SAAS,EAAE,CAAC,YAAY,CAAC;SAC1B,CAAC,CAAA;QAEF,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,EAAE,CAAA;QACX,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAA;QAEhF,OAAO,CAAC,MAAM,IAAA,qBAAa,EAAC,wBAAS,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;YAC3F,EAAE;YACF,IAAI;YACJ,QAAQ;YACR,WAAW;YACX,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;SAC3B,CAAC,CAAC,CAAA;IACL,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IAGG,AAAN,KAAK,CAAC,SAAS,CAAgB,MAAc,EAAS,OAAwB;QAC5E,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAEhC,MAAM,IAAI,GAAG,MAAM,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAC/F,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;QAElE,MAAM,KAAK,GAAG,MAAM,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAC,IAAI,CAAC;YAC3C,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAA,YAAE,EAAC,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE;SAC5E,CAAC,CAAA;QAEF,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IAC1G,CAAC;IAGK,AAAN,KAAK,CAAC,OAAO,CAAQ,OAAwB;QAC3C,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAEtC,MAAM,KAAK,GAAG,MAAM,IAAA,qBAAa,EAAC,cAAI,CAAC;aACpC,kBAAkB,CAAC,MAAM,CAAC;aAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;aAC9B,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;aACtC,KAAK,CAAC,mBAAmB,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;aAC/C,QAAQ,CAAC,kCAAkC,EAAE,EAAE,SAAS,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;aACzG,MAAM,CAAC,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;aAChC,OAAO,EAAE,CAAA;QAEZ,OAAO,KAAK,CAAA;IACd,CAAC;IAED;;;;;;;;;;;;OAYG;IAGG,AAAN,KAAK,CAAC,MAAM,CAAS,IAAU;QAC7B,OAAO,MAAM,IAAA,qBAAa,EAAC,cAAM,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAA;IACrE,CAAC;IAED;;;OAGG;IAGG,AAAN,KAAK,CAAC,KAAK,CAAS,IAAU,EAAS,OAAwB;QAC7D,MAAM,QAAQ,GAAG,MAAM,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAC,OAAO,CAAC;YACjD,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE;YACtB,SAAS,EAAE,CAAC,OAAO,CAAC;SACrB,CAAC,CAAA;QAEF,OAAO,QAAQ,EAAE,KAAK,IAAI,EAAE,CAAA;IAC9B,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IAEG,AAAN,KAAK,CAAC,cAAc,CAAS,IAAU;QACrC,OAAO,MAAM,IAAA,qBAAa,EAAC,wBAAS,CAAC;aAClC,kBAAkB,CAAC,WAAW,CAAC;aAC/B,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC;aACpC,KAAK,CAAC,eAAe,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;aACvC,QAAQ,EAAE,CAAA;IACf,CAAC;IAGK,AAAN,KAAK,CAAC,UAAU,CAAS,IAAU;QACjC,OAAO,CACL,MAAM,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAC,OAAO,CAAC;YAChC,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE;YACtB,SAAS,EAAE,CAAC,YAAY,CAAC;SAC1B,CAAC,CACH,CAAC,UAAU,CAAA;IACd,CAAC;IAGK,AAAN,KAAK,CAAC,OAAO,CAAS,IAAU;QAC9B,OAAO,MAAM,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAA;IACpE,CAAC;IAGK,AAAN,KAAK,CAAC,OAAO,CAAS,IAAU;QAC9B,OAAO,MAAM,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAA;IACpE,CAAC;CACF,CAAA;AAzOY,8BAAS;AAuBd;IAFL,IAAA,wBAAS,EAAC,oGAAoG,CAAC;IAC/G,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,cAAI,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE,CAAC;IAC7C,mBAAA,IAAA,kBAAG,EAAC,MAAM,CAAC,CAAA;IAAgB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;qCAW3C;AAIK;IAFL,IAAA,wBAAS,EAAC,oGAAoG,CAAC;IAC/G,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,wBAAQ,EAAE,EAAE,WAAW,EAAE,yBAAyB,EAAE,CAAC;IAC1D,mBAAA,IAAA,mBAAI,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAS,CAAC,CAAA;IAAqB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;6CAAjB,iBAAS;;sCAWrD;AAsBK;IAFL,IAAA,wBAAS,EAAC,oGAAoG,CAAC;IAC/G,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC,6BAAa,CAAC,EAAE,EAAE,WAAW,EAAE,+BAA+B,EAAE,CAAC;IAC9D,mBAAA,IAAA,kBAAG,EAAC,QAAQ,CAAC,CAAA;IAAkB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;+CAyBzD;AAqBK;IAFL,IAAA,wBAAS,EAAC,oGAAoG,CAAC;IAC/G,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC,wBAAQ,CAAC,EAAE,EAAE,WAAW,EAAE,0BAA0B,EAAE,CAAC;IACzD,mBAAA,IAAA,kBAAG,EAAC,QAAQ,CAAC,CAAA;IAAkB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;0CAWpD;AAGK;IADL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC,yBAAS,CAAC,EAAE,EAAE,WAAW,EAAE,gDAAgD,EAAE,CAAC;IAClF,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;wCAanB;AAiBK;IADL,IAAA,4BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;IAChB,mBAAA,IAAA,mBAAI,GAAE,CAAA;;6CAAO,cAAI;;uCAE9B;AAQK;IAFL,IAAA,wBAAS,EAAC,oGAAoG,CAAC;IAC/G,IAAA,4BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,cAAI,CAAC,CAAC;IACjB,mBAAA,IAAA,mBAAI,GAAE,CAAA;IAAc,mBAAA,IAAA,kBAAG,GAAE,CAAA;;6CAAZ,cAAI;;sCAO7B;AA4BK;IADL,IAAA,4BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,CAAC;IACL,mBAAA,IAAA,mBAAI,GAAE,CAAA;;6CAAO,cAAI;;+CAMtC;AAGK;IADL,IAAA,4BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,wBAAS,CAAC,CAAC;IACjB,mBAAA,IAAA,mBAAI,GAAE,CAAA;;6CAAO,cAAI;;2CAOlC;AAGK;IADL,IAAA,4BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,cAAI,CAAC;IACb,mBAAA,IAAA,mBAAI,GAAE,CAAA;;6CAAO,cAAI;;wCAE/B;AAGK;IADL,IAAA,4BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,cAAI,CAAC;IACb,mBAAA,IAAA,mBAAI,GAAE,CAAA;;6CAAO,cAAI;;wCAE/B;oBAxOU,SAAS;IADrB,IAAA,uBAAQ,EAAC,cAAI,CAAC;GACF,SAAS,CAyOrB","sourcesContent":["import { In } from 'typeorm'\nimport { Arg, Args, Ctx, Directive, FieldResolver, Int, Query, Resolver, Root } from 'type-graphql'\nimport { SelectQueryBuilder } from 'typeorm'\n\nimport { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'\n\nimport { Privilege } from '../privilege/privilege.js'\nimport { User } from '../user/user.js'\nimport { Role } from './role.js'\nimport { RoleList, RolePrivilege, UserRole, RoleBrief } from './role-types.js'\n\n@Resolver(Role)\nexport class RoleQuery {\n /*\n * A role by name, from this domain or the one it inherits from.\n *\n * ── Why this is two lookups and not one `In(...)` ───────────────────────\n * A name can match twice: the four duplicate checks in `role-mutation.ts` ask only about\n * this domain, so a role may be created with the same name as one that is already visible\n * through the parent. Measured 2026-09-20 — `createRole` accepts it, and both rows then\n * come back from every list.\n *\n * With `In([own, parent])` there is no `ORDER BY`, so which of the two answered was whatever\n * the driver returned first. Not wrong on a particular day, just unruled: the same call could\n * answer the parent's role on one installation and this domain's on another, and a role is\n * what a privilege check reads.\n *\n * This domain's own comes first. Both readings of what a duplicate should mean point the same\n * way — if the platform ends up forbidding the name, the rows that already exist still have\n * to resolve somewhere, and if shadowing becomes the official meaning this is what it means.\n * ⚠ It is **not** a ruling on that (ADR-0062 decision 3, still with the architect). It is the\n * deterministic reading of either.\n */\n @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => Role, { description: 'To fetch role' })\n async role(@Arg('name') name: string, @Ctx() context: ResolverContext): Promise<Role> {\n const { domain } = context.state\n const repository = getRepository(Role)\n\n const own = await repository.findOneBy({ domain: { id: domain.id }, name })\n\n if (own || !domain.parentId) {\n return own\n }\n\n return await repository.findOneBy({ domain: { id: domain.parentId }, name })\n }\n\n @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => RoleList, { description: 'To fetch multiple users' })\n async roles(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<RoleList> {\n const { domain } = context.state\n\n const [items, total] = await getQueryBuilderFromListParams({\n repository: getRepository(Role),\n params,\n domain,\n searchables: ['name', 'description']\n }).getManyAndCount()\n\n return { items, total }\n }\n\n /**\n * 한 역할의 권한 — **전체 목록과 이 역할이 가진 것을 한 번에** 돌려준다.\n *\n * 권한 편집 화면이 필요한 것은 「무엇이 있나」와 「이 역할이 무엇을 갖나」 둘인데, 그것을\n * 질의 둘로 가르면 화면이 둘을 짝지어야 하고 그 짝짓기가 역할 **이름**으로 이루어졌다\n * (옛 `role-privilege-editor` — 같은 이름의 부모 도메인 역할과 헷갈렸다). 여기서 `roleId` 로\n * 짝지어 주면 화면이 그 일을 안 한다.\n *\n * ── 원시 SQL 을 걷어냈다 ────────────────────────────────────────────────────\n * 셋을 동시에 잃고 있었다.\n *\n * ① `roleId` 를 문자열로 이어 붙였다. `user:query` 를 가진 사람이 임의의 SQL 을 넣을 수 있다.\n * ② `CASE WHEN … THEN true` 는 방언이다 — MSSQL 에 boolean 리터럴이 없다. 이 저장소는\n * DB 5종을 지원한다.\n * ③ 도메인을 안 봤다. 남의 도메인 역할의 `roleId` 로도 답했다.\n *\n * 권한 마스터는 전체 배포에 84행 규모(도메인마다 늘지 않는다)이므로 관계를 그대로 읽는다.\n */\n @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => [RolePrivilege], { description: 'To fetch privileges of a role' })\n async rolePrivileges(@Arg('roleId') roleId: string, @Ctx() context: ResolverContext): Promise<RolePrivilege[]> {\n const { domain } = context.state\n\n /* 상속으로 보이는 부모 도메인 역할까지가 범위다 — `role` 질의와 같은 규칙. */\n const role = await getRepository(Role).findOne({\n where: {\n id: roleId,\n domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }\n },\n relations: ['privileges']\n })\n\n if (!role) {\n return []\n }\n\n const assigned = new Set((role.privileges || []).map(privilege => privilege.id))\n\n return (await getRepository(Privilege).find()).map(({ id, name, category, description }) => ({\n id,\n name,\n category,\n description,\n assigned: assigned.has(id)\n }))\n }\n\n /**\n * 한 사람의 역할 — **부여할 수 있는 전체와 이 사람이 가진 것을 한 번에.**\n *\n * `rolePrivileges` 와 같은 모양이고, **같은 결함을 갖고 있었다.** 한 자리를 고쳤으면 두 번째\n * 자리도 재야 한다는 것이 이 파일에서 그대로 났다.\n *\n * ① `UR.users_id` 자리에 `userId` 를 문자열로 이어 붙였다. `user:query` 를 가진\n * 사람이 임의의 SQL 을 넣을 수 있다.\n * ② `CASE WHEN … THEN true` 는 방언이다 — MSSQL 에 boolean 리터럴이 없다(DB 5종 지원).\n * ③ `FROM roles` — **도메인을 안 봤다.** 남의 도메인 역할까지 전부 돌려줬다.\n *\n * ③ 을 고치면서 범위를 `role`·`rolePrivileges` 와 같게 뒀다 — 내 도메인과 부모 1단.\n * 그것이 실제로 부여할 수 있는 범위다.\n *\n * 지금 이 질의를 부르는 화면은 없다(옛 `user-role-editor` 는 `user(email:)` 로 읽었다).\n * 그래서 범위를 좁히는 것이 깨뜨릴 소비처가 없다.\n */\n @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => [UserRole], { description: 'To fetch roles of a user' })\n async userRoles(@Arg('userId') userId: string, @Ctx() context: ResolverContext): Promise<UserRole[]> {\n const { domain } = context.state\n\n const user = await getRepository(User).findOne({ where: { id: userId }, relations: ['roles'] })\n const assigned = new Set((user?.roles || []).map(role => role.id))\n\n const roles = await getRepository(Role).find({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) } }\n })\n\n return roles.map(({ id, name, description }) => ({ id, name, description, assigned: assigned.has(id) }))\n }\n\n @Query(returns => [RoleBrief], { description: 'To fetch roles of current user (id, name only)' })\n async myRoles(@Ctx() context: ResolverContext): Promise<RoleBrief[]> {\n const { user, domain } = context.state\n\n const roles = await getRepository(Role)\n .createQueryBuilder('ROLE')\n .leftJoin('ROLE.users', 'USER')\n .leftJoin('ROLE.domain', 'ROLE_DOMAIN')\n .where('USER.id = :userId', { userId: user.id })\n .andWhere('ROLE_DOMAIN.id In(:...domainIds)', { domainIds: [domain.id, domain.parentId].filter(Boolean) })\n .select(['ROLE.id', 'ROLE.name'])\n .getMany()\n\n return roles\n }\n\n /*\n * `checkRolePreset` stood here and has been removed. It read `config.get('rolePreset')`\n * and returned it unchanged, declared as `[Role]`.\n *\n * Nothing set that config key and nothing called the query, across every repository on\n * this machine — and it carried no `@privilege`, so any signed-in user could read\n * whatever an installation later put there. Empty today, quietly public the day someone\n * fills it. Role templates (`service/role-template/`) do what this was reaching for, as\n * a boot registry rather than a config blob.\n *\n * auth-base ships to npm, so consumers outside this repository cannot be counted. The\n * removal is recorded in the changelog — that is the only channel they have.\n */\n\n @FieldResolver(type => Domain)\n async domain(@Root() role: Role) {\n return await getRepository(Domain).findOneBy({ id: role.domainId })\n }\n\n /**\n * Role의 users 필드를 위한 리졸버\n * 사용자 조회 권한이 있는 경우에만 접근 가능\n */\n @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\n @FieldResolver(type => [User])\n async users(@Root() role: Role, @Ctx() context: ResolverContext) {\n const roleData = await getRepository(Role).findOne({\n where: { id: role.id },\n relations: ['users']\n })\n\n return roleData?.users || []\n }\n\n /**\n * 이 역할이 여는 권한이 **몇 개인가.**\n *\n * ── 왜 수만 따로 있나 ──────────────────────────────────────────────────────\n * 목록 화면이 「이 역할이 무엇을 여나」를 말해야 하는데, 그것을 위해 권한 **행**을 받고 있었다.\n *\n * ```\n * [확인함] roles(limit:20) 권한 포함 5,731 bytes · 69ms\n * [확인함] roles(limit:20) 권한 없이 518 bytes · 7ms → 11.1배 · 10배\n * ```\n *\n * 역할 2개에 권한 행 60개를 받아서 화면에는 잘린 코드 용어 셋을 그렸다. 역할 40개 ×\n * 권한 100개면 한 페이지에 4,000행이다. **`privileges` 필드 리졸버가 역할마다 한 번씩 도는\n * 것**까지 함께 없어지는 것이 더 큰 쪽이다.\n *\n * 그래서 목록은 이 칸을 쓴다 — 행을 안 받고 수를 받는다. 권한을 실제로 고치는 화면은\n * `rolePrivileges(roleId:)` 로 따로 읽는다(전체 목록 + 이 역할이 가진 것을 한 번에).\n *\n * ── 왜 관계를 세지 않고 질의로 세나 ────────────────────────────────────────\n * `relation(...).loadMany()` 는 행을 다 읽고 길이를 재는 것이라 여기서 고치려는 것을 그대로\n * 다시 한다. `getCount()` 는 DB 가 센다.\n *\n * 조인 테이블의 주인은 `Privilege` 쪽이지만(`privilege.ts:72` 의 `@JoinTable`), 양쪽이 관계를\n * 선언하고 있으므로 어느 쪽에서 조인해도 같다.\n */\n @FieldResolver(type => Int)\n async privilegeCount(@Root() role: Role): Promise<number> {\n return await getRepository(Privilege)\n .createQueryBuilder('PRIVILEGE')\n .innerJoin('PRIVILEGE.roles', 'ROLE')\n .where('ROLE.id = :id', { id: role.id })\n .getCount()\n }\n\n @FieldResolver(type => [Privilege])\n async privileges(@Root() role: Role) {\n return (\n await getRepository(Role).findOne({\n where: { id: role.id },\n relations: ['privileges']\n })\n ).privileges\n }\n\n @FieldResolver(type => User)\n async updater(@Root() role: Role): Promise<User> {\n return await getRepository(User).findOneBy({ id: role.updaterId })\n }\n\n @FieldResolver(type => User)\n async creator(@Root() role: Role): Promise<User> {\n return await getRepository(User).findOneBy({ id: role.creatorId })\n }\n}\n"]}
|
|
@@ -5,9 +5,11 @@ export declare class NewRole {
|
|
|
5
5
|
description?: string;
|
|
6
6
|
users?: ObjectRef[];
|
|
7
7
|
privileges?: ObjectRef[];
|
|
8
|
+
separately?: boolean;
|
|
8
9
|
}
|
|
9
10
|
export declare class RolePatch {
|
|
10
11
|
id?: string;
|
|
12
|
+
separately?: boolean;
|
|
11
13
|
name?: string;
|
|
12
14
|
description?: string;
|
|
13
15
|
users?: ObjectRef[];
|
|
@@ -24,6 +24,10 @@ tslib_1.__decorate([
|
|
|
24
24
|
(0, type_graphql_1.Field)(type => [shell_1.ObjectRef], { nullable: true, description: 'Privileges to be granted by the new role.' }),
|
|
25
25
|
tslib_1.__metadata("design:type", Array)
|
|
26
26
|
], NewRole.prototype, "privileges", void 0);
|
|
27
|
+
tslib_1.__decorate([
|
|
28
|
+
(0, type_graphql_1.Field)({ nullable: true, description: "Stand this role even though the domain inherits one of the same name." }),
|
|
29
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
30
|
+
], NewRole.prototype, "separately", void 0);
|
|
27
31
|
exports.NewRole = NewRole = tslib_1.__decorate([
|
|
28
32
|
(0, type_graphql_1.InputType)({ description: 'Input for creating a new role.' })
|
|
29
33
|
], NewRole);
|
|
@@ -34,6 +38,10 @@ tslib_1.__decorate([
|
|
|
34
38
|
(0, type_graphql_1.Field)({ nullable: true, description: 'The unique identifier of the role to update.' }),
|
|
35
39
|
tslib_1.__metadata("design:type", String)
|
|
36
40
|
], RolePatch.prototype, "id", void 0);
|
|
41
|
+
tslib_1.__decorate([
|
|
42
|
+
(0, type_graphql_1.Field)({ nullable: true, description: "Stand this role even though the domain inherits one of the same name." }),
|
|
43
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
44
|
+
], RolePatch.prototype, "separately", void 0);
|
|
37
45
|
tslib_1.__decorate([
|
|
38
46
|
(0, type_graphql_1.Field)({ nullable: true, description: 'The new name for the role.' }),
|
|
39
47
|
tslib_1.__metadata("design:type", String)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role-types.js","sourceRoot":"","sources":["../../../server/service/role/role-types.ts"],"names":[],"mappings":";;;;AAAA,+CAAgE;AAChE,iDAAiD;AACjD,uCAAgC;AAGzB,IAAM,OAAO,GAAb,MAAM,OAAO;
|
|
1
|
+
{"version":3,"file":"role-types.js","sourceRoot":"","sources":["../../../server/service/role/role-types.ts"],"names":[],"mappings":"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","sourcesContent":["import { ObjectType, InputType, Field, Int } from 'type-graphql'\nimport { ObjectRef } from '@things-factory/shell'\nimport { Role } from './role.js'\n\n@InputType({ description: 'Input for creating a new role.' })\nexport class NewRole {\n @Field({ description: 'The name of the new role.' })\n name: string\n\n @Field({ nullable: true, description: 'A description for the new role.' })\n description?: string\n\n @Field(type => [ObjectRef], { nullable: true, description: 'Users to be assigned to the new role.' })\n users?: ObjectRef[]\n\n @Field(type => [ObjectRef], { nullable: true, description: 'Privileges to be granted by the new role.' })\n privileges?: ObjectRef[]\n\n /*\n * The operator saying this domain stands its own role of a name it already inherits\n * (ADR-0062 decision 3). The name stays the same — that is the point of it, because moving a\n * domain off an inherited structure means standing up the same names here first (decision 2).\n * Absent means not said, and an inherited name is refused.\n */\n @Field({ nullable: true, description: \"Stand this role even though the domain inherits one of the same name.\" })\n separately?: boolean\n}\n\n@InputType({ description: 'Input for updating (patching) an existing role.' })\nexport class RolePatch {\n @Field({ nullable: true, description: 'The unique identifier of the role to update.' })\n id?: string\n\n /*\n * The operator saying this domain stands its own role of a name it already inherits\n * (ADR-0062 decision 3). The name stays the same — that is the point of it, because moving a\n * domain off an inherited structure means standing up the same names here first (decision 2).\n * Absent means not said, and an inherited name is refused.\n */\n @Field({ nullable: true, description: \"Stand this role even though the domain inherits one of the same name.\" })\n separately?: boolean\n\n @Field({ nullable: true, description: 'The new name for the role.' })\n name?: string\n\n @Field({ nullable: true, description: 'The new description for the role.' })\n description?: string\n\n @Field(type => [ObjectRef], { nullable: true, description: 'A new list of users to be assigned to the role.' })\n users?: ObjectRef[]\n\n @Field(type => [ObjectRef], { nullable: true, description: 'A new list of privileges to be granted by the role.' })\n privileges?: ObjectRef[]\n\n @Field({ nullable: true, description: 'A flag indicating whether the role is being created, updated, or deleted.' })\n cuFlag?: string\n}\n\n@ObjectType({ description: 'A paginated list of roles.' })\nexport class RoleList {\n @Field(type => [Role], { nullable: true, description: 'The list of role items.' })\n items?: Role[]\n\n @Field(type => Int, { nullable: true, description: 'The total number of roles.' })\n total?: number\n}\n\n@ObjectType({ description: 'A brief summary of a role, containing only the ID and name.' })\nexport class RoleBrief {\n @Field({ nullable: true, description: 'The unique identifier of the role.' })\n id?: string\n\n @Field({ nullable: true, description: 'The name of the role.' })\n name?: string\n}\n\n@ObjectType({ description: 'Represents a privilege and whether it is assigned to a specific role.' })\nexport class RolePrivilege {\n @Field({ nullable: true, description: 'The unique identifier of the privilege.' })\n id?: string\n\n @Field({ nullable: true, description: 'The name of the privilege.' })\n name?: string\n\n @Field({ nullable: true, description: 'The category of the privilege.' })\n category?: string\n\n @Field({ nullable: true, description: 'A description of the privilege.' })\n description?: string\n\n @Field({ nullable: true, description: 'Indicates whether the privilege is assigned to the role.' })\n assigned?: boolean\n}\n\n@ObjectType({ description: 'Represents a role and whether it is assigned to a specific user.' })\nexport class UserRole {\n @Field({ nullable: true, description: 'The unique identifier of the role.' })\n id?: string\n\n @Field({ nullable: true, description: 'The name of the role.' })\n name?: string\n\n @Field({ nullable: true, description: 'A description of the role.' })\n description?: string\n\n @Field({ nullable: true, description: 'Indicates whether the role is assigned to the user.' })\n assigned?: boolean\n}\n"]}
|
|
@@ -35,5 +35,21 @@ export declare class RoleTemplateMutation {
|
|
|
35
35
|
* inspector hold the same privileges and take different approvals. Without a name of
|
|
36
36
|
* their own a template could be used exactly once per domain.
|
|
37
37
|
*/
|
|
38
|
-
name: string | undefined, context: ResolverContext
|
|
38
|
+
name: string | undefined, context: ResolverContext,
|
|
39
|
+
/**
|
|
40
|
+
* The operator saying this domain stands its own role of a name it already inherits
|
|
41
|
+
* (ADR-0062 decision 3).
|
|
42
|
+
*
|
|
43
|
+
* ⚠ This is where the rule bites hardest, and stopping here is the point of it. A template
|
|
44
|
+
* seeds the seats a product needs, and on a tenant with a parent those names are often
|
|
45
|
+
* already visible through inheritance — so the template would quietly put a second row of
|
|
46
|
+
* the same name beside the inherited one, which is the state the whole rule exists to
|
|
47
|
+
* prevent.
|
|
48
|
+
*
|
|
49
|
+
* The name is not changed to get past it. Standing up the same names in this domain is the
|
|
50
|
+
* first step of moving off an inherited structure (decision 2), so this says "yes, that is
|
|
51
|
+
* what I am doing" rather than "call it something else". `name` above remains for the other
|
|
52
|
+
* thing — a second seat with the same privileges under a name of its own.
|
|
53
|
+
*/
|
|
54
|
+
separately: boolean | undefined): Promise<Role>;
|
|
39
55
|
}
|