@things-factory/auth-base 10.1.7 → 10.1.17
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/CHANGELOG.md +31 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/index.js +8 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/router/auth-private-process-router.js +20 -1
- package/dist-server/router/auth-private-process-router.js.map +1 -1
- package/dist-server/service/app-binding/app-binding-query.d.ts +34 -0
- package/dist-server/service/app-binding/app-binding-query.js +47 -32
- package/dist-server/service/app-binding/app-binding-query.js.map +1 -1
- package/dist-server/service/index.d.ts +2 -1
- package/dist-server/service/index.js +1 -0
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/privilege/privilege-axis.d.ts +72 -0
- package/dist-server/service/privilege/privilege-axis.js +188 -0
- package/dist-server/service/privilege/privilege-axis.js.map +1 -0
- package/dist-server/service/privilege/privilege-directive.js +48 -0
- package/dist-server/service/privilege/privilege-directive.js.map +1 -1
- package/dist-server/service/role-template/index.d.ts +1 -1
- package/dist-server/service/role-template/role-template.js +24 -6
- 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.js +11 -2
- package/dist-server/utils/check-permission.js.map +1 -1
- package/package.json +4 -4
- package/tests/app-binding-db.test.ts +162 -0
- package/tests/domain-inheritance-sentinel.test.ts +93 -0
- package/tests/permission-gate.test.ts +16 -12
- package/tests/privilege-axis.test.ts +157 -0
- package/tests/privilege-directive.test.ts +79 -0
- package/tests/role-template.test.ts +36 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-binding-query.js","sourceRoot":"","sources":["../../../server/service/app-binding/app-binding-query.ts"],"names":[],"mappings":";;;;AAAA,+CAAmF;AAGnF,iDAA4E;AAE5E,yEAA8E;AAC9E,kEAA2D;AAC3D,6CAAsC;AAEtC,qDAA6C;AAC7C,iEAAuD;
|
|
1
|
+
{"version":3,"file":"app-binding-query.js","sourceRoot":"","sources":["../../../server/service/app-binding/app-binding-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Args, Ctx, FieldResolver, Query, Resolver, Root } from 'type-graphql'\nimport { SelectQueryBuilder } from 'typeorm'\n\nimport { buildQuery, getRepository, ListParam } from '@things-factory/shell'\n\nimport { buildDomainUsersQueryBuilder } from '../../utils/get-domain-users.js'\nimport { Application } from '../application/application.js'\nimport { User } from '../user/user.js'\nimport { UserList } from '../user/user-types.js'\nimport { AppBinding } from './app-binding.js'\nimport { AppBindingList } from './app-binding-types.js'\n\n/**\n * A binding is a `User` row wearing this type — the same value `oauth2-server.ts` writes when\n * an application is granted access. It is spelled once here so the list and the single lookup\n * cannot drift apart.\n */\nconst APPLICATION_USER_TYPE = 'application'\n\n@Resolver(AppBinding)\nexport class AppBindingQuery {\n /**\n * One binding.\n *\n * The domain was read out of the context and then not used — `findOneBy({ id, userType })`\n * searched every tenant, and a comment said so: \"TODO should check domain is available\".\n * An id from anywhere returned that application user's name, email and reference. The\n * refresh token was never part of it, since `AppBinding.refreshToken` carries its own\n * `@privilege(category: \"security\", …)`, but the rest of the row is still another tenant's.\n *\n * A binding belongs to the domain that made it, so the lookup asks for both. An id from\n * another domain now answers nothing, which is the same thing it answers for an id that\n * does not exist — deliberately: telling the two apart would confirm that some id is real\n * somewhere else.\n */\n @Query(returns => AppBinding)\n async appBinding(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<User> {\n const { domain } = context.state\n\n return await buildDomainUsersQueryBuilder(domain.id, 'USER')\n .andWhere('USER.id = :id', { id })\n .andWhere('USER.userType = :userType', { userType: APPLICATION_USER_TYPE })\n .getOne()\n }\n\n /**\n * The bindings of this domain.\n *\n * ── Paging used to happen before the filtering ───────────────────────────────\n * `buildQuery` applied the caller's pagination to the query, and the \"is this an\n * application user\" test ran in JavaScript on whatever came back:\n *\n * buildQuery(qb, params, …) take 20 users of any type\n * items = items.filter(user => user.userType == …) keep the ones that qualify\n * return { items, total: items.length } report that many in total\n *\n * A page of twenty therefore arrived with however many of those twenty happened to be\n * applications — three, or none — and the total reported the survivors of that one page.\n * The grid's pager believed it, so with more bindings than fit in a page the later ones\n * were unreachable: the pager never offered a page two that the total said did not exist.\n *\n * The condition belongs in the query, where the database can page what is already\n * filtered, and `getManyAndCount` then counts every matching row rather than the visible\n * ones.\n */\n @Query(returns => AppBindingList)\n async appBindings(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<UserList> {\n const { domain } = context.state\n\n const alias: string = 'USER'\n const qb: SelectQueryBuilder<User> = buildDomainUsersQueryBuilder(domain.id, alias)\n\n qb.andWhere(`${alias}.userType = :userType`, { userType: APPLICATION_USER_TYPE })\n\n buildQuery(qb, params, null, { domainRef: false })\n\n const [items, total] = await qb.getManyAndCount()\n\n return { items, total }\n }\n\n @FieldResolver(type => Application)\n async application(@Root() appBinding: AppBinding): Promise<Application> {\n return await getRepository(Application).findOneBy({ id: appBinding.reference })\n }\n\n @FieldResolver(type => String)\n async scope(@Root() appBinding: AppBinding): Promise<string> {\n const u = await getRepository(User).findOne({ where: { reference: appBinding.reference }, relations: ['roles'] })\n return u.roles.map(role => role.name).join(',')\n }\n\n @FieldResolver(type => String)\n async refreshToken(@Root() appBinding: AppBinding): Promise<string> {\n return appBinding.password\n }\n\n @FieldResolver(type => User)\n async updater(@Root() appBinding: AppBinding): Promise<User> {\n return await getRepository(User).findOneBy({ id: appBinding.updaterId })\n }\n\n @FieldResolver(type => User)\n async creator(@Root() appBinding: AppBinding): Promise<User> {\n return await getRepository(User).findOneBy({ id: appBinding.creatorId })\n }\n}\n"]}
|
|
@@ -4,6 +4,7 @@ export * from './application/application.js';
|
|
|
4
4
|
export * from './appliance/appliance.js';
|
|
5
5
|
export * from './privilege/privilege.js';
|
|
6
6
|
export * from './role/role.js';
|
|
7
|
+
export * from './privilege/privilege-axis.js';
|
|
7
8
|
export * from './role-template/role-template.js';
|
|
8
9
|
export * from './role-template/role-template-types.js';
|
|
9
10
|
export * from './user/user.js';
|
|
@@ -27,7 +28,7 @@ export * from './privilege/privilege-types.js';
|
|
|
27
28
|
export * from './role/role-types.js';
|
|
28
29
|
export * from './user/user-types.js';
|
|
29
30
|
export * from './domain-link/domain-link-types.js';
|
|
30
|
-
export declare const entities: (typeof import("./
|
|
31
|
+
export declare const entities: (typeof import("./web-auth-credential/web-auth-credential.js").WebAuthCredential | typeof import("./user/user.js").User | typeof import("./auth-provider/auth-provider.js").AuthProvider | typeof import("./users-auth-providers/users-auth-providers.js").UsersAuthProviders | typeof import("./domain-owner/domain-owner.js").DomainOwner | typeof import("./granted-role/granted-role.js").GrantedRole | typeof import("./role/role.js").Role | typeof import("./privilege/privilege.js").Privilege | typeof import("./verification-token/verification-token.js").VerificationToken | typeof import("./verification-token/verification-token.js").VerificationTokenType | typeof import("./password-history/password-history.js").PasswordHistory | typeof import("./invitation/invitation.js").Invitation | typeof import("./application/application.js").Application | typeof import("./login-history/login-history.js").LoginHistory | typeof import("./appliance/appliance.js").Appliance | typeof import("./partner/partner.js").Partner | typeof import("./domain-link/domain-link.js").DomainLink)[];
|
|
31
32
|
export declare const schema: {
|
|
32
33
|
typeDefs: {
|
|
33
34
|
privilegeDirectiveTypeDefs: import("graphql").DocumentNode;
|
|
@@ -30,6 +30,7 @@ tslib_1.__exportStar(require("./application/application.js"), exports);
|
|
|
30
30
|
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
|
+
tslib_1.__exportStar(require("./privilege/privilege-axis.js"), exports);
|
|
33
34
|
tslib_1.__exportStar(require("./role-template/role-template.js"), exports);
|
|
34
35
|
tslib_1.__exportStar(require("./role-template/role-template-types.js"), exports);
|
|
35
36
|
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,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 './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,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"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an installation gets when no module says otherwise.
|
|
3
|
+
*
|
|
4
|
+
* The two travel together: a house running on the default vocabulary is also running on the
|
|
5
|
+
* default answer to "which of these is read-only". Splitting them would leave the common case
|
|
6
|
+
* — nobody declared anything — with a vocabulary and no idea which half is safe.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DEFAULT_AXES: string[];
|
|
9
|
+
export declare const DEFAULT_READ_ONLY_AXES: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Declare the axes a module uses, and which of them are read-only. Additive.
|
|
12
|
+
*
|
|
13
|
+
* Additive rather than one installation-wide list because a single installation genuinely
|
|
14
|
+
* mixes vocabularies: in dssp, `project` is declared with query/mutation while `kpi` uses
|
|
15
|
+
* eight words of its own. Each module says what it uses, and says it in the same call that
|
|
16
|
+
* says which are read-only — a separate call for the second half could drift out of step with
|
|
17
|
+
* the first, and drift like that is silent.
|
|
18
|
+
*
|
|
19
|
+
* Declaring an axis that is already known is not an error. Two modules reaching for `read`
|
|
20
|
+
* independently is the case this is built for, not a conflict.
|
|
21
|
+
*
|
|
22
|
+
* ── `readOnly` means "holding only this axis changes nothing" ────────────────
|
|
23
|
+
* It does **not** mean `@Query`. The two come apart, and where they do the axis is right and
|
|
24
|
+
* the operation type is the loose one:
|
|
25
|
+
*
|
|
26
|
+
* @Mutation(returns => ExportResult)
|
|
27
|
+
* @Directive('@privilege(category: "label-studio", privilege: "query")')
|
|
28
|
+
* async exportLabelStudioAnnotations(…)
|
|
29
|
+
*
|
|
30
|
+
* That calls an external API's export and hands back what it returns. Nothing in our database
|
|
31
|
+
* moves. It is declared `@Mutation` and the axis is `query`, and **the axis is the correct
|
|
32
|
+
* one** — a role holding only `label-studio:query` still changes nothing by running it.
|
|
33
|
+
*
|
|
34
|
+
* Six declarations in this house disagree this way, and all six are deliberate. Do not
|
|
35
|
+
* "fix" them to match their operation type.
|
|
36
|
+
*/
|
|
37
|
+
export declare function declareAxes(axes: string[], options?: {
|
|
38
|
+
readOnly?: string[];
|
|
39
|
+
}): void;
|
|
40
|
+
/**
|
|
41
|
+
* The axes this installation accepts.
|
|
42
|
+
*
|
|
43
|
+
* Falls back to the default rather than to "no check": an empty registry means nobody
|
|
44
|
+
* declared, not that everything is allowed.
|
|
45
|
+
*/
|
|
46
|
+
export declare function declaredAxes(): Set<string>;
|
|
47
|
+
/** Whether a value may be used as an axis here. */
|
|
48
|
+
export declare function isDeclaredAxis(axis: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* The axes that open nothing a role can change.
|
|
51
|
+
*
|
|
52
|
+
* Empty is a real answer, and it is different from the default. A house that declared its own
|
|
53
|
+
* vocabulary and never said which half is read-only has not told us — see
|
|
54
|
+
* `readOnlyAxesAreKnown`.
|
|
55
|
+
*/
|
|
56
|
+
export declare function readOnlyAxes(): Set<string>;
|
|
57
|
+
/**
|
|
58
|
+
* Whether anyone has said which axes are read-only.
|
|
59
|
+
*
|
|
60
|
+
* `computedRoleTemplates` asks this before offering VIEWER. Guessing would mean matching the
|
|
61
|
+
* literal word `query`, which is what this replaces: it is right in this house and wrong in
|
|
62
|
+
* dssp, where the read axis is spelled `read`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function readOnlyAxesAreKnown(): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Say what the vocabulary ended up being.
|
|
67
|
+
*
|
|
68
|
+
* The list is not in the SDL and cannot be, since it differs per installation. A line at boot
|
|
69
|
+
* is the honest substitute — and it is the only way to tell "nobody declared, so these are the
|
|
70
|
+
* defaults" from "someone declared exactly these".
|
|
71
|
+
*/
|
|
72
|
+
export declare function reportDeclaredAxes(): void;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_READ_ONLY_AXES = exports.DEFAULT_AXES = void 0;
|
|
4
|
+
exports.declareAxes = declareAxes;
|
|
5
|
+
exports.declaredAxes = declaredAxes;
|
|
6
|
+
exports.isDeclaredAxis = isDeclaredAxis;
|
|
7
|
+
exports.readOnlyAxes = readOnlyAxes;
|
|
8
|
+
exports.readOnlyAxesAreKnown = readOnlyAxesAreKnown;
|
|
9
|
+
exports.reportDeclaredAxes = reportDeclaredAxes;
|
|
10
|
+
const env_1 = require("@things-factory/env");
|
|
11
|
+
/**
|
|
12
|
+
* The axis vocabulary this installation uses.
|
|
13
|
+
*
|
|
14
|
+
* ── ⚠ Call `declareAxes` at the top of the module's `server/index.ts` ────────
|
|
15
|
+
* Not from a bootstrap hook. The check runs while the schema is assembled, and the schema is
|
|
16
|
+
* assembled before every hook a module can reach:
|
|
17
|
+
*
|
|
18
|
+
* shell/server/server.ts:115 emitHook('bootstrap-module-subscription')
|
|
19
|
+
* shell/server/server.ts:117 await schema() ← the axis check runs in here
|
|
20
|
+
* shell/server/server.ts:295 emitHook('bootstrap-module-start')
|
|
21
|
+
*
|
|
22
|
+
* `bootstrap-module-start` is where a module registers most things, role templates included,
|
|
23
|
+
* and it is 178 lines too late for this one.
|
|
24
|
+
*
|
|
25
|
+
* ── What an axis is ──────────────────────────────────────────────────────────
|
|
26
|
+
* The `privilege:` half of `@privilege(category:, privilege:)`. It does **not** name a
|
|
27
|
+
* GraphQL operation. It names which of a category's gates a role has to hold, and the values
|
|
28
|
+
* this house uses happen to borrow transport words.
|
|
29
|
+
*
|
|
30
|
+
* ── Why the framework does not own the list ──────────────────────────────────
|
|
31
|
+
* Closing the axis to `query | mutation` was ruled and then withdrawn (architect,
|
|
32
|
+
* 2026-09-15). It was measured first:
|
|
33
|
+
*
|
|
34
|
+
* things-factory + operato-application 792 declarations, all query or mutation
|
|
35
|
+
* dssp 65 declarations, 40 of them neither
|
|
36
|
+
* kpi: read · input · assessment · sentiment
|
|
37
|
+
* finalize · recalculate · auto-collect
|
|
38
|
+
* basic-info
|
|
39
|
+
*
|
|
40
|
+
* A framework enum would have made dssp's upgrade a 40-declaration migration, and the cost
|
|
41
|
+
* would fall on people who were not in the room. The framework cannot know which words are
|
|
42
|
+
* right; it does not own the modules that use them.
|
|
43
|
+
*
|
|
44
|
+
* ── What this buys ───────────────────────────────────────────────────────────
|
|
45
|
+
* A typo stops being a silent gate. `@privilege(category: "board", privilege: "mutaiton")`
|
|
46
|
+
* used to register a pair in `process['PRIVILEGES']` that nobody would ever be granted — a
|
|
47
|
+
* door permanently shut, with no error anywhere.
|
|
48
|
+
*/
|
|
49
|
+
const REGISTRY = 'PRIVILEGE_AXES';
|
|
50
|
+
/**
|
|
51
|
+
* What an installation gets when no module says otherwise.
|
|
52
|
+
*
|
|
53
|
+
* The two travel together: a house running on the default vocabulary is also running on the
|
|
54
|
+
* default answer to "which of these is read-only". Splitting them would leave the common case
|
|
55
|
+
* — nobody declared anything — with a vocabulary and no idea which half is safe.
|
|
56
|
+
*/
|
|
57
|
+
exports.DEFAULT_AXES = ['query', 'mutation'];
|
|
58
|
+
exports.DEFAULT_READ_ONLY_AXES = ['query'];
|
|
59
|
+
function registry() {
|
|
60
|
+
if (!process[REGISTRY]) {
|
|
61
|
+
process[REGISTRY] = { axes: new Set(), readOnly: new Set(), declared: false };
|
|
62
|
+
}
|
|
63
|
+
return process[REGISTRY];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Declare the axes a module uses, and which of them are read-only. Additive.
|
|
67
|
+
*
|
|
68
|
+
* Additive rather than one installation-wide list because a single installation genuinely
|
|
69
|
+
* mixes vocabularies: in dssp, `project` is declared with query/mutation while `kpi` uses
|
|
70
|
+
* eight words of its own. Each module says what it uses, and says it in the same call that
|
|
71
|
+
* says which are read-only — a separate call for the second half could drift out of step with
|
|
72
|
+
* the first, and drift like that is silent.
|
|
73
|
+
*
|
|
74
|
+
* Declaring an axis that is already known is not an error. Two modules reaching for `read`
|
|
75
|
+
* independently is the case this is built for, not a conflict.
|
|
76
|
+
*
|
|
77
|
+
* ── `readOnly` means "holding only this axis changes nothing" ────────────────
|
|
78
|
+
* It does **not** mean `@Query`. The two come apart, and where they do the axis is right and
|
|
79
|
+
* the operation type is the loose one:
|
|
80
|
+
*
|
|
81
|
+
* @Mutation(returns => ExportResult)
|
|
82
|
+
* @Directive('@privilege(category: "label-studio", privilege: "query")')
|
|
83
|
+
* async exportLabelStudioAnnotations(…)
|
|
84
|
+
*
|
|
85
|
+
* That calls an external API's export and hands back what it returns. Nothing in our database
|
|
86
|
+
* moves. It is declared `@Mutation` and the axis is `query`, and **the axis is the correct
|
|
87
|
+
* one** — a role holding only `label-studio:query` still changes nothing by running it.
|
|
88
|
+
*
|
|
89
|
+
* Six declarations in this house disagree this way, and all six are deliberate. Do not
|
|
90
|
+
* "fix" them to match their operation type.
|
|
91
|
+
*/
|
|
92
|
+
function declareAxes(axes, options = {}) {
|
|
93
|
+
if (!Array.isArray(axes) || axes.length === 0) {
|
|
94
|
+
throw new Error('declareAxes needs at least one axis. An empty list would refuse every declaration in ' +
|
|
95
|
+
'the installation, which is never what the caller meant.');
|
|
96
|
+
}
|
|
97
|
+
for (const axis of axes) {
|
|
98
|
+
if (typeof axis !== 'string' || !axis.trim()) {
|
|
99
|
+
throw new Error(`declareAxes got ${JSON.stringify(axis)} as an axis. Axes are non-empty strings.`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const readOnly = options.readOnly || [];
|
|
103
|
+
/*
|
|
104
|
+
* A read-only axis has to be one of the axes named in the same call.
|
|
105
|
+
*
|
|
106
|
+
* Otherwise a typo here is the quietest kind: `readOnly: ['querry']` would mark nothing,
|
|
107
|
+
* VIEWER would silently lose every category of this module, and the screen would count the
|
|
108
|
+
* shortfall correctly and show it as fact.
|
|
109
|
+
*/
|
|
110
|
+
for (const axis of readOnly) {
|
|
111
|
+
if (!axes.includes(axis)) {
|
|
112
|
+
throw new Error(`declareAxes was told "${axis}" is read-only, but that axis is not in the same call. ` +
|
|
113
|
+
`Declared here: ${axes.join(', ')}.`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const store = registry();
|
|
117
|
+
for (const axis of axes) {
|
|
118
|
+
store.axes.add(axis);
|
|
119
|
+
}
|
|
120
|
+
for (const axis of readOnly) {
|
|
121
|
+
store.readOnly.add(axis);
|
|
122
|
+
}
|
|
123
|
+
store.declared = true;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The axes this installation accepts.
|
|
127
|
+
*
|
|
128
|
+
* Falls back to the default rather than to "no check": an empty registry means nobody
|
|
129
|
+
* declared, not that everything is allowed.
|
|
130
|
+
*/
|
|
131
|
+
function declaredAxes() {
|
|
132
|
+
const store = registry();
|
|
133
|
+
return store.declared ? store.axes : new Set(exports.DEFAULT_AXES);
|
|
134
|
+
}
|
|
135
|
+
/** Whether a value may be used as an axis here. */
|
|
136
|
+
function isDeclaredAxis(axis) {
|
|
137
|
+
return declaredAxes().has(axis);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The axes that open nothing a role can change.
|
|
141
|
+
*
|
|
142
|
+
* Empty is a real answer, and it is different from the default. A house that declared its own
|
|
143
|
+
* vocabulary and never said which half is read-only has not told us — see
|
|
144
|
+
* `readOnlyAxesAreKnown`.
|
|
145
|
+
*/
|
|
146
|
+
function readOnlyAxes() {
|
|
147
|
+
const store = registry();
|
|
148
|
+
return store.declared ? store.readOnly : new Set(exports.DEFAULT_READ_ONLY_AXES);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Whether anyone has said which axes are read-only.
|
|
152
|
+
*
|
|
153
|
+
* `computedRoleTemplates` asks this before offering VIEWER. Guessing would mean matching the
|
|
154
|
+
* literal word `query`, which is what this replaces: it is right in this house and wrong in
|
|
155
|
+
* dssp, where the read axis is spelled `read`.
|
|
156
|
+
*/
|
|
157
|
+
function readOnlyAxesAreKnown() {
|
|
158
|
+
return readOnlyAxes().size > 0;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Say what the vocabulary ended up being.
|
|
162
|
+
*
|
|
163
|
+
* The list is not in the SDL and cannot be, since it differs per installation. A line at boot
|
|
164
|
+
* is the honest substitute — and it is the only way to tell "nobody declared, so these are the
|
|
165
|
+
* defaults" from "someone declared exactly these".
|
|
166
|
+
*/
|
|
167
|
+
function reportDeclaredAxes() {
|
|
168
|
+
const store = registry();
|
|
169
|
+
const axes = [...declaredAxes()].sort().join(', ');
|
|
170
|
+
const readOnly = [...readOnlyAxes()].sort().join(', ');
|
|
171
|
+
if (!store.declared) {
|
|
172
|
+
env_1.logger.info(`[privilege] no module declared privilege axes; using the defaults: ${axes} (read-only: ${readOnly})`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
env_1.logger.info(`[privilege] axes declared by this installation: ${axes}`);
|
|
176
|
+
if (readOnlyAxesAreKnown()) {
|
|
177
|
+
env_1.logger.info(`[privilege] read-only axes: ${readOnly}`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
/*
|
|
181
|
+
* Loud, because the consequence is a template quietly going missing from the role screen.
|
|
182
|
+
* "We do not know" is not "there are none", and folding the two would hand an administrator
|
|
183
|
+
* a VIEWER that opens nothing.
|
|
184
|
+
*/
|
|
185
|
+
env_1.logger.warn('[privilege] no module said which axes are read-only, so the VIEWER template is not offered. ' +
|
|
186
|
+
"Add it where the axes are declared: declareAxes([...], { readOnly: ['query'] }).");
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=privilege-axis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"privilege-axis.js","sourceRoot":"","sources":["../../../server/service/privilege/privilege-axis.ts"],"names":[],"mappings":"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","sourcesContent":["import { logger } from '@things-factory/env'\n\n/**\n * The axis vocabulary this installation uses.\n *\n * ── ⚠ Call `declareAxes` at the top of the module's `server/index.ts` ────────\n * Not from a bootstrap hook. The check runs while the schema is assembled, and the schema is\n * assembled before every hook a module can reach:\n *\n * shell/server/server.ts:115 emitHook('bootstrap-module-subscription')\n * shell/server/server.ts:117 await schema() ← the axis check runs in here\n * shell/server/server.ts:295 emitHook('bootstrap-module-start')\n *\n * `bootstrap-module-start` is where a module registers most things, role templates included,\n * and it is 178 lines too late for this one.\n *\n * ── What an axis is ──────────────────────────────────────────────────────────\n * The `privilege:` half of `@privilege(category:, privilege:)`. It does **not** name a\n * GraphQL operation. It names which of a category's gates a role has to hold, and the values\n * this house uses happen to borrow transport words.\n *\n * ── Why the framework does not own the list ──────────────────────────────────\n * Closing the axis to `query | mutation` was ruled and then withdrawn (architect,\n * 2026-09-15). It was measured first:\n *\n * things-factory + operato-application 792 declarations, all query or mutation\n * dssp 65 declarations, 40 of them neither\n * kpi: read · input · assessment · sentiment\n * finalize · recalculate · auto-collect\n * basic-info\n *\n * A framework enum would have made dssp's upgrade a 40-declaration migration, and the cost\n * would fall on people who were not in the room. The framework cannot know which words are\n * right; it does not own the modules that use them.\n *\n * ── What this buys ───────────────────────────────────────────────────────────\n * A typo stops being a silent gate. `@privilege(category: \"board\", privilege: \"mutaiton\")`\n * used to register a pair in `process['PRIVILEGES']` that nobody would ever be granted — a\n * door permanently shut, with no error anywhere.\n */\n\nconst REGISTRY = 'PRIVILEGE_AXES'\n\n/**\n * What an installation gets when no module says otherwise.\n *\n * The two travel together: a house running on the default vocabulary is also running on the\n * default answer to \"which of these is read-only\". Splitting them would leave the common case\n * — nobody declared anything — with a vocabulary and no idea which half is safe.\n */\nexport const DEFAULT_AXES = ['query', 'mutation']\nexport const DEFAULT_READ_ONLY_AXES = ['query']\n\ntype Registry = { axes: Set<string>; readOnly: Set<string>; declared: boolean }\n\nfunction registry(): Registry {\n if (!process[REGISTRY]) {\n process[REGISTRY] = { axes: new Set<string>(), readOnly: new Set<string>(), declared: false }\n }\n\n return process[REGISTRY]\n}\n\n/**\n * Declare the axes a module uses, and which of them are read-only. Additive.\n *\n * Additive rather than one installation-wide list because a single installation genuinely\n * mixes vocabularies: in dssp, `project` is declared with query/mutation while `kpi` uses\n * eight words of its own. Each module says what it uses, and says it in the same call that\n * says which are read-only — a separate call for the second half could drift out of step with\n * the first, and drift like that is silent.\n *\n * Declaring an axis that is already known is not an error. Two modules reaching for `read`\n * independently is the case this is built for, not a conflict.\n *\n * ── `readOnly` means \"holding only this axis changes nothing\" ────────────────\n * It does **not** mean `@Query`. The two come apart, and where they do the axis is right and\n * the operation type is the loose one:\n *\n * @Mutation(returns => ExportResult)\n * @Directive('@privilege(category: \"label-studio\", privilege: \"query\")')\n * async exportLabelStudioAnnotations(…)\n *\n * That calls an external API's export and hands back what it returns. Nothing in our database\n * moves. It is declared `@Mutation` and the axis is `query`, and **the axis is the correct\n * one** — a role holding only `label-studio:query` still changes nothing by running it.\n *\n * Six declarations in this house disagree this way, and all six are deliberate. Do not\n * \"fix\" them to match their operation type.\n */\nexport function declareAxes(axes: string[], options: { readOnly?: string[] } = {}): void {\n if (!Array.isArray(axes) || axes.length === 0) {\n throw new Error(\n 'declareAxes needs at least one axis. An empty list would refuse every declaration in ' +\n 'the installation, which is never what the caller meant.'\n )\n }\n\n for (const axis of axes) {\n if (typeof axis !== 'string' || !axis.trim()) {\n throw new Error(`declareAxes got ${JSON.stringify(axis)} as an axis. Axes are non-empty strings.`)\n }\n }\n\n const readOnly = options.readOnly || []\n\n /*\n * A read-only axis has to be one of the axes named in the same call.\n *\n * Otherwise a typo here is the quietest kind: `readOnly: ['querry']` would mark nothing,\n * VIEWER would silently lose every category of this module, and the screen would count the\n * shortfall correctly and show it as fact.\n */\n for (const axis of readOnly) {\n if (!axes.includes(axis)) {\n throw new Error(\n `declareAxes was told \"${axis}\" is read-only, but that axis is not in the same call. ` +\n `Declared here: ${axes.join(', ')}.`\n )\n }\n }\n\n const store = registry()\n\n for (const axis of axes) {\n store.axes.add(axis)\n }\n\n for (const axis of readOnly) {\n store.readOnly.add(axis)\n }\n\n store.declared = true\n}\n\n/**\n * The axes this installation accepts.\n *\n * Falls back to the default rather than to \"no check\": an empty registry means nobody\n * declared, not that everything is allowed.\n */\nexport function declaredAxes(): Set<string> {\n const store = registry()\n\n return store.declared ? store.axes : new Set(DEFAULT_AXES)\n}\n\n/** Whether a value may be used as an axis here. */\nexport function isDeclaredAxis(axis: string): boolean {\n return declaredAxes().has(axis)\n}\n\n/**\n * The axes that open nothing a role can change.\n *\n * Empty is a real answer, and it is different from the default. A house that declared its own\n * vocabulary and never said which half is read-only has not told us — see\n * `readOnlyAxesAreKnown`.\n */\nexport function readOnlyAxes(): Set<string> {\n const store = registry()\n\n return store.declared ? store.readOnly : new Set(DEFAULT_READ_ONLY_AXES)\n}\n\n/**\n * Whether anyone has said which axes are read-only.\n *\n * `computedRoleTemplates` asks this before offering VIEWER. Guessing would mean matching the\n * literal word `query`, which is what this replaces: it is right in this house and wrong in\n * dssp, where the read axis is spelled `read`.\n */\nexport function readOnlyAxesAreKnown(): boolean {\n return readOnlyAxes().size > 0\n}\n\n/**\n * Say what the vocabulary ended up being.\n *\n * The list is not in the SDL and cannot be, since it differs per installation. A line at boot\n * is the honest substitute — and it is the only way to tell \"nobody declared, so these are the\n * defaults\" from \"someone declared exactly these\".\n */\nexport function reportDeclaredAxes(): void {\n const store = registry()\n const axes = [...declaredAxes()].sort().join(', ')\n const readOnly = [...readOnlyAxes()].sort().join(', ')\n\n if (!store.declared) {\n logger.info(`[privilege] no module declared privilege axes; using the defaults: ${axes} (read-only: ${readOnly})`)\n return\n }\n\n logger.info(`[privilege] axes declared by this installation: ${axes}`)\n\n if (readOnlyAxesAreKnown()) {\n logger.info(`[privilege] read-only axes: ${readOnly}`)\n return\n }\n\n /*\n * Loud, because the consequence is a template quietly going missing from the role screen.\n * \"We do not know\" is not \"there are none\", and folding the two would hand an administrator\n * a VIEWER that opens nothing.\n */\n logger.warn(\n '[privilege] no module said which axes are read-only, so the VIEWER template is not offered. ' +\n \"Add it where the axes are declared: declareAxes([...], { readOnly: ['query'] }).\"\n )\n}\n"]}
|
|
@@ -6,6 +6,7 @@ const graphql_1 = require("graphql");
|
|
|
6
6
|
const graphql_tag_1 = tslib_1.__importDefault(require("graphql-tag"));
|
|
7
7
|
const utils_1 = require("@graphql-tools/utils");
|
|
8
8
|
const check_permission_js_1 = require("../../utils/check-permission.js");
|
|
9
|
+
const privilege_axis_js_1 = require("./privilege-axis.js");
|
|
9
10
|
const privilege_rejection_js_1 = require("./privilege-rejection.js");
|
|
10
11
|
process['PRIVILEGES'] = {};
|
|
11
12
|
const DIRECTIVE = 'privilege';
|
|
@@ -41,6 +42,53 @@ const privilegeDirectiveResolver = (schema) => (0, utils_1.mapSchema)(schema, {
|
|
|
41
42
|
throw new Error(`Unexpected Error. args should be defined in @privilege directive for field ${fieldName}.`);
|
|
42
43
|
}
|
|
43
44
|
const { domainOwnerGranted, superUserGranted, category, privilege } = privilegeDirective;
|
|
45
|
+
const where = `${typeName}.${fieldName}`;
|
|
46
|
+
/*
|
|
47
|
+
* A declaration has to name something.
|
|
48
|
+
*
|
|
49
|
+
* `category` and `privilege` are one thing, not two: together they name a row in the
|
|
50
|
+
* privilege table, and either alone names no row, so `User.hasPrivilege` is never
|
|
51
|
+
* reached and the field falls back on ownership flags it did not set.
|
|
52
|
+
*/
|
|
53
|
+
if (!category !== !privilege) {
|
|
54
|
+
throw new Error(`@privilege on ${where} gives ${category ? 'a category with no privilege' : 'a privilege with no category'}. ` +
|
|
55
|
+
`The pair names the privilege to look for; one half names none.`);
|
|
56
|
+
}
|
|
57
|
+
/*
|
|
58
|
+
* `@privilege()` with nothing in it reads like a gate and guards nothing. Until
|
|
59
|
+
* 2026-09-15 what it did depended on where the caller sat: `checkPermission` answered
|
|
60
|
+
* false from a safe address and refused everyone, true from an unsafe one and let
|
|
61
|
+
* everyone through. Both are false now, so the shape is merely useless rather than
|
|
62
|
+
* dangerous — and a field that is open should say so by carrying no directive, not by
|
|
63
|
+
* carrying an empty one.
|
|
64
|
+
*
|
|
65
|
+
* It is also the shape someone reaches for wanting "being signed in is enough". That
|
|
66
|
+
* is a real thing to want and this directive does not express it; the answer settled
|
|
67
|
+
* on 2026-09-15 is a category for the published side of a module, `<module>-published`.
|
|
68
|
+
*
|
|
69
|
+
* Throwing stops the boot, which is what §6-2 of docs/design/auth-rebuild.md settled
|
|
70
|
+
* for a resolver that declares nothing. A decorator that declares nothing is that
|
|
71
|
+
* same resolver wearing one.
|
|
72
|
+
*/
|
|
73
|
+
if (!category && !privilege && !domainOwnerGranted && !superUserGranted) {
|
|
74
|
+
throw new Error(`@privilege on ${where} declares nothing. ` +
|
|
75
|
+
`Name (category, privilege) for a privilege gate, or domainOwnerGranted / superUserGranted for an ` +
|
|
76
|
+
`ownership gate. A field open to everyone carries no directive at all.`);
|
|
77
|
+
}
|
|
78
|
+
/*
|
|
79
|
+
* The axis has to be one this installation declared.
|
|
80
|
+
*
|
|
81
|
+
* Without this, `privilege: "mutaiton"` registered a pair nobody would ever be granted
|
|
82
|
+
* — a gate shut forever, with no error to read. The list is not the framework's: see
|
|
83
|
+
* `privilege-axis.ts` for why, and for the fact that it must be declared at module
|
|
84
|
+
* import time rather than on a bootstrap hook.
|
|
85
|
+
*/
|
|
86
|
+
if (privilege && !(0, privilege_axis_js_1.isDeclaredAxis)(privilege)) {
|
|
87
|
+
throw new Error(`@privilege on ${where} uses axis "${privilege}", which this installation does not declare. ` +
|
|
88
|
+
`Declared axes: ${[...(0, privilege_axis_js_1.declaredAxes)()].sort().join(', ')}. ` +
|
|
89
|
+
`Fix the spelling, or call declareAxes(['${privilege}']) at the top level of the module's ` +
|
|
90
|
+
`server/index.ts — a bootstrap hook runs after the schema is built and is too late.`);
|
|
91
|
+
}
|
|
44
92
|
if (category && privilege) {
|
|
45
93
|
process['PRIVILEGES'][`${category} ${privilege}`] = [category, privilege];
|
|
46
94
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"privilege-directive.js","sourceRoot":"","sources":["../../../server/service/privilege/privilege-directive.ts"],"names":[],"mappings":";;;;AAAA,qCAA6D;AAC7D,sEAA6B;AAE7B,gDAA0E;AAG1E,yEAAiE;AACjE,qEAA6D;AAE7D,OAAO,CAAC,YAAY,CAAC,GAAG,EAAE,CAAA;AAE1B,MAAM,SAAS,GAAG,WAAW,CAAA;AAE7B;;;;;;;;;;;;;;GAcG;AACU,QAAA,0BAA0B,GAAG,IAAA,qBAAG,EAAA;;;;;;;CAO5C,CAAA;AACM,MAAM,0BAA0B,GAAG,CAAC,MAAqB,EAAE,EAAE,CAClE,IAAA,iBAAS,EAAC,MAAM,EAAE;IAChB,CAAC,kBAAU,CAAC,YAAY,CAAC,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;QACtE,MAAM,kBAAkB,GAAG,IAAA,oBAAY,EAAC,MAAM,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QAC5E,IAAI,kBAAkB,EAAE,CAAC;YACvB,MAAM,EAAE,OAAO,GAAG,8BAAoB,EAAE,IAAI,EAAE,GAAG,WAAW,CAAA;YAE5D,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,KAAK,CAAC,8EAA8E,SAAS,GAAG,CAAC,CAAA;YAC7G,CAAC;YAED,MAAM,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,kBAAkB,CAAA;YACxF,IAAI,QAAQ,IAAI,SAAS,EAAE,CAAC;gBAC1B,OAAO,CAAC,YAAY,CAAC,CAAC,GAAG,QAAQ,IAAI,SAAS,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAA;YAC3E,CAAC;YAED,0BAA0B;YAC1B,MAAM,mBAAmB,GAAG,WAAW,CAAC,WAAW,IAAI,EAAE,CAAA;YAEzD,gCAAgC;YAChC,MAAM,oBAAoB,GACxB,8BAA8B,QAAQ,IAAI,SAAS,EAAE;gBACrD,CAAC,kBAAkB,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChD,CAAC,gBAAgB,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;YAEhD,qBAAqB;YACrB,WAAW,CAAC,WAAW,GAAG,GAAG,mBAAmB,IAAI,oBAAoB,EAAE,CAAC,IAAI,EAAE,CAAA;YAEjF,WAAW,CAAC,OAAO,GAAG,KAAK,WAAW,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI;gBAC/D,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;gBAEtE,MAAM,SAAS,GAAG,MAAM,IAAA,qCAAe,EACrC;oBACE,QAAQ;oBACR,SAAS;oBACT,KAAK,EAAE,kBAAkB;oBACzB,KAAK,EAAE,gBAAgB;iBACxB,EACD,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,oBAAoB,CACrB,CAAA;gBAED,IAAI,SAAS,EAAE,CAAC;oBACd,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;gBAC9D,CAAC;gBAED;;;;;;;mBAOG;gBACH,MAAM,IAAA,2CAAkB,EAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAA;YACxD,CAAC,CAAA;YAED,OAAO,WAAW,CAAA;QACpB,CAAC;IACH,CAAC;CACF,CAAC,CAAA;
|
|
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,4 +1,4 @@
|
|
|
1
1
|
import { RoleTemplateMutation } from './role-template-mutation.js';
|
|
2
2
|
import { RoleTemplateQuery } from './role-template-query.js';
|
|
3
3
|
export declare const entities: any[];
|
|
4
|
-
export declare const resolvers: (typeof
|
|
4
|
+
export declare const resolvers: (typeof RoleTemplateMutation | typeof RoleTemplateQuery)[];
|
|
@@ -8,6 +8,7 @@ exports.allRoleTemplates = allRoleTemplates;
|
|
|
8
8
|
exports.unknownTemplateGrants = unknownTemplateGrants;
|
|
9
9
|
exports.reportUnknownTemplateGrants = reportUnknownTemplateGrants;
|
|
10
10
|
const env_1 = require("@things-factory/env");
|
|
11
|
+
const privilege_axis_js_1 = require("../privilege/privilege-axis.js");
|
|
11
12
|
const REGISTRY = 'ROLE_TEMPLATES';
|
|
12
13
|
process[REGISTRY] = {};
|
|
13
14
|
function registry() {
|
|
@@ -82,11 +83,28 @@ function computedRoleTemplates() {
|
|
|
82
83
|
* screen already computes from `grants` — "opens 30 things across 30 modules" is exact
|
|
83
84
|
* and moves when the installation does.
|
|
84
85
|
*/
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
/*
|
|
87
|
+
* VIEWER is only offered when someone has said which axes are read-only.
|
|
88
|
+
*
|
|
89
|
+
* This used to match the literal word `query`, which is right in this house and wrong in
|
|
90
|
+
* dssp, where the read axis is spelled `read` and `kpi` has no `query` at all — VIEWER
|
|
91
|
+
* would have quietly dropped that installation's main module.
|
|
92
|
+
*
|
|
93
|
+
* And when nobody has said, the template is **not offered at all** rather than offered
|
|
94
|
+
* empty. An empty VIEWER is the quiet failure this whole design is built to avoid: an
|
|
95
|
+
* administrator picks it, gets a role, no door opens, and the list counts the zero
|
|
96
|
+
* correctly. "We do not know" is not "there are none".
|
|
97
|
+
*/
|
|
98
|
+
const readOnly = (0, privilege_axis_js_1.readOnlyAxes)();
|
|
99
|
+
const viewer = (0, privilege_axis_js_1.readOnlyAxesAreKnown)()
|
|
100
|
+
? {
|
|
101
|
+
id: 'framework.viewer',
|
|
102
|
+
name: 'VIEWER',
|
|
103
|
+
grants: categories
|
|
104
|
+
.map(category => ({ category, axes: [...axesOf.get(category)].filter(axis => readOnly.has(axis)).sort() }))
|
|
105
|
+
.filter(grant => grant.axes.length > 0)
|
|
106
|
+
}
|
|
107
|
+
: undefined;
|
|
90
108
|
/*
|
|
91
109
|
* The one place that widens on purpose.
|
|
92
110
|
*
|
|
@@ -99,7 +117,7 @@ function computedRoleTemplates() {
|
|
|
99
117
|
name: 'ADMINISTRATOR',
|
|
100
118
|
grants: categories.map(category => ({ category, axes: [...axesOf.get(category)].sort() }))
|
|
101
119
|
};
|
|
102
|
-
return [viewer, administrator];
|
|
120
|
+
return viewer ? [viewer, administrator] : [administrator];
|
|
103
121
|
}
|
|
104
122
|
/** Every template this installation offers — declared by apps, plus the computed two. */
|
|
105
123
|
function allRoleTemplates() {
|