@things-factory/auth-base 10.1.192 → 10.1.198
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-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/controllers/signin.d.ts +5 -0
- package/dist-server/controllers/signin.js +25 -0
- package/dist-server/controllers/signin.js.map +1 -1
- package/dist-server/index.js +7 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/middlewares/audit-scope-middleware.d.ts +7 -0
- package/dist-server/middlewares/audit-scope-middleware.js +15 -0
- package/dist-server/middlewares/audit-scope-middleware.js.map +1 -0
- package/dist-server/middlewares/index.js +3 -0
- package/dist-server/middlewares/index.js.map +1 -1
- package/dist-server/role-tasks.d.ts +2 -0
- package/dist-server/role-tasks.js +20 -0
- package/dist-server/role-tasks.js.map +1 -0
- package/dist-server/router/auth-checkin-router.js +3 -5
- package/dist-server/router/auth-checkin-router.js.map +1 -1
- package/dist-server/router/auth-public-process-router.js +7 -1
- package/dist-server/router/auth-public-process-router.js.map +1 -1
- package/dist-server/router/site-root-router.d.ts +2 -0
- package/dist-server/router/site-root-router.js +2 -0
- package/dist-server/router/site-root-router.js.map +1 -1
- package/dist-server/service/access-request/index.d.ts +1 -1
- package/dist-server/service/audit/audit-action.d.ts +36 -0
- package/dist-server/service/audit/audit-action.js +137 -0
- package/dist-server/service/audit/audit-action.js.map +1 -0
- package/dist-server/service/audit/audit-kinds.d.ts +19 -0
- package/dist-server/service/audit/audit-kinds.js +70 -0
- package/dist-server/service/audit/audit-kinds.js.map +1 -0
- package/dist-server/service/audit/audit-query.d.ts +11 -0
- package/dist-server/service/audit/audit-query.js +295 -0
- package/dist-server/service/audit/audit-query.js.map +1 -0
- package/dist-server/service/audit/audit-recorder.d.ts +77 -0
- package/dist-server/service/audit/audit-recorder.js +247 -0
- package/dist-server/service/audit/audit-recorder.js.map +1 -0
- package/dist-server/service/audit/audit-request.d.ts +28 -0
- package/dist-server/service/audit/audit-request.js +96 -0
- package/dist-server/service/audit/audit-request.js.map +1 -0
- package/dist-server/service/audit/audit-retention.d.ts +12 -0
- package/dist-server/service/audit/audit-retention.js +42 -0
- package/dist-server/service/audit/audit-retention.js.map +1 -0
- package/dist-server/service/audit/audit-subscriber.d.ts +39 -0
- package/dist-server/service/audit/audit-subscriber.js +191 -0
- package/dist-server/service/audit/audit-subscriber.js.map +1 -0
- package/dist-server/service/audit/audit-target.d.ts +19 -0
- package/dist-server/service/audit/audit-target.js +46 -0
- package/dist-server/service/audit/audit-target.js.map +1 -0
- package/dist-server/service/audit/audit-types.d.ts +44 -0
- package/dist-server/service/audit/audit-types.js +161 -0
- package/dist-server/service/audit/audit-types.js.map +1 -0
- package/dist-server/service/audit/index.d.ts +6 -0
- package/dist-server/service/audit/index.js +11 -0
- package/dist-server/service/audit/index.js.map +1 -0
- package/dist-server/service/domain-owner/domain-owner-mutation.js +3 -0
- package/dist-server/service/domain-owner/domain-owner-mutation.js.map +1 -1
- package/dist-server/service/index.d.ts +9 -2
- package/dist-server/service/index.js +19 -4
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/open-fields.js +1 -1
- package/dist-server/service/open-fields.js.map +1 -1
- package/dist-server/service/privilege/privilege-directive.js +25 -1
- package/dist-server/service/privilege/privilege-directive.js.map +1 -1
- package/dist-server/service/role/role-mutation.js +8 -1
- package/dist-server/service/role/role-mutation.js.map +1 -1
- package/dist-server/service/role-template/index.d.ts +1 -1
- package/dist-server/service/role-template/role-task.d.ts +74 -0
- package/dist-server/service/role-template/role-task.js +113 -0
- package/dist-server/service/role-template/role-task.js.map +1 -0
- package/dist-server/service/role-template/role-template-gap-resolver.d.ts +9 -5
- package/dist-server/service/role-template/role-template-gap-resolver.js +41 -13
- package/dist-server/service/role-template/role-template-gap-resolver.js.map +1 -1
- package/dist-server/service/role-template/role-template-mutation.js +10 -1
- package/dist-server/service/role-template/role-template-mutation.js.map +1 -1
- package/dist-server/service/role-template/role-template-query.js +4 -2
- package/dist-server/service/role-template/role-template-query.js.map +1 -1
- package/dist-server/service/role-template/role-template-types.d.ts +24 -0
- package/dist-server/service/role-template/role-template-types.js +63 -1
- package/dist-server/service/role-template/role-template-types.js.map +1 -1
- package/dist-server/service/role-template/role-template.d.ts +63 -10
- package/dist-server/service/role-template/role-template.js +92 -18
- package/dist-server/service/role-template/role-template.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/dist-server/utils/check-permission.d.ts +6 -0
- package/dist-server/utils/check-permission.js +23 -8
- package/dist-server/utils/check-permission.js.map +1 -1
- package/package.json +3 -2
- package/tests/audit-db.test.ts +394 -0
- package/tests/auth-base-role-tasks.test.ts +58 -0
- package/tests/list-inheritance-db.test.ts +0 -7
- package/tests/open-doors.test.ts +2 -2
- package/tests/refusal-cause.test.ts +1 -1
- package/tests/role-task.test.ts +168 -0
- package/tests/role-template-gap-db.test.ts +77 -2
- package/translations/en.json +13 -1
- package/translations/es.json +13 -1
- package/translations/ja.json +13 -1
- package/translations/ko.json +13 -1
- package/translations/ms.json +13 -1
- package/translations/zh.json +13 -1
|
@@ -9,6 +9,7 @@ export { privilegeOfField, type FieldGate } from './privilege/privilege-directiv
|
|
|
9
9
|
export { privilegeRejection } from './privilege/privilege-rejection.js';
|
|
10
10
|
export { type OpenFieldsDeclaration } from './privilege/open-field-rule.js';
|
|
11
11
|
export * from './role-template/role-template.js';
|
|
12
|
+
export * from './role-template/role-task.js';
|
|
12
13
|
export * from './role-template/role-template-types.js';
|
|
13
14
|
export * from './user/user.js';
|
|
14
15
|
export * from './partner/partner.js';
|
|
@@ -17,7 +18,11 @@ export * from './invitation/invitation.js';
|
|
|
17
18
|
export * from './app-binding/app-binding.js';
|
|
18
19
|
export * from './password-history/password-history.js';
|
|
19
20
|
export * from './verification-token/verification-token.js';
|
|
20
|
-
export * from './
|
|
21
|
+
export * from './audit/audit-request.js';
|
|
22
|
+
export * from './audit/audit-action.js';
|
|
23
|
+
export { registerAuditKind } from './audit/audit-kinds.js';
|
|
24
|
+
export { recordAuditAction, noteAuditAction, flushAudit, type AuditActionInput } from './audit/audit-recorder.js';
|
|
25
|
+
export { purgeExpiredAudit, startAuditRetention } from './audit/audit-retention.js';
|
|
21
26
|
export * from './web-auth-credential/web-auth-credential.js';
|
|
22
27
|
export * from './domain-link/domain-link.js';
|
|
23
28
|
export * from './domain-owner/domain-owner.js';
|
|
@@ -33,10 +38,12 @@ export * from './role/role-types.js';
|
|
|
33
38
|
export * from './user/user-types.js';
|
|
34
39
|
export * from './domain-link/domain-link-types.js';
|
|
35
40
|
export * from './access-request/access-request-types.js';
|
|
36
|
-
export declare const entities: (typeof import("./
|
|
41
|
+
export declare const entities: (typeof import("./users-auth-providers/users-auth-providers.js").UsersAuthProviders | typeof import("./auth-provider/auth-provider.js").AuthProvider | typeof import("./application/application.js").Application | typeof import("./appliance/appliance.js").Appliance | typeof import("./privilege/privilege.js").Privilege | typeof import("./role/role.js").Role | typeof import("./user/user.js").User | typeof import("./partner/partner.js").Partner | typeof import("./granted-role/granted-role.js").GrantedRole | typeof import("./invitation/invitation.js").Invitation | typeof import("./password-history/password-history.js").PasswordHistory | typeof import("./verification-token/verification-token.js").VerificationToken | typeof import("./verification-token/verification-token.js").VerificationTokenType | typeof import("./audit/audit-request.js").AuditRequest | typeof import("./audit/audit-action.js").AuditAction | typeof import("./web-auth-credential/web-auth-credential.js").WebAuthCredential | typeof import("./domain-link/domain-link.js").DomainLink | typeof import("./domain-owner/domain-owner.js").DomainOwner | typeof import("./access-request/access-request.js").AccessRequest)[];
|
|
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").InvitationAuditSubscriber | typeof import("./audit/audit-subscriber.js").WebAuthCredentialAuditSubscriber)[];
|
|
37
43
|
export declare const schema: {
|
|
38
44
|
typeDefs: {
|
|
39
45
|
privilegeDirectiveTypeDefs: import("graphql").DocumentNode;
|
|
46
|
+
auditTargetDirectiveTypeDefs: import("graphql").DocumentNode;
|
|
40
47
|
};
|
|
41
48
|
openFields: import("./privilege/open-field-rule.js").OpenFieldsDeclaration;
|
|
42
49
|
resolverClasses: any[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.schema = exports.entities = exports.privilegeRejection = exports.privilegeOfField = void 0;
|
|
3
|
+
exports.schema = exports.subscribers = exports.entities = exports.startAuditRetention = exports.purgeExpiredAudit = exports.flushAudit = exports.noteAuditAction = exports.recordAuditAction = exports.registerAuditKind = exports.privilegeRejection = exports.privilegeOfField = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
/* IMPORT ENTITIES AND RESOLVERS */
|
|
6
6
|
const index_js_1 = require("./access-request/index.js");
|
|
@@ -12,7 +12,8 @@ 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
14
|
const index_js_9 = require("./invitation/index.js");
|
|
15
|
-
const index_js_10 = require("./
|
|
15
|
+
const index_js_10 = require("./audit/index.js");
|
|
16
|
+
const audit_target_js_1 = require("./audit/audit-target.js");
|
|
16
17
|
const index_js_11 = require("./partner/index.js");
|
|
17
18
|
const index_js_12 = require("./password-history/index.js");
|
|
18
19
|
const index_js_13 = require("./privilege/index.js");
|
|
@@ -40,6 +41,7 @@ Object.defineProperty(exports, "privilegeOfField", { enumerable: true, get: func
|
|
|
40
41
|
var privilege_rejection_js_1 = require("./privilege/privilege-rejection.js");
|
|
41
42
|
Object.defineProperty(exports, "privilegeRejection", { enumerable: true, get: function () { return privilege_rejection_js_1.privilegeRejection; } });
|
|
42
43
|
tslib_1.__exportStar(require("./role-template/role-template.js"), exports);
|
|
44
|
+
tslib_1.__exportStar(require("./role-template/role-task.js"), exports);
|
|
43
45
|
tslib_1.__exportStar(require("./role-template/role-template-types.js"), exports);
|
|
44
46
|
tslib_1.__exportStar(require("./user/user.js"), exports);
|
|
45
47
|
tslib_1.__exportStar(require("./partner/partner.js"), exports);
|
|
@@ -48,7 +50,17 @@ tslib_1.__exportStar(require("./invitation/invitation.js"), exports);
|
|
|
48
50
|
tslib_1.__exportStar(require("./app-binding/app-binding.js"), exports);
|
|
49
51
|
tslib_1.__exportStar(require("./password-history/password-history.js"), exports);
|
|
50
52
|
tslib_1.__exportStar(require("./verification-token/verification-token.js"), exports);
|
|
51
|
-
tslib_1.__exportStar(require("./
|
|
53
|
+
tslib_1.__exportStar(require("./audit/audit-request.js"), exports);
|
|
54
|
+
tslib_1.__exportStar(require("./audit/audit-action.js"), exports);
|
|
55
|
+
var audit_kinds_js_1 = require("./audit/audit-kinds.js");
|
|
56
|
+
Object.defineProperty(exports, "registerAuditKind", { enumerable: true, get: function () { return audit_kinds_js_1.registerAuditKind; } });
|
|
57
|
+
var audit_recorder_js_1 = require("./audit/audit-recorder.js");
|
|
58
|
+
Object.defineProperty(exports, "recordAuditAction", { enumerable: true, get: function () { return audit_recorder_js_1.recordAuditAction; } });
|
|
59
|
+
Object.defineProperty(exports, "noteAuditAction", { enumerable: true, get: function () { return audit_recorder_js_1.noteAuditAction; } });
|
|
60
|
+
Object.defineProperty(exports, "flushAudit", { enumerable: true, get: function () { return audit_recorder_js_1.flushAudit; } });
|
|
61
|
+
var audit_retention_js_1 = require("./audit/audit-retention.js");
|
|
62
|
+
Object.defineProperty(exports, "purgeExpiredAudit", { enumerable: true, get: function () { return audit_retention_js_1.purgeExpiredAudit; } });
|
|
63
|
+
Object.defineProperty(exports, "startAuditRetention", { enumerable: true, get: function () { return audit_retention_js_1.startAuditRetention; } });
|
|
52
64
|
tslib_1.__exportStar(require("./web-auth-credential/web-auth-credential.js"), exports);
|
|
53
65
|
tslib_1.__exportStar(require("./domain-link/domain-link.js"), exports);
|
|
54
66
|
tslib_1.__exportStar(require("./domain-owner/domain-owner.js"), exports);
|
|
@@ -85,9 +97,12 @@ exports.entities = [
|
|
|
85
97
|
...index_js_20.entities,
|
|
86
98
|
...index_js_1.entities
|
|
87
99
|
];
|
|
100
|
+
/* The audit of who may do what, written where it is saved (ADR-0141 §3.3). */
|
|
101
|
+
exports.subscribers = [...index_js_10.subscribers];
|
|
88
102
|
exports.schema = {
|
|
89
103
|
typeDefs: {
|
|
90
|
-
privilegeDirectiveTypeDefs: privilege_directive_js_1.privilegeDirectiveTypeDefs
|
|
104
|
+
privilegeDirectiveTypeDefs: privilege_directive_js_1.privilegeDirectiveTypeDefs,
|
|
105
|
+
auditTargetDirectiveTypeDefs: audit_target_js_1.auditTargetDirectiveTypeDefs
|
|
91
106
|
},
|
|
92
107
|
/* The fields that carry no @privilege, and why — `open-fields.ts`, read by the directive. */
|
|
93
108
|
openFields: open_fields_js_1.openFields,
|
|
@@ -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,oDAAuG;AACvG,
|
|
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,oDAAuG;AACvG,gDAIyB;AACzB,6DAAsE;AACtE,kDAA+F;AAC/F,2DAAiF;AACjF,oDAAqG;AACrG,+EAA2G;AAC3G,qDAA6C;AAC7C,+CAAsF;AACtF,wDAA6E;AAC7E,+CAAsF;AACtF,6DAAqF;AACrF,8DAAsF;AACtF,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,qEAA0C;AAC1C,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,2EAAgD;AAChD,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,mBAAkB;IACrB,GAAG,oBAAuB;IAC1B,GAAG,oBAAyB;IAC5B,GAAG,oBAAa;IAChB,GAAG,oBAAyB;IAC5B,GAAG,oBAAkB;IACrB,GAAG,oBAAmB;IACtB,GAAG,mBAAqB;CACzB,CAAA;AAED,8EAA8E;AACjE,QAAA,WAAW,GAAG,CAAC,GAAG,uBAAgB,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,oBAAkB;QACrB,GAAG,oBAAuB;QAC1B,GAAG,qBAAc;QACjB,GAAG,qBAAkB;QACrB,GAAG,qBAAoB;QACvB,GAAG,oBAAsB;KAC1B;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 { entities as InvitationEntities, resolvers as InvitationResolver } from './invitation/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 } 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 './invitation/invitation.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 './invitation/invitation-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 ...InvitationEntities,\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 ...InvitationResolver,\n ...AppbindingResolver,\n ...DomainGeneratorResolver,\n ...AuditResolvers,\n ...DomainLinkResolver,\n ...DomainOwnerResolvers,\n ...AccessRequestResolvers\n ],\n directives: {\n privilege: privilegeDirectiveResolver\n }\n}\n"]}
|
|
@@ -10,7 +10,7 @@ exports.openFields = void 0;
|
|
|
10
10
|
*/
|
|
11
11
|
exports.openFields = {
|
|
12
12
|
byDesign: {
|
|
13
|
-
'Query.
|
|
13
|
+
'Query.myAuditActions': "the caller's own actions - the actor is the session's user",
|
|
14
14
|
'Query.myRoles': "the caller's own roles",
|
|
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',
|
|
@@ -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,
|
|
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,wCAAwC,EAAE,iCAAiC;QAC3E,4BAA4B,EAAE,qDAAqD;QACnF,oBAAoB,EAClB,2JAA2J;QAC7J,yBAAyB,EAAE,qFAAqF;QAChH,wBAAwB,EAAE,kCAAkC;QAC5D,4BAA4B,EAAE,oFAAoF;QAClH,iCAAiC,EAAE,qFAAqF;QACxH,wBAAwB,EACtB,6LAA6L;QAC/L,gCAAgC,EAAE,8DAA8D;KACjG;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.checkResettablePasswordToDefault': \"about the caller's own password\",\n 'Query.checkDefaultPassword': 'whether the caller is still on the default password',\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 '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 }\n}\n"]}
|
|
@@ -8,6 +8,8 @@ const graphql_tag_1 = tslib_1.__importDefault(require("graphql-tag"));
|
|
|
8
8
|
const utils_1 = require("@graphql-tools/utils");
|
|
9
9
|
const env_1 = require("@things-factory/env");
|
|
10
10
|
const check_permission_js_1 = require("../../utils/check-permission.js");
|
|
11
|
+
const audit_recorder_js_1 = require("../audit/audit-recorder.js");
|
|
12
|
+
const audit_target_js_1 = require("../audit/audit-target.js");
|
|
11
13
|
const privilege_axis_js_1 = require("./privilege-axis.js");
|
|
12
14
|
const privilege_rejection_js_1 = require("./privilege-rejection.js");
|
|
13
15
|
const open_field_rule_js_1 = require("./open-field-rule.js");
|
|
@@ -102,6 +104,14 @@ const privilegeDirectiveResolver = (schema, options = {}) => {
|
|
|
102
104
|
}
|
|
103
105
|
const { domainOwnerGranted, superUserGranted, category, privilege } = privilegeDirective;
|
|
104
106
|
const where = `${typeName}.${fieldName}`;
|
|
107
|
+
/*
|
|
108
|
+
* The audit's name for this door (ADR-0141 §3.2), and what the field says it acts on (§3.3).
|
|
109
|
+
* A door that asks ownership only is named by the ownership it asks.
|
|
110
|
+
*/
|
|
111
|
+
const door = category && privilege
|
|
112
|
+
? `${category}:${privilege}`
|
|
113
|
+
: [domainOwnerGranted && 'owner', superUserGranted && 'super'].filter(Boolean).join('+');
|
|
114
|
+
const target = (0, utils_1.getDirective)(schema, fieldConfig, 'auditTarget')?.[0];
|
|
105
115
|
/*
|
|
106
116
|
* A declaration has to name something.
|
|
107
117
|
*
|
|
@@ -174,9 +184,23 @@ const privilegeDirectiveResolver = (schema, options = {}) => {
|
|
|
174
184
|
owner: domainOwnerGranted,
|
|
175
185
|
super: superUserGranted
|
|
176
186
|
}, user, domain, unsafeIP, prohibitedPrivileges);
|
|
187
|
+
const targetKind = target?.kind;
|
|
188
|
+
const targetIds = target ? (0, audit_target_js_1.targetIdsOf)(args, target.arg) : undefined;
|
|
189
|
+
const targetRole = target ? 'requested' : undefined;
|
|
177
190
|
if (judgement.permitted === true) {
|
|
178
|
-
|
|
191
|
+
(0, audit_recorder_js_1.noteAuditAction)({ kind: 'door.check', door, decision: 'granted', basis: judgement.by, targetKind, targetIds, targetRole });
|
|
192
|
+
/* Passing the door is not doing the work - what the work came to is its own row (§3.2). */
|
|
193
|
+
try {
|
|
194
|
+
const result = await resolve.call(this, source, args, context, info);
|
|
195
|
+
(0, audit_recorder_js_1.noteAuditAction)({ kind: 'door.result', door, outcome: 'ok', returned: (0, audit_target_js_1.returnedCountOf)(result), targetKind, targetIds, targetRole });
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
(0, audit_recorder_js_1.noteAuditAction)({ kind: 'door.result', door, outcome: 'error', code: (0, audit_target_js_1.errorCodeOf)(error), targetKind, targetIds, targetRole });
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
179
202
|
}
|
|
203
|
+
(0, audit_recorder_js_1.noteAuditAction)({ kind: 'door.check', door, decision: 'denied', basis: judgement.because, targetKind, targetIds, targetRole });
|
|
180
204
|
/*
|
|
181
205
|
* 안 열렸으면 **거기서 끝이다.**
|
|
182
206
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"privilege-directive.js","sourceRoot":"","sources":["../../../server/service/privilege/privilege-directive.ts"],"names":[],"mappings":"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","sourcesContent":["import { defaultFieldResolver, GraphQLError, GraphQLSchema } from 'graphql'\nimport gql from 'graphql-tag'\n\nimport { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils'\nimport { logger } from '@things-factory/env'\n\nimport { judgePermission } from '../../utils/check-permission.js'\nimport { declaredAxes, isDeclaredAxis } from './privilege-axis.js'\nimport { privilegeRejection } from './privilege-rejection.js'\nimport {\n coverageOf,\n DeclaringModule,\n enforcementMode,\n EnforcementMode,\n judgeUndeclared,\n modulesFromLoader\n} from './open-field-rule.js'\n\nprocess['PRIVILEGES'] = {}\n\n/*\n * ── What each field asks for, by `Type.field` ─────────────────────────────────\n * `PRIVILEGES` above is the list of pairs this installation declares; it cannot answer \"what\n * does `Query.jobOrders` ask for\". Something that reaches a door by another entrance has to ask\n * exactly what the door asks — an AI tool is one (operato-twin ADR-0071 decision 2, ADR-0072\n * decision 5). Without this map each caller copied the pair by hand, and a copy drifts the day\n * the door's declaration changes.\n *\n * Three answers, and they are not the same:\n * - not in the map the schema has no such root field. A caller that named it has a typo,\n * and reading that as \"open\" is how a gate opens without an error.\n * - `null` the field exists and declares nothing. It is open.\n * - a gate what `checkPermission` is asked at that field.\n *\n * Every root field is recorded (Query / Mutation / Subscription) so the first two can be told\n * apart; a field of any other type is recorded only when it carries the directive.\n * Kept on `process` for the same reason `PRIVILEGES` is.\n */\nprocess['PRIVILEGE_OF_FIELD'] = {}\n\n/** What `checkPermission` is asked at a field — the same four things the directive passes it. */\nexport interface FieldGate {\n category?: string\n privilege?: string\n owner: boolean\n super: boolean\n}\n\n/**\n * What the field `Type.field` asks for. Valid once the schema has been built.\n *\n * `known: false` means the schema has no such root field — treat it as a mistake, never as open.\n */\nexport function privilegeOfField(typeName: string, fieldName: string): { known: false } | { known: true; gate: FieldGate | null } {\n const map = process['PRIVILEGE_OF_FIELD'] as Record<string, FieldGate | null>\n const where = `${typeName}.${fieldName}`\n return where in map ? { known: true, gate: map[where] } : { known: false }\n}\n\nconst DIRECTIVE = 'privilege'\n\n/**\n * ⚠ **다음에 목록 인자를 붙이는 사람에게.**\n *\n * 여기 `legacy` 인자가 있었다 — 권한 이름을 옮기는 동안 옛 이름도 받아 주는 별칭이었다.\n * **걷어냈다.** 이 저장소는 대개조 중이고 **호환 계층을 끼우지 않는다**: 이름을 바꾸면 바꾼\n * 것이고, 부여는 관리자가 다시 놓는다. 별칭은 「한동안」이 영원이 되는 길이었다.\n *\n * 곧 필요해지는 목록 인자는 **AND** 다(`docs/design/auth-rebuild.md` §1-(3)): 외부에서\n * 당겨오면서 남의 데이터를 앉히는 호출은 `execute` **와** 그 모듈의 `mutation` 을 **둘 다**\n * 요구해야 한다.\n *\n * 그것을 `privileges: [...]` 처럼 중립적으로 이름 지으면 다음 사람이 「이 중 하나」로 읽고,\n * 그렇게 쓰는 날 게이트가 **오류 없이** 열린다. `requiresAll: [...]` 처럼 **이름이 AND 라고\n * 말하게** 한다.\n */\nexport const privilegeDirectiveTypeDefs = gql`\n directive @privilege(\n category: String\n privilege: String\n domainOwnerGranted: Boolean\n superUserGranted: Boolean\n ) on FIELD_DEFINITION\n`\n/**\n * The server calls this with the merged schema only. `modules` and `mode` are for tests, which\n * have no module walk of their own (`orderedModuleNames` is empty under jest) — see `open-fields.ts`.\n */\nexport const privilegeDirectiveResolver = (\n schema: GraphQLSchema,\n options: { modules?: DeclaringModule[]; mode?: EnforcementMode } = {}\n) => {\n const coverage = coverageOf(options.modules ?? modulesFromLoader())\n const mode = options.mode ?? enforcementMode()\n const unlisted: string[] = []\n\n const mapped = mapSchema(schema, {\n [MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName, typeName, schema) => {\n const privilegeDirective = getDirective(schema, fieldConfig, DIRECTIVE)?.[0]\n\n /* A root field with no directive is recorded as open, so \"open\" and \"no such field\" differ. */\n if (!privilegeDirective && isRootType(schema, typeName)) {\n const where = `${typeName}.${fieldName}`\n\n process['PRIVILEGE_OF_FIELD'][where] = null\n\n /*\n * ── The field that arrived without saying anything ───────────────────\n * Its module opted in (declared `openFields`) and names it in neither list. That is how\n * `deleteAppBinding` went in, and the whole point of §8 step 1 is that it cannot happen\n * quietly any more. A field of a module that has not opted in is left exactly as before.\n */\n if (judgeUndeclared(where, coverage) === 'unlisted') {\n if (mode === 'boot') {\n unlisted.push(where)\n } else {\n refuseUnlisted(fieldConfig, where)\n }\n }\n }\n\n if (privilegeDirective) {\n const { resolve = defaultFieldResolver, args } = fieldConfig\n\n if (!args) {\n throw new Error(`Unexpected Error. args should be defined in @privilege directive for field ${fieldName}.`)\n }\n\n const { domainOwnerGranted, superUserGranted, category, privilege } = privilegeDirective\n const where = `${typeName}.${fieldName}`\n\n /*\n * A declaration has to name something.\n *\n * `category` and `privilege` are one thing, not two: together they name a row in the\n * privilege table, and either alone names no row, so `User.hasPrivilege` is never\n * reached and the field falls back on ownership flags it did not set.\n */\n if (!category !== !privilege) {\n throw new Error(\n `@privilege on ${where} gives ${category ? 'a category with no privilege' : 'a privilege with no category'}. ` +\n `The pair names the privilege to look for; one half names none.`\n )\n }\n\n /*\n * `@privilege()` with nothing in it reads like a gate and guards nothing. Until\n * 2026-09-15 what it did depended on where the caller sat: `checkPermission` answered\n * false from a safe address and refused everyone, true from an unsafe one and let\n * everyone through. Both are false now, so the shape is merely useless rather than\n * dangerous — and a field that is open should say so by carrying no directive, not by\n * carrying an empty one.\n *\n * It is also the shape someone reaches for wanting \"being signed in is enough\". That\n * is a real thing to want and this directive does not express it; the answer settled\n * on 2026-09-15 is a category for the published side of a module, `<module>-published`.\n *\n * Throwing stops the boot, which is what §6-2 of docs/design/auth-rebuild.md settled\n * for a resolver that declares nothing. A decorator that declares nothing is that\n * same resolver wearing one.\n */\n if (!category && !privilege && !domainOwnerGranted && !superUserGranted) {\n throw new Error(\n `@privilege on ${where} declares nothing. ` +\n `Name (category, privilege) for a privilege gate, or domainOwnerGranted / superUserGranted for an ` +\n `ownership gate. A field open to everyone carries no directive at all.`\n )\n }\n\n /*\n * The axis has to be one this installation declared.\n *\n * Without this, `privilege: \"mutaiton\"` registered a pair nobody would ever be granted\n * — a gate shut forever, with no error to read. The list is not the framework's: see\n * `privilege-axis.ts` for why, and for the fact that it must be declared at module\n * import time rather than on a bootstrap hook.\n */\n if (privilege && !isDeclaredAxis(privilege)) {\n throw new Error(\n `@privilege on ${where} uses axis \"${privilege}\", which this installation does not declare. ` +\n `Declared axes: ${[...declaredAxes()].sort().join(', ')}. ` +\n `Fix the spelling, or call declareAxes(['${privilege}']) at the top level of the module's ` +\n `server/index.ts — a bootstrap hook runs after the schema is built and is too late.`\n )\n }\n\n if (category && privilege) {\n process['PRIVILEGES'][`${category} ${privilege}`] = [category, privilege]\n }\n\n /* After every refusal above, so a declaration that names nothing never lands here. */\n process['PRIVILEGE_OF_FIELD'][where] = {\n category: category || undefined,\n privilege: privilege || undefined,\n owner: !!domainOwnerGranted,\n super: !!superUserGranted\n } satisfies FieldGate\n\n // 필드의 기존 description 가져오기\n const existingDescription = fieldConfig.description || ''\n\n // 권한 정보를 포함한 새로운 description 생성\n const privilegeDescription =\n `\\n\\n🔒 Requires privilege: ${category}:${privilege}` +\n (domainOwnerGranted ? ', Domain ownership' : '') +\n (superUserGranted ? ', System ownership' : '')\n\n // 기존 description과 결합\n fieldConfig.description = `${existingDescription} ${privilegeDescription}`.trim()\n\n fieldConfig.resolve = async function (source, args, context, info) {\n const { domain, user, unsafeIP, prohibitedPrivileges } = context.state\n\n const judgement = await judgePermission(\n {\n category,\n privilege,\n owner: domainOwnerGranted,\n super: superUserGranted\n },\n user,\n domain,\n unsafeIP,\n prohibitedPrivileges\n )\n\n if (judgement.permitted === true) {\n return await resolve.call(this, source, args, context, info)\n }\n\n /*\n * 안 열렸으면 **거기서 끝이다.**\n *\n * 여기 옛 이름으로 한 번 더 보는 길이 있었다. 그것을 걷어냈다 — 호환 계층을 끼우지\n * 않는다. 이름을 옮기면 부여도 옮기는 것이고, 그 일은 관리자가 역할 화면에서 한다.\n *\n * 문장 만들기는 `privilege-rejection.ts` 가 소유한다.\n */\n throw privilegeRejection(context, category, privilege, judgement.because, info?.operation?.operation)\n }\n\n /*\n * ── A subscription is opened by `subscribe`, not `resolve` ────────────\n * Wrapping only `resolve` left a declared subscription connectable by anyone: the stream\n * opened and every event then failed. It leaked nothing, and it refused nothing either.\n * The same question is asked where the stream starts.\n */\n if (fieldConfig.subscribe) {\n const subscribe = fieldConfig.subscribe\n\n fieldConfig.subscribe = async function (source, args, context, info) {\n const { domain, user, unsafeIP, prohibitedPrivileges } = context.state\n\n const judgement = await judgePermission(\n { category, privilege, owner: domainOwnerGranted, super: superUserGranted },\n user,\n domain,\n unsafeIP,\n prohibitedPrivileges\n )\n\n if (judgement.permitted === false) {\n throw privilegeRejection(context, category, privilege, judgement.because, 'subscription')\n }\n\n return await subscribe.call(this, source, args, context, info)\n }\n }\n\n return fieldConfig\n }\n }\n })\n\n /*\n * Every unlisted field at once, rather than the first one. The person who meets this has usually\n * added several resolvers together, and fixing them one boot at a time is how a list gets\n * abandoned half done.\n */\n if (unlisted.length) {\n throw new Error(\n `${unlisted.length} field(s) declare no @privilege and are not listed as open by their module:\\n` +\n unlisted.map(where => ` ${where}`).join('\\n') +\n `\\n\\nFor each, either give it @privilege(category: …, privilege: …), or name it in the module's ` +\n `schema.openFields — under byDesign with the reason it is open, or under notYetDeclared if it ` +\n `is waiting to be closed. See docs/design/auth-rebuild.md §8.`\n )\n }\n\n return mapped\n}\n\n/**\n * Production's answer to an unlisted field: that field refuses, and says so in the log once.\n *\n * The server stays up. What cannot happen is the field answering — it arrived without saying who\n * may call it, and the answer to that is nobody until somebody says.\n */\nconst reported = new Set<string>()\n\nfunction refuseUnlisted(fieldConfig: any, where: string) {\n const refuse = () => {\n if (!reported.has(where)) {\n reported.add(where)\n logger.error(\n `${where} declares no @privilege and is not listed in its module's schema.openFields — refusing it.`\n )\n }\n\n return new GraphQLError(`${where} is not available: it declares no privilege.`, {\n extensions: { code: 'FORBIDDEN', refusal: { code: 'field-declares-no-privilege', params: { field: where } } }\n })\n }\n\n fieldConfig.resolve = async () => {\n throw refuse()\n }\n\n if (fieldConfig.subscribe) {\n fieldConfig.subscribe = async () => {\n throw refuse()\n }\n }\n}\n\nfunction isRootType(schema: GraphQLSchema, typeName: string): boolean {\n return [schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType()].some(\n root => root?.name === typeName\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"privilege-directive.js","sourceRoot":"","sources":["../../../server/service/privilege/privilege-directive.ts"],"names":[],"mappings":"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","sourcesContent":["import { defaultFieldResolver, GraphQLError, GraphQLSchema } from 'graphql'\nimport gql from 'graphql-tag'\n\nimport { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils'\nimport { logger } from '@things-factory/env'\n\nimport { judgePermission } from '../../utils/check-permission.js'\nimport { noteAuditAction } from '../audit/audit-recorder.js'\nimport { errorCodeOf, returnedCountOf, targetIdsOf } from '../audit/audit-target.js'\nimport { declaredAxes, isDeclaredAxis } from './privilege-axis.js'\nimport { privilegeRejection } from './privilege-rejection.js'\nimport {\n coverageOf,\n DeclaringModule,\n enforcementMode,\n EnforcementMode,\n judgeUndeclared,\n modulesFromLoader\n} from './open-field-rule.js'\n\nprocess['PRIVILEGES'] = {}\n\n/*\n * ── What each field asks for, by `Type.field` ─────────────────────────────────\n * `PRIVILEGES` above is the list of pairs this installation declares; it cannot answer \"what\n * does `Query.jobOrders` ask for\". Something that reaches a door by another entrance has to ask\n * exactly what the door asks — an AI tool is one (operato-twin ADR-0071 decision 2, ADR-0072\n * decision 5). Without this map each caller copied the pair by hand, and a copy drifts the day\n * the door's declaration changes.\n *\n * Three answers, and they are not the same:\n * - not in the map the schema has no such root field. A caller that named it has a typo,\n * and reading that as \"open\" is how a gate opens without an error.\n * - `null` the field exists and declares nothing. It is open.\n * - a gate what `checkPermission` is asked at that field.\n *\n * Every root field is recorded (Query / Mutation / Subscription) so the first two can be told\n * apart; a field of any other type is recorded only when it carries the directive.\n * Kept on `process` for the same reason `PRIVILEGES` is.\n */\nprocess['PRIVILEGE_OF_FIELD'] = {}\n\n/** What `checkPermission` is asked at a field — the same four things the directive passes it. */\nexport interface FieldGate {\n category?: string\n privilege?: string\n owner: boolean\n super: boolean\n}\n\n/**\n * What the field `Type.field` asks for. Valid once the schema has been built.\n *\n * `known: false` means the schema has no such root field — treat it as a mistake, never as open.\n */\nexport function privilegeOfField(typeName: string, fieldName: string): { known: false } | { known: true; gate: FieldGate | null } {\n const map = process['PRIVILEGE_OF_FIELD'] as Record<string, FieldGate | null>\n const where = `${typeName}.${fieldName}`\n return where in map ? { known: true, gate: map[where] } : { known: false }\n}\n\nconst DIRECTIVE = 'privilege'\n\n/**\n * ⚠ **다음에 목록 인자를 붙이는 사람에게.**\n *\n * 여기 `legacy` 인자가 있었다 — 권한 이름을 옮기는 동안 옛 이름도 받아 주는 별칭이었다.\n * **걷어냈다.** 이 저장소는 대개조 중이고 **호환 계층을 끼우지 않는다**: 이름을 바꾸면 바꾼\n * 것이고, 부여는 관리자가 다시 놓는다. 별칭은 「한동안」이 영원이 되는 길이었다.\n *\n * 곧 필요해지는 목록 인자는 **AND** 다(`docs/design/auth-rebuild.md` §1-(3)): 외부에서\n * 당겨오면서 남의 데이터를 앉히는 호출은 `execute` **와** 그 모듈의 `mutation` 을 **둘 다**\n * 요구해야 한다.\n *\n * 그것을 `privileges: [...]` 처럼 중립적으로 이름 지으면 다음 사람이 「이 중 하나」로 읽고,\n * 그렇게 쓰는 날 게이트가 **오류 없이** 열린다. `requiresAll: [...]` 처럼 **이름이 AND 라고\n * 말하게** 한다.\n */\nexport const privilegeDirectiveTypeDefs = gql`\n directive @privilege(\n category: String\n privilege: String\n domainOwnerGranted: Boolean\n superUserGranted: Boolean\n ) on FIELD_DEFINITION\n`\n/**\n * The server calls this with the merged schema only. `modules` and `mode` are for tests, which\n * have no module walk of their own (`orderedModuleNames` is empty under jest) — see `open-fields.ts`.\n */\nexport const privilegeDirectiveResolver = (\n schema: GraphQLSchema,\n options: { modules?: DeclaringModule[]; mode?: EnforcementMode } = {}\n) => {\n const coverage = coverageOf(options.modules ?? modulesFromLoader())\n const mode = options.mode ?? enforcementMode()\n const unlisted: string[] = []\n\n const mapped = mapSchema(schema, {\n [MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName, typeName, schema) => {\n const privilegeDirective = getDirective(schema, fieldConfig, DIRECTIVE)?.[0]\n\n /* A root field with no directive is recorded as open, so \"open\" and \"no such field\" differ. */\n if (!privilegeDirective && isRootType(schema, typeName)) {\n const where = `${typeName}.${fieldName}`\n\n process['PRIVILEGE_OF_FIELD'][where] = null\n\n /*\n * ── The field that arrived without saying anything ───────────────────\n * Its module opted in (declared `openFields`) and names it in neither list. That is how\n * `deleteAppBinding` went in, and the whole point of §8 step 1 is that it cannot happen\n * quietly any more. A field of a module that has not opted in is left exactly as before.\n */\n if (judgeUndeclared(where, coverage) === 'unlisted') {\n if (mode === 'boot') {\n unlisted.push(where)\n } else {\n refuseUnlisted(fieldConfig, where)\n }\n }\n }\n\n if (privilegeDirective) {\n const { resolve = defaultFieldResolver, args } = fieldConfig\n\n if (!args) {\n throw new Error(`Unexpected Error. args should be defined in @privilege directive for field ${fieldName}.`)\n }\n\n const { domainOwnerGranted, superUserGranted, category, privilege } = privilegeDirective\n const where = `${typeName}.${fieldName}`\n\n /*\n * The audit's name for this door (ADR-0141 §3.2), and what the field says it acts on (§3.3).\n * A door that asks ownership only is named by the ownership it asks.\n */\n const door =\n category && privilege\n ? `${category}:${privilege}`\n : [domainOwnerGranted && 'owner', superUserGranted && 'super'].filter(Boolean).join('+')\n const target = getDirective(schema, fieldConfig, 'auditTarget')?.[0] as { kind: string; arg: string } | undefined\n\n /*\n * A declaration has to name something.\n *\n * `category` and `privilege` are one thing, not two: together they name a row in the\n * privilege table, and either alone names no row, so `User.hasPrivilege` is never\n * reached and the field falls back on ownership flags it did not set.\n */\n if (!category !== !privilege) {\n throw new Error(\n `@privilege on ${where} gives ${category ? 'a category with no privilege' : 'a privilege with no category'}. ` +\n `The pair names the privilege to look for; one half names none.`\n )\n }\n\n /*\n * `@privilege()` with nothing in it reads like a gate and guards nothing. Until\n * 2026-09-15 what it did depended on where the caller sat: `checkPermission` answered\n * false from a safe address and refused everyone, true from an unsafe one and let\n * everyone through. Both are false now, so the shape is merely useless rather than\n * dangerous — and a field that is open should say so by carrying no directive, not by\n * carrying an empty one.\n *\n * It is also the shape someone reaches for wanting \"being signed in is enough\". That\n * is a real thing to want and this directive does not express it; the answer settled\n * on 2026-09-15 is a category for the published side of a module, `<module>-published`.\n *\n * Throwing stops the boot, which is what §6-2 of docs/design/auth-rebuild.md settled\n * for a resolver that declares nothing. A decorator that declares nothing is that\n * same resolver wearing one.\n */\n if (!category && !privilege && !domainOwnerGranted && !superUserGranted) {\n throw new Error(\n `@privilege on ${where} declares nothing. ` +\n `Name (category, privilege) for a privilege gate, or domainOwnerGranted / superUserGranted for an ` +\n `ownership gate. A field open to everyone carries no directive at all.`\n )\n }\n\n /*\n * The axis has to be one this installation declared.\n *\n * Without this, `privilege: \"mutaiton\"` registered a pair nobody would ever be granted\n * — a gate shut forever, with no error to read. The list is not the framework's: see\n * `privilege-axis.ts` for why, and for the fact that it must be declared at module\n * import time rather than on a bootstrap hook.\n */\n if (privilege && !isDeclaredAxis(privilege)) {\n throw new Error(\n `@privilege on ${where} uses axis \"${privilege}\", which this installation does not declare. ` +\n `Declared axes: ${[...declaredAxes()].sort().join(', ')}. ` +\n `Fix the spelling, or call declareAxes(['${privilege}']) at the top level of the module's ` +\n `server/index.ts — a bootstrap hook runs after the schema is built and is too late.`\n )\n }\n\n if (category && privilege) {\n process['PRIVILEGES'][`${category} ${privilege}`] = [category, privilege]\n }\n\n /* After every refusal above, so a declaration that names nothing never lands here. */\n process['PRIVILEGE_OF_FIELD'][where] = {\n category: category || undefined,\n privilege: privilege || undefined,\n owner: !!domainOwnerGranted,\n super: !!superUserGranted\n } satisfies FieldGate\n\n // 필드의 기존 description 가져오기\n const existingDescription = fieldConfig.description || ''\n\n // 권한 정보를 포함한 새로운 description 생성\n const privilegeDescription =\n `\\n\\n🔒 Requires privilege: ${category}:${privilege}` +\n (domainOwnerGranted ? ', Domain ownership' : '') +\n (superUserGranted ? ', System ownership' : '')\n\n // 기존 description과 결합\n fieldConfig.description = `${existingDescription} ${privilegeDescription}`.trim()\n\n fieldConfig.resolve = async function (source, args, context, info) {\n const { domain, user, unsafeIP, prohibitedPrivileges } = context.state\n\n const judgement = await judgePermission(\n {\n category,\n privilege,\n owner: domainOwnerGranted,\n super: superUserGranted\n },\n user,\n domain,\n unsafeIP,\n prohibitedPrivileges\n )\n\n const targetKind = target?.kind\n const targetIds = target ? targetIdsOf(args, target.arg) : undefined\n const targetRole = target ? ('requested' as const) : undefined\n\n if (judgement.permitted === true) {\n noteAuditAction({ kind: 'door.check', door, decision: 'granted', basis: judgement.by, targetKind, targetIds, targetRole })\n\n /* Passing the door is not doing the work - what the work came to is its own row (§3.2). */\n try {\n const result = await resolve.call(this, source, args, context, info)\n noteAuditAction({ kind: 'door.result', door, outcome: 'ok', returned: returnedCountOf(result), targetKind, targetIds, targetRole })\n return result\n } catch (error) {\n noteAuditAction({ kind: 'door.result', door, outcome: 'error', code: errorCodeOf(error), targetKind, targetIds, targetRole })\n throw error\n }\n }\n\n noteAuditAction({ kind: 'door.check', door, decision: 'denied', basis: judgement.because, targetKind, targetIds, targetRole })\n\n /*\n * 안 열렸으면 **거기서 끝이다.**\n *\n * 여기 옛 이름으로 한 번 더 보는 길이 있었다. 그것을 걷어냈다 — 호환 계층을 끼우지\n * 않는다. 이름을 옮기면 부여도 옮기는 것이고, 그 일은 관리자가 역할 화면에서 한다.\n *\n * 문장 만들기는 `privilege-rejection.ts` 가 소유한다.\n */\n throw privilegeRejection(context, category, privilege, judgement.because, info?.operation?.operation)\n }\n\n /*\n * ── A subscription is opened by `subscribe`, not `resolve` ────────────\n * Wrapping only `resolve` left a declared subscription connectable by anyone: the stream\n * opened and every event then failed. It leaked nothing, and it refused nothing either.\n * The same question is asked where the stream starts.\n */\n if (fieldConfig.subscribe) {\n const subscribe = fieldConfig.subscribe\n\n fieldConfig.subscribe = async function (source, args, context, info) {\n const { domain, user, unsafeIP, prohibitedPrivileges } = context.state\n\n const judgement = await judgePermission(\n { category, privilege, owner: domainOwnerGranted, super: superUserGranted },\n user,\n domain,\n unsafeIP,\n prohibitedPrivileges\n )\n\n if (judgement.permitted === false) {\n throw privilegeRejection(context, category, privilege, judgement.because, 'subscription')\n }\n\n return await subscribe.call(this, source, args, context, info)\n }\n }\n\n return fieldConfig\n }\n }\n })\n\n /*\n * Every unlisted field at once, rather than the first one. The person who meets this has usually\n * added several resolvers together, and fixing them one boot at a time is how a list gets\n * abandoned half done.\n */\n if (unlisted.length) {\n throw new Error(\n `${unlisted.length} field(s) declare no @privilege and are not listed as open by their module:\\n` +\n unlisted.map(where => ` ${where}`).join('\\n') +\n `\\n\\nFor each, either give it @privilege(category: …, privilege: …), or name it in the module's ` +\n `schema.openFields — under byDesign with the reason it is open, or under notYetDeclared if it ` +\n `is waiting to be closed. See docs/design/auth-rebuild.md §8.`\n )\n }\n\n return mapped\n}\n\n/**\n * Production's answer to an unlisted field: that field refuses, and says so in the log once.\n *\n * The server stays up. What cannot happen is the field answering — it arrived without saying who\n * may call it, and the answer to that is nobody until somebody says.\n */\nconst reported = new Set<string>()\n\nfunction refuseUnlisted(fieldConfig: any, where: string) {\n const refuse = () => {\n if (!reported.has(where)) {\n reported.add(where)\n logger.error(\n `${where} declares no @privilege and is not listed in its module's schema.openFields — refusing it.`\n )\n }\n\n return new GraphQLError(`${where} is not available: it declares no privilege.`, {\n extensions: { code: 'FORBIDDEN', refusal: { code: 'field-declares-no-privilege', params: { field: where } } }\n })\n }\n\n fieldConfig.resolve = async () => {\n throw refuse()\n }\n\n if (fieldConfig.subscribe) {\n fieldConfig.subscribe = async () => {\n throw refuse()\n }\n }\n}\n\nfunction isRootType(schema: GraphQLSchema, typeName: string): boolean {\n return [schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType()].some(\n root => root?.name === typeName\n )\n}\n"]}
|
|
@@ -9,6 +9,7 @@ const refuse_role_name_js_1 = require("../../utils/refuse-role-name.js");
|
|
|
9
9
|
const privilege_js_1 = require("../privilege/privilege.js");
|
|
10
10
|
const user_js_1 = require("../user/user.js");
|
|
11
11
|
const role_js_1 = require("./role.js");
|
|
12
|
+
const audit_recorder_js_1 = require("../audit/audit-recorder.js");
|
|
12
13
|
const role_types_js_1 = require("./role-types.js");
|
|
13
14
|
/*
|
|
14
15
|
* ── Why this class passes through `SpeakRefusals` ───────────────────────────
|
|
@@ -156,6 +157,8 @@ let RoleMutation = class RoleMutation {
|
|
|
156
157
|
role.privileges = [];
|
|
157
158
|
role = await tx.getRepository(role_js_1.Role).save(role);
|
|
158
159
|
await tx.getRepository(role_js_1.Role).delete(role.id);
|
|
160
|
+
/* `delete()` passes no subscriber - the deletion records itself, in the same transaction (ADR-0141 §4-1). */
|
|
161
|
+
await (0, audit_recorder_js_1.recordAuditAction)({ kind: 'role.delete', targetKind: 'Role', targetIds: [role.id], targetRole: 'handled' }, { manager: tx });
|
|
159
162
|
return true;
|
|
160
163
|
}
|
|
161
164
|
catch (e) {
|
|
@@ -179,7 +182,11 @@ let RoleMutation = class RoleMutation {
|
|
|
179
182
|
});
|
|
180
183
|
const deletable = roles.filter(role => !role.users?.length).map(role => role.id);
|
|
181
184
|
if (deletable.length) {
|
|
182
|
-
|
|
185
|
+
/* The deletion and its record stand or fall together (ADR-0141 §4-1). */
|
|
186
|
+
await (0, shell_1.getRepository)(role_js_1.Role).manager.transaction(async (manager) => {
|
|
187
|
+
await manager.getRepository(role_js_1.Role).delete(deletable);
|
|
188
|
+
await (0, audit_recorder_js_1.recordAuditAction)({ kind: 'role.delete', targetKind: 'Role', targetIds: deletable, targetRole: 'handled' }, { manager });
|
|
189
|
+
});
|
|
183
190
|
}
|
|
184
191
|
return true;
|
|
185
192
|
}
|
|
@@ -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 { 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"]}
|
|
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"]}
|
|
@@ -2,4 +2,4 @@ import { RoleTemplateGapResolver } from './role-template-gap-resolver.js';
|
|
|
2
2
|
import { RoleTemplateMutation } from './role-template-mutation.js';
|
|
3
3
|
import { RoleTemplateQuery } from './role-template-query.js';
|
|
4
4
|
export declare const entities: any[];
|
|
5
|
-
export declare const resolvers: (typeof
|
|
5
|
+
export declare const resolvers: (typeof RoleTemplateQuery | typeof RoleTemplateMutation | typeof RoleTemplateGapResolver)[];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { RoleTemplateGrant } from './role-template.js';
|
|
2
|
+
/**
|
|
3
|
+
* Role tasks — a piece of work named by the module that owns it, and the privileges it takes (ADR-0139 decision 11).
|
|
4
|
+
*
|
|
5
|
+
* ── Why a task and not a template ────────────────────────────────────────────
|
|
6
|
+
* A template is a seat in one application: who does what in an office or a plant. A module does not know the seats
|
|
7
|
+
* of the applications that install it, but it does know its own work — accounting knows that writing an expenditure
|
|
8
|
+
* request takes one's own requests and picking a counterparty and an account subject. So the module names the work
|
|
9
|
+
* and the application builds its seats from the work (`RoleTemplate.tasks`). An application names work of its own
|
|
10
|
+
* in the same form, under its own prefix.
|
|
11
|
+
*
|
|
12
|
+
* ── What a task is not ───────────────────────────────────────────────────────
|
|
13
|
+
* - Not a narrower door. A task holds whole privileges, and each privilege opens what its doors open. A task called
|
|
14
|
+
* "read the books" that holds `accounting:query` opens everything `accounting:query` opens; the name and the
|
|
15
|
+
* description say so rather than hide it.
|
|
16
|
+
* - Not stored. Nothing on a role records which tasks it was built from — the role screen compares a role's
|
|
17
|
+
* privileges with the tasks, computed, the same way a template's gap is (`templateGapOf`).
|
|
18
|
+
* - Not nested. A task lists privileges only; seats are where tasks are put together.
|
|
19
|
+
* - Not declared twice. One piece of work has one task. Two tasks may share a privilege — picking a counterparty is
|
|
20
|
+
* part of more than one piece of work — and that is not a duplicate.
|
|
21
|
+
*
|
|
22
|
+
* Its words live in the declaring module's translations, `task.<id>.name` and `task.<id>.description`, so a module
|
|
23
|
+
* that is not installed takes its words away with it. The screen draws the id when there are none.
|
|
24
|
+
*/
|
|
25
|
+
export type RoleTask = {
|
|
26
|
+
/** `<module>.<work>` — the declaring module's name first: `accounting.expense-authoring`. */
|
|
27
|
+
id: string;
|
|
28
|
+
/** Privileges by category, axes listed one by one — the shape a template's grants have. */
|
|
29
|
+
grants: RoleTemplateGrant[];
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Declares a task. Call it from the module's server entry, beside its `@privilege` doors.
|
|
33
|
+
*
|
|
34
|
+
* Privileges are not checked here, for the reason templates are not: modules load before the schema is built and
|
|
35
|
+
* `process['PRIVILEGES']` is filled. Boot checks them (`reportRoleDefinitionErrors`).
|
|
36
|
+
*/
|
|
37
|
+
export declare function registerRoleTask(task: RoleTask): void;
|
|
38
|
+
export declare function roleTasks(): RoleTask[];
|
|
39
|
+
export declare function roleTaskById(id: string): RoleTask | undefined;
|
|
40
|
+
/** The module a task is declared by — the part of its id before the first dot. */
|
|
41
|
+
export declare function roleTaskModule(task: RoleTask): string;
|
|
42
|
+
/**
|
|
43
|
+
* Several lists of grants as one: one entry per category, each axis once, in the order first written.
|
|
44
|
+
* A privilege held through two tasks, or through a task and a direct grant, is held once.
|
|
45
|
+
*/
|
|
46
|
+
export declare function mergeGrants(lists: RoleTemplateGrant[][]): RoleTemplateGrant[];
|
|
47
|
+
/** `category axis` — the key `process['PRIVILEGES']` and a role's held set use. */
|
|
48
|
+
export declare function privilegeKey(category: string, axis: string): string;
|
|
49
|
+
/** A task's privileges this installation does not declare, as `category:axis` — a definition error of the task. */
|
|
50
|
+
export declare function undeclaredTaskGrants(task: RoleTask, declared?: Record<string, unknown>): string[];
|
|
51
|
+
export type RoleTaskStatus = 'all' | 'some' | 'none' | 'broken';
|
|
52
|
+
export type RoleTaskCoverage = {
|
|
53
|
+
taskId: string;
|
|
54
|
+
module: string;
|
|
55
|
+
status: RoleTaskStatus;
|
|
56
|
+
held: RoleTemplateGrant[];
|
|
57
|
+
missing: RoleTemplateGrant[];
|
|
58
|
+
/** Privileges the task names that this installation does not declare. Any of them makes the task `broken`. */
|
|
59
|
+
undeclared: RoleTemplateGrant[];
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* **A role's privileges laid against every task** — what the role screen groups by.
|
|
63
|
+
*
|
|
64
|
+
* It says which of a task's privileges the role holds, nothing more. `all` is not "this person can do the work":
|
|
65
|
+
* another role of theirs, the data's own scope, an approval line or a field's door decide that too. `some` is not a
|
|
66
|
+
* fault: a privilege shared by several tasks makes each of them `some`. `none` is the common case.
|
|
67
|
+
*
|
|
68
|
+
* `unclassified` is what the role holds, declared here, that no task names. A privilege the role holds that nothing
|
|
69
|
+
* declares any more is not here — the screen already strikes it through (`isDeprecatedPrivilege`).
|
|
70
|
+
*/
|
|
71
|
+
export declare function taskCoverageOf(held: ReadonlySet<string>, tasks?: RoleTask[], declared?: Record<string, unknown>): {
|
|
72
|
+
tasks: RoleTaskCoverage[];
|
|
73
|
+
unclassified: RoleTemplateGrant[];
|
|
74
|
+
};
|