@things-factory/auth-base 10.1.28 → 10.1.31
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/service/index.d.ts +1 -0
- package/dist-server/service/index.js +4 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/privilege/privilege-directive.d.ts +18 -0
- package/dist-server/service/privilege/privilege-directive.js +44 -0
- package/dist-server/service/privilege/privilege-directive.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/tests/privilege-directive.test.ts +35 -1
|
@@ -5,6 +5,7 @@ export * from './appliance/appliance.js';
|
|
|
5
5
|
export * from './privilege/privilege.js';
|
|
6
6
|
export * from './role/role.js';
|
|
7
7
|
export * from './privilege/privilege-axis.js';
|
|
8
|
+
export { privilegeOfField, type FieldGate } from './privilege/privilege-directive.js';
|
|
8
9
|
export * from './role-template/role-template.js';
|
|
9
10
|
export * from './role-template/role-template-types.js';
|
|
10
11
|
export * from './user/user.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.schema = exports.entities = void 0;
|
|
3
|
+
exports.schema = exports.entities = exports.privilegeOfField = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
/* IMPORT ENTITIES AND RESOLVERS */
|
|
6
6
|
const index_js_1 = require("./users-auth-providers/index.js");
|
|
@@ -31,6 +31,9 @@ tslib_1.__exportStar(require("./appliance/appliance.js"), exports);
|
|
|
31
31
|
tslib_1.__exportStar(require("./privilege/privilege.js"), exports);
|
|
32
32
|
tslib_1.__exportStar(require("./role/role.js"), exports);
|
|
33
33
|
tslib_1.__exportStar(require("./privilege/privilege-axis.js"), exports);
|
|
34
|
+
/* What a field asks for, by name — for another entrance to the same door (operato-twin ADR-0072 decision 5). */
|
|
35
|
+
var privilege_directive_js_2 = require("./privilege/privilege-directive.js");
|
|
36
|
+
Object.defineProperty(exports, "privilegeOfField", { enumerable: true, get: function () { return privilege_directive_js_2.privilegeOfField; } });
|
|
34
37
|
tslib_1.__exportStar(require("./role-template/role-template.js"), exports);
|
|
35
38
|
tslib_1.__exportStar(require("./role-template/role-template-types.js"), exports);
|
|
36
39
|
tslib_1.__exportStar(require("./user/user.js"), exports);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,mCAAmC;AACnC,8DAGwC;AACxC,uDAA+G;AAC/G,qDAAwE;AACxE,mDAAqG;AACrG,qDAA2G;AAC3G,0DAAkF;AAClF,sDAA2G;AAC3G,oDAAuG;AACvG,uDAA8G;AAC9G,kDAA+F;AAC/F,2DAAiF;AACjF,oDAAqG;AACrG,+EAA2G;AAC3G,+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,2EAAgD;AAChD,iFAAsD;AACtD,yDAA8B;AAC9B,+DAAoC;AACpC,yEAA8C;AAC9C,qEAA0C;AAC1C,uEAA4C;AAC5C,iFAAsD;AACtD,qFAA0D;AAC1D,2EAAgD;AAChD,uFAA4D;AAC5D,uEAA4C;AAC5C,yEAA8C;AAE9C,kBAAkB;AAClB,6EAAkD;AAClD,yEAA8C;AAC9C,6EAAkD;AAClD,uFAA4D;AAC5D,2EAAgD;AAChD,qEAA0C;AAC1C,yEAA8C;AAC9C,+DAAoC;AACpC,+DAAoC;AACpC,6EAAkD;AAErC,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,mBAAoB;IACvB,GAAG,oBAAyB;IAC5B,GAAG,oBAAkB;IACrB,GAAG,oBAAmB;CACvB,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,QAAQ,EAAE;QACR,0BAA0B,EAA1B,mDAA0B;KAC3B;IAED,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,oBAAoB;QACvB,GAAG,qBAAkB;QACrB,GAAG,qBAAoB;KACxB;IACD,UAAU,EAAE;QACV,SAAS,EAAE,mDAA0B;KACtC;CACF,CAAA","sourcesContent":["/* IMPORT ENTITIES AND RESOLVERS */\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 { entities as LoginHistoryEntities, resolvers as LoginHistoryResolver } from './login-history/index.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 { 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'\nexport * from './role-template/role-template.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 './login-history/login-history.js'\nexport * from './web-auth-credential/web-auth-credential.js'\nexport * from './domain-link/domain-link.js'\nexport * from './domain-owner/domain-owner.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'\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 ...LoginHistoryEntities,\n ...WebAuthCredentialEntities,\n ...DomainLinkEntities,\n ...DomainOwnerEntities\n]\n\nexport const schema = {\n typeDefs: {\n privilegeDirectiveTypeDefs\n },\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 ...LoginHistoryResolver,\n ...DomainLinkResolver,\n ...DomainOwnerResolvers\n ],\n directives: {\n privilege: privilegeDirectiveResolver\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,mCAAmC;AACnC,8DAGwC;AACxC,uDAA+G;AAC/G,qDAAwE;AACxE,mDAAqG;AACrG,qDAA2G;AAC3G,0DAAkF;AAClF,sDAA2G;AAC3G,oDAAuG;AACvG,uDAA8G;AAC9G,kDAA+F;AAC/F,2DAAiF;AACjF,oDAAqG;AACrG,+EAA2G;AAC3G,+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,2EAAgD;AAChD,iFAAsD;AACtD,yDAA8B;AAC9B,+DAAoC;AACpC,yEAA8C;AAC9C,qEAA0C;AAC1C,uEAA4C;AAC5C,iFAAsD;AACtD,qFAA0D;AAC1D,2EAAgD;AAChD,uFAA4D;AAC5D,uEAA4C;AAC5C,yEAA8C;AAE9C,kBAAkB;AAClB,6EAAkD;AAClD,yEAA8C;AAC9C,6EAAkD;AAClD,uFAA4D;AAC5D,2EAAgD;AAChD,qEAA0C;AAC1C,yEAA8C;AAC9C,+DAAoC;AACpC,+DAAoC;AACpC,6EAAkD;AAErC,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,mBAAoB;IACvB,GAAG,oBAAyB;IAC5B,GAAG,oBAAkB;IACrB,GAAG,oBAAmB;CACvB,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,QAAQ,EAAE;QACR,0BAA0B,EAA1B,mDAA0B;KAC3B;IAED,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,oBAAoB;QACvB,GAAG,qBAAkB;QACrB,GAAG,qBAAoB;KACxB;IACD,UAAU,EAAE;QACV,SAAS,EAAE,mDAA0B;KACtC;CACF,CAAA","sourcesContent":["/* IMPORT ENTITIES AND RESOLVERS */\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 { entities as LoginHistoryEntities, resolvers as LoginHistoryResolver } from './login-history/index.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 { 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'\nexport * from './role-template/role-template.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 './login-history/login-history.js'\nexport * from './web-auth-credential/web-auth-credential.js'\nexport * from './domain-link/domain-link.js'\nexport * from './domain-owner/domain-owner.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'\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 ...LoginHistoryEntities,\n ...WebAuthCredentialEntities,\n ...DomainLinkEntities,\n ...DomainOwnerEntities\n]\n\nexport const schema = {\n typeDefs: {\n privilegeDirectiveTypeDefs\n },\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 ...LoginHistoryResolver,\n ...DomainLinkResolver,\n ...DomainOwnerResolvers\n ],\n directives: {\n privilege: privilegeDirectiveResolver\n }\n}\n"]}
|
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import { GraphQLSchema } from 'graphql';
|
|
2
|
+
/** What `checkPermission` is asked at a field — the same four things the directive passes it. */
|
|
3
|
+
export interface FieldGate {
|
|
4
|
+
category?: string;
|
|
5
|
+
privilege?: string;
|
|
6
|
+
owner: boolean;
|
|
7
|
+
super: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* What the field `Type.field` asks for. Valid once the schema has been built.
|
|
11
|
+
*
|
|
12
|
+
* `known: false` means the schema has no such root field — treat it as a mistake, never as open.
|
|
13
|
+
*/
|
|
14
|
+
export declare function privilegeOfField(typeName: string, fieldName: string): {
|
|
15
|
+
known: false;
|
|
16
|
+
} | {
|
|
17
|
+
known: true;
|
|
18
|
+
gate: FieldGate | null;
|
|
19
|
+
};
|
|
2
20
|
/**
|
|
3
21
|
* ⚠ **다음에 목록 인자를 붙이는 사람에게.**
|
|
4
22
|
*
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.privilegeDirectiveResolver = exports.privilegeDirectiveTypeDefs = void 0;
|
|
4
|
+
exports.privilegeOfField = privilegeOfField;
|
|
4
5
|
const tslib_1 = require("tslib");
|
|
5
6
|
const graphql_1 = require("graphql");
|
|
6
7
|
const graphql_tag_1 = tslib_1.__importDefault(require("graphql-tag"));
|
|
@@ -9,6 +10,35 @@ const check_permission_js_1 = require("../../utils/check-permission.js");
|
|
|
9
10
|
const privilege_axis_js_1 = require("./privilege-axis.js");
|
|
10
11
|
const privilege_rejection_js_1 = require("./privilege-rejection.js");
|
|
11
12
|
process['PRIVILEGES'] = {};
|
|
13
|
+
/*
|
|
14
|
+
* ── What each field asks for, by `Type.field` ─────────────────────────────────
|
|
15
|
+
* `PRIVILEGES` above is the list of pairs this installation declares; it cannot answer "what
|
|
16
|
+
* does `Query.jobOrders` ask for". Something that reaches a door by another entrance has to ask
|
|
17
|
+
* exactly what the door asks — an AI tool is one (operato-twin ADR-0071 decision 2, ADR-0072
|
|
18
|
+
* decision 5). Without this map each caller copied the pair by hand, and a copy drifts the day
|
|
19
|
+
* the door's declaration changes.
|
|
20
|
+
*
|
|
21
|
+
* Three answers, and they are not the same:
|
|
22
|
+
* - not in the map the schema has no such root field. A caller that named it has a typo,
|
|
23
|
+
* and reading that as "open" is how a gate opens without an error.
|
|
24
|
+
* - `null` the field exists and declares nothing. It is open.
|
|
25
|
+
* - a gate what `checkPermission` is asked at that field.
|
|
26
|
+
*
|
|
27
|
+
* Every root field is recorded (Query / Mutation / Subscription) so the first two can be told
|
|
28
|
+
* apart; a field of any other type is recorded only when it carries the directive.
|
|
29
|
+
* Kept on `process` for the same reason `PRIVILEGES` is.
|
|
30
|
+
*/
|
|
31
|
+
process['PRIVILEGE_OF_FIELD'] = {};
|
|
32
|
+
/**
|
|
33
|
+
* What the field `Type.field` asks for. Valid once the schema has been built.
|
|
34
|
+
*
|
|
35
|
+
* `known: false` means the schema has no such root field — treat it as a mistake, never as open.
|
|
36
|
+
*/
|
|
37
|
+
function privilegeOfField(typeName, fieldName) {
|
|
38
|
+
const map = process['PRIVILEGE_OF_FIELD'];
|
|
39
|
+
const where = `${typeName}.${fieldName}`;
|
|
40
|
+
return where in map ? { known: true, gate: map[where] } : { known: false };
|
|
41
|
+
}
|
|
12
42
|
const DIRECTIVE = 'privilege';
|
|
13
43
|
/**
|
|
14
44
|
* ⚠ **다음에 목록 인자를 붙이는 사람에게.**
|
|
@@ -36,6 +66,10 @@ exports.privilegeDirectiveTypeDefs = (0, graphql_tag_1.default) `
|
|
|
36
66
|
const privilegeDirectiveResolver = (schema) => (0, utils_1.mapSchema)(schema, {
|
|
37
67
|
[utils_1.MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName, typeName, schema) => {
|
|
38
68
|
const privilegeDirective = (0, utils_1.getDirective)(schema, fieldConfig, DIRECTIVE)?.[0];
|
|
69
|
+
/* A root field with no directive is recorded as open, so "open" and "no such field" differ. */
|
|
70
|
+
if (!privilegeDirective && isRootType(schema, typeName)) {
|
|
71
|
+
process['PRIVILEGE_OF_FIELD'][`${typeName}.${fieldName}`] = null;
|
|
72
|
+
}
|
|
39
73
|
if (privilegeDirective) {
|
|
40
74
|
const { resolve = graphql_1.defaultFieldResolver, args } = fieldConfig;
|
|
41
75
|
if (!args) {
|
|
@@ -92,6 +126,13 @@ const privilegeDirectiveResolver = (schema) => (0, utils_1.mapSchema)(schema, {
|
|
|
92
126
|
if (category && privilege) {
|
|
93
127
|
process['PRIVILEGES'][`${category} ${privilege}`] = [category, privilege];
|
|
94
128
|
}
|
|
129
|
+
/* After every refusal above, so a declaration that names nothing never lands here. */
|
|
130
|
+
process['PRIVILEGE_OF_FIELD'][where] = {
|
|
131
|
+
category: category || undefined,
|
|
132
|
+
privilege: privilege || undefined,
|
|
133
|
+
owner: !!domainOwnerGranted,
|
|
134
|
+
super: !!superUserGranted
|
|
135
|
+
};
|
|
95
136
|
// 필드의 기존 description 가져오기
|
|
96
137
|
const existingDescription = fieldConfig.description || '';
|
|
97
138
|
// 권한 정보를 포함한 새로운 description 생성
|
|
@@ -126,4 +167,7 @@ const privilegeDirectiveResolver = (schema) => (0, utils_1.mapSchema)(schema, {
|
|
|
126
167
|
}
|
|
127
168
|
});
|
|
128
169
|
exports.privilegeDirectiveResolver = privilegeDirectiveResolver;
|
|
170
|
+
function isRootType(schema, typeName) {
|
|
171
|
+
return [schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType()].some(root => root?.name === typeName);
|
|
172
|
+
}
|
|
129
173
|
//# sourceMappingURL=privilege-directive.js.map
|
|
@@ -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, 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 { checkPermission } from '../../utils/check-permission.js'\nimport { declaredAxes, isDeclaredAxis } from './privilege-axis.js'\nimport { privilegeRejection } from './privilege-rejection.js'\n\nprocess['PRIVILEGES'] = {}\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`\nexport const privilegeDirectiveResolver = (schema: GraphQLSchema) =>\n mapSchema(schema, {\n [MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName, typeName, schema) => {\n const privilegeDirective = getDirective(schema, fieldConfig, DIRECTIVE)?.[0]\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 // 필드의 기존 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 permitted = await checkPermission(\n {\n category,\n privilege,\n owner: domainOwnerGranted,\n super: superUserGranted\n },\n user,\n domain,\n unsafeIP,\n prohibitedPrivileges\n )\n\n if (permitted) {\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)\n }\n\n return fieldConfig\n }\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, 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 { checkPermission } from '../../utils/check-permission.js'\nimport { declaredAxes, isDeclaredAxis } from './privilege-axis.js'\nimport { privilegeRejection } from './privilege-rejection.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`\nexport const privilegeDirectiveResolver = (schema: GraphQLSchema) =>\n 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 process['PRIVILEGE_OF_FIELD'][`${typeName}.${fieldName}`] = null\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 permitted = await checkPermission(\n {\n category,\n privilege,\n owner: domainOwnerGranted,\n super: superUserGranted\n },\n user,\n domain,\n unsafeIP,\n prohibitedPrivileges\n )\n\n if (permitted) {\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)\n }\n\n return fieldConfig\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"]}
|