@things-factory/auth-base 10.1.37 → 10.1.38

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.
Files changed (37) hide show
  1. package/dist-client/auth.d.ts +8 -0
  2. package/dist-client/auth.js +26 -0
  3. package/dist-client/auth.js.map +1 -1
  4. package/dist-client/domain-required.d.ts +54 -0
  5. package/dist-client/domain-required.js +91 -0
  6. package/dist-client/domain-required.js.map +1 -0
  7. package/dist-client/tsconfig.tsbuildinfo +1 -1
  8. package/dist-server/controllers/invitation.d.ts +26 -0
  9. package/dist-server/controllers/invitation.js +12 -1
  10. package/dist-server/controllers/invitation.js.map +1 -1
  11. package/dist-server/service/app-binding/app-binding-query.d.ts +17 -0
  12. package/dist-server/service/app-binding/app-binding-query.js +32 -2
  13. package/dist-server/service/app-binding/app-binding-query.js.map +1 -1
  14. package/dist-server/service/index.d.ts +7 -2
  15. package/dist-server/service/index.js +3 -0
  16. package/dist-server/service/index.js.map +1 -1
  17. package/dist-server/service/open-fields.d.ts +9 -0
  18. package/dist-server/service/open-fields.js +24 -0
  19. package/dist-server/service/open-fields.js.map +1 -0
  20. package/dist-server/service/privilege/open-field-rule.d.ts +93 -0
  21. package/dist-server/service/privilege/open-field-rule.js +81 -0
  22. package/dist-server/service/privilege/open-field-rule.js.map +1 -0
  23. package/dist-server/service/privilege/privilege-directive.d.ts +9 -1
  24. package/dist-server/service/privilege/privilege-directive.js +177 -95
  25. package/dist-server/service/privilege/privilege-directive.js.map +1 -1
  26. package/dist-server/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +4 -4
  28. package/tests/app-binding-db.test.ts +48 -0
  29. package/tests/domain-required.test.ts +127 -0
  30. package/tests/invitation-route.test.ts +97 -0
  31. package/tests/open-doors.test.ts +13 -21
  32. package/tests/open-fields-enforcement.test.ts +215 -0
  33. package/translations/en.json +0 -1
  34. package/translations/ja.json +0 -1
  35. package/translations/ko.json +0 -1
  36. package/translations/ms.json +0 -1
  37. package/translations/zh.json +0 -1
@@ -1 +1 @@
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, Directive, 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 { APP_BINDING_USER_TYPE, AppBinding } from './app-binding.js'\nimport { AppBindingList } from './app-binding-types.js'\n\n/*\n * ── Why these two now carry a gate ──────────────────────────────────────────\n * They carried none, so the schema recorded them as open and any valid token could list this\n * tenant's bindings. The rows are tenant-scoped, and `AppBinding.refreshToken` has a gate of\n * its own, so what was reachable was the rest of the row — the name, the email, and which\n * application each client is bound to.\n *\n * The pair is the one the appliance and application lists ask for. Those three screens sit\n * together and are the same kind of thing, and leaving one of them open reads as a decision\n * that nobody made.\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 @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\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: APP_BINDING_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 @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\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: APP_BINDING_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"]}
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, Directive, 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 { APP_BINDING_USER_TYPE, AppBinding } from './app-binding.js'\nimport { AppBindingList } from './app-binding-types.js'\n\n/*\n * ── Why these two now carry a gate ──────────────────────────────────────────\n * They carried none, so the schema recorded them as open and any valid token could list this\n * tenant's bindings. The rows are tenant-scoped, and `AppBinding.refreshToken` has a gate of\n * its own, so what was reachable was the rest of the row — the name, the email, and which\n * application each client is bound to.\n *\n * The pair is the one the appliance and application lists ask for. Those three screens sit\n * together and are the same kind of thing, and leaving one of them open reads as a decision\n * that nobody made.\n */\n/**\n * The fields a free-text search looks in: the binding's name, its generated email, and the\n * description someone wrote for it.\n */\nexport const APP_BINDING_SEARCHABLES = ['name', 'email', 'description']\n\n/**\n * Turns the list's free-text term into one search filter per searchable field.\n *\n * The screen now sends the term itself (`ListParam.search`) and leaves it to the server to say\n * where it looks. This door builds its query with the older `buildQuery`, which only knows search\n * *filters* - so the term is spelled out here, at the caller, rather than by changing the shared\n * helper for one door. Without this the term reaches the door and is dropped, and the list comes\n * back whole with nothing to say so.\n *\n * `buildQuery` applies a search filter as `LIKE :value`, so the value carries its own wildcards.\n */\nexport function withSearchTerm(params: ListParam): ListParam {\n const term = params?.search?.trim()\n\n if (!term) {\n return params\n }\n\n return {\n ...params,\n filters: [\n ...(params.filters ?? []),\n ...APP_BINDING_SEARCHABLES.map(name => ({ name, operator: 'search', value: `%${term}%` }))\n ]\n }\n}\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 @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\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: APP_BINDING_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 @Directive('@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)')\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: APP_BINDING_USER_TYPE })\n\n buildQuery(qb, withSearchTerm(params), null, { domainRef: false, searchables: APP_BINDING_SEARCHABLES })\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"]}
@@ -6,6 +6,7 @@ export * from './privilege/privilege.js';
6
6
  export * from './role/role.js';
7
7
  export * from './privilege/privilege-axis.js';
8
8
  export { privilegeOfField, type FieldGate } from './privilege/privilege-directive.js';
9
+ export { type OpenFieldsDeclaration } from './privilege/open-field-rule.js';
9
10
  export * from './role-template/role-template.js';
10
11
  export * from './role-template/role-template-types.js';
11
12
  export * from './user/user.js';
@@ -29,13 +30,17 @@ export * from './privilege/privilege-types.js';
29
30
  export * from './role/role-types.js';
30
31
  export * from './user/user-types.js';
31
32
  export * from './domain-link/domain-link-types.js';
32
- 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)[];
33
+ export declare const entities: (typeof import("./user/user.js").User | typeof import("./application/application.js").Application | typeof import("./users-auth-providers/users-auth-providers.js").UsersAuthProviders | typeof import("./auth-provider/auth-provider.js").AuthProvider | typeof import("./appliance/appliance.js").Appliance | typeof import("./privilege/privilege.js").Privilege | typeof import("./role/role.js").Role | typeof import("./partner/partner.js").Partner | typeof import("./granted-role/granted-role.js").GrantedRole | typeof import("./invitation/invitation.js").Invitation | typeof import("./password-history/password-history.js").PasswordHistory | typeof import("./verification-token/verification-token.js").VerificationToken | typeof import("./verification-token/verification-token.js").VerificationTokenType | typeof import("./login-history/login-history.js").LoginHistory | typeof import("./web-auth-credential/web-auth-credential.js").WebAuthCredential | typeof import("./domain-link/domain-link.js").DomainLink | typeof import("./domain-owner/domain-owner.js").DomainOwner)[];
33
34
  export declare const schema: {
34
35
  typeDefs: {
35
36
  privilegeDirectiveTypeDefs: import("graphql").DocumentNode;
36
37
  };
38
+ openFields: import("./privilege/open-field-rule.js").OpenFieldsDeclaration;
37
39
  resolverClasses: any[];
38
40
  directives: {
39
- privilege: (schema: import("graphql").GraphQLSchema) => import("graphql").GraphQLSchema;
41
+ privilege: (schema: import("graphql").GraphQLSchema, options?: {
42
+ modules?: import("./privilege/open-field-rule.js").DeclaringModule[];
43
+ mode?: import("./privilege/open-field-rule.js").EnforcementMode;
44
+ }) => import("graphql").GraphQLSchema;
40
45
  };
41
46
  };
@@ -16,6 +16,7 @@ const index_js_10 = require("./partner/index.js");
16
16
  const index_js_11 = require("./password-history/index.js");
17
17
  const index_js_12 = require("./privilege/index.js");
18
18
  const privilege_directive_js_1 = require("./privilege/privilege-directive.js");
19
+ const open_fields_js_1 = require("./open-fields.js");
19
20
  const index_js_13 = require("./role/index.js");
20
21
  const index_js_14 = require("./role-template/index.js");
21
22
  const index_js_15 = require("./user/index.js");
@@ -81,6 +82,8 @@ exports.schema = {
81
82
  typeDefs: {
82
83
  privilegeDirectiveTypeDefs: privilege_directive_js_1.privilegeDirectiveTypeDefs
83
84
  },
85
+ /* The fields that carry no @privilege, and why — `open-fields.ts`, read by the directive. */
86
+ openFields: open_fields_js_1.openFields,
84
87
  resolverClasses: [
85
88
  /* RESOLVER CLASSES */
86
89
  ...index_js_1.resolvers,
@@ -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,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
+ {"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,qDAA6C;AAC7C,+CAAsF;AACtF,wDAA6E;AAC7E,+CAAsF;AACtF,6DAAqF;AACrF,8DAAsF;AACtF,sDAAwG;AACxG,uDAA4G;AAE5G,yBAAyB;AACzB,yFAA8D;AAC9D,2EAAgD;AAChD,uEAA4C;AAC5C,mEAAwC;AACxC,mEAAwC;AACxC,yDAA8B;AAC9B,wEAA6C;AAC7C,gHAAgH;AAChH,6EAAqF;AAA5E,0HAAA,gBAAgB,OAAA;AAGzB,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,6FAA6F;IAC7F,UAAU,EAAV,2BAAU;IAEV,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,oBAA2B;QAC9B,GAAG,oBAAqB;QACxB,GAAG,oBAAoB;QACvB,GAAG,oBAAkB;QACrB,GAAG,qBAAkB;QACrB,GAAG,qBAAa;QAChB,GAAG,qBAAqB;QACxB,GAAG,qBAAa;QAChB,GAAG,qBAAgB;QACnB,GAAG,oBAAmB;QACtB,GAAG,oBAAkB;QACrB,GAAG,oBAAkB;QACrB,GAAG,oBAAuB;QAC1B,GAAG,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 { openFields } from './open-fields.js'\nimport { entities as RoleEntities, resolvers as RoleResolvers } from './role/index.js'\nimport { resolvers as RoleTemplateResolvers } from './role-template/index.js'\nimport { entities as UserEntities, resolvers as UserResolvers } from './user/index.js'\nimport { entities as VerificationTokenEntities } from './verification-token/index.js'\nimport { entities as WebAuthCredentialEntities } from './web-auth-credential/index.js'\nimport { entities as DomainLinkEntities, resolvers as DomainLinkResolver } from './domain-link/index.js'\nimport { entities as DomainOwnerEntities, resolvers as DomainOwnerResolvers } from './domain-owner/index.js'\n\n/* EXPORT ENTITY TYPES */\nexport * from './users-auth-providers/users-auth-providers.js'\nexport * from './auth-provider/auth-provider.js'\nexport * from './application/application.js'\nexport * from './appliance/appliance.js'\nexport * from './privilege/privilege.js'\nexport * from './role/role.js'\nexport * from './privilege/privilege-axis.js'\n/* What a field asks for, by name — for another entrance to the same door (operato-twin ADR-0072 decision 5). */\nexport { privilegeOfField, type FieldGate } from './privilege/privilege-directive.js'\n/* How a module declares the fields it leaves open — see `privilege/open-field-rule.ts`. */\nexport { type OpenFieldsDeclaration } from './privilege/open-field-rule.js'\nexport * from './role-template/role-template.js'\nexport * from './role-template/role-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 /* The fields that carry no @privilege, and why — `open-fields.ts`, read by the directive. */\n openFields,\n\n resolverClasses: [\n /* RESOLVER CLASSES */\n ...UsersAuthProvidersResolvers,\n ...AuthProviderResolvers,\n ...ApplicationResolvers,\n ...ApplianceResolvers,\n ...PrivilegeResolvers,\n ...RoleResolvers,\n ...RoleTemplateResolvers,\n ...UserResolvers,\n ...PartnerResolvers,\n ...GrantedRoleResolver,\n ...InvitationResolver,\n ...AppbindingResolver,\n ...DomainGeneratorResolver,\n ...LoginHistoryResolver,\n ...DomainLinkResolver,\n ...DomainOwnerResolvers\n ],\n directives: {\n privilege: privilegeDirectiveResolver\n }\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import type { OpenFieldsDeclaration } from './privilege/open-field-rule.js';
2
+ /**
3
+ * auth-base's root fields that declare no `@privilege` — every one of them, and why.
4
+ *
5
+ * There is no `notYetDeclared` here. The twelve that were open without a reason were closed on
6
+ * 2026-09-20 (`c02ee9bdc1`); what is left answers about the caller, or about the installation
7
+ * rather than anybody's data. An addition to this list needs its reason written first.
8
+ */
9
+ export declare const openFields: OpenFieldsDeclaration;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.openFields = void 0;
4
+ /**
5
+ * auth-base's root fields that declare no `@privilege` — every one of them, and why.
6
+ *
7
+ * There is no `notYetDeclared` here. The twelve that were open without a reason were closed on
8
+ * 2026-09-20 (`c02ee9bdc1`); what is left answers about the caller, or about the installation
9
+ * rather than anybody's data. An addition to this list needs its reason written first.
10
+ */
11
+ exports.openFields = {
12
+ byDesign: {
13
+ 'Query.myLoginHistories': "the caller's own sign-ins",
14
+ 'Query.myRoles': "the caller's own roles",
15
+ 'Query.hasPrivilege': 'whether the caller has one — a gate here would need the answer to itself',
16
+ 'Query.domainsWithPrivilege': 'the domains where the caller has one',
17
+ 'Query.checkUserBelongsDomain': 'whether the caller belongs to this domain',
18
+ 'Query.checkResettablePasswordToDefault': "about the caller's own password",
19
+ 'Query.checkDefaultPassword': 'whether the caller is still on the default password',
20
+ 'Query.passwordRule': "the installation's password policy. The change-password screen needs it before the caller has any privilege — and after a forced reset they may have none",
21
+ 'Query.authProviderTypes': "which provider types this build supports. A catalogue, not a tenant's configuration"
22
+ }
23
+ };
24
+ //# sourceMappingURL=open-fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-fields.js","sourceRoot":"","sources":["../../server/service/open-fields.ts"],"names":[],"mappings":";;;AAEA;;;;;;GAMG;AACU,QAAA,UAAU,GAA0B;IAC/C,QAAQ,EAAE;QACR,wBAAwB,EAAE,2BAA2B;QACrD,eAAe,EAAE,wBAAwB;QACzC,oBAAoB,EAAE,0EAA0E;QAChG,4BAA4B,EAAE,sCAAsC;QACpE,8BAA8B,EAAE,2CAA2C;QAC3E,wCAAwC,EAAE,iCAAiC;QAC3E,4BAA4B,EAAE,qDAAqD;QACnF,oBAAoB,EAClB,2JAA2J;QAC7J,yBAAyB,EAAE,qFAAqF;KACjH;CACF,CAAA","sourcesContent":["import type { OpenFieldsDeclaration } from './privilege/open-field-rule.js'\n\n/**\n * auth-base's root fields that declare no `@privilege` — every one of them, and why.\n *\n * There is no `notYetDeclared` here. The twelve that were open without a reason were closed on\n * 2026-09-20 (`c02ee9bdc1`); what is left answers about the caller, or about the installation\n * rather than anybody's data. An addition to this list needs its reason written first.\n */\nexport const openFields: OpenFieldsDeclaration = {\n byDesign: {\n 'Query.myLoginHistories': \"the caller's own sign-ins\",\n 'Query.myRoles': \"the caller's own roles\",\n 'Query.hasPrivilege': 'whether the caller has one — a gate here would need the answer to itself',\n 'Query.domainsWithPrivilege': 'the domains where the caller has one',\n 'Query.checkUserBelongsDomain': 'whether the caller belongs to this domain',\n 'Query.checkResettablePasswordToDefault': \"about the caller's own password\",\n 'Query.checkDefaultPassword': 'whether the caller is still on the default password',\n 'Query.passwordRule':\n \"the installation's password policy. The change-password screen needs it before the caller has any privilege — and after a forced reset they may have none\",\n 'Query.authProviderTypes': \"which provider types this build supports. A catalogue, not a tenant's configuration\"\n }\n}\n"]}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Which root fields are allowed to carry no `@privilege`, module by module
3
+ * (`docs/design/auth-rebuild.md` §6-2 and §8 step 1; the question sheet of 2026-09-24, Q1 · Q2).
4
+ *
5
+ * ── The problem this answers ────────────────────────────────────────────────
6
+ * `@privilege` is opt-in. A root field that declares nothing is open to anyone in the domain,
7
+ * and it is recorded as open in exactly the way a field somebody chose to open is — no error, no
8
+ * warning, nothing to grep for. On 2026-09-24 each product served about 300 of them, and new ones
9
+ * kept arriving the same way; `deleteAppBinding`, which deleted any account in the installation,
10
+ * went in like that.
11
+ *
12
+ * ── The declaration ─────────────────────────────────────────────────────────
13
+ * A module states its undeclared fields on the `schema` object it already exports, beside its
14
+ * `resolverClasses`, in two lists that mean different things:
15
+ *
16
+ * byDesign open on purpose, with the reason. `myRoles` answers about the caller;
17
+ * `passwordRule` has to answer before the caller has any privilege at all.
18
+ * These do not shrink.
19
+ * notYetDeclared open because nobody has decided what they guard yet. Names only. This list
20
+ * is meant to reach zero, and when it does the key goes with it.
21
+ *
22
+ * Mixing the two would turn the transition list into a permanent allow-list.
23
+ *
24
+ * ── Why a module opts in, rather than every field at once ───────────────────
25
+ * The four products each serve resolvers of their own from another repository. Enforcing on every
26
+ * field the day this lands would stop all four from booting in development. So a module that
27
+ * declares `openFields` is held to it, and one that does not is left as it was — and counted
28
+ * (`tests/open-fields-framework.test.ts`), so that "not opted in" is visible rather than silent.
29
+ *
30
+ * ── Why the data sits on `schema` and not behind a call into auth-base ──────
31
+ * `shell` has resolvers and cannot depend on auth-base (auth-base depends on it), and five other
32
+ * modules with resolvers do not depend on it either. The `schema` object every module already
33
+ * exports needs no import, and it is what the server walks to build the schema in the first place.
34
+ */
35
+ /** What a module says about its undeclared root fields. Keys are `Type.field`, e.g. `Query.myRoles`. */
36
+ export type OpenFieldsDeclaration = {
37
+ byDesign?: Record<string, string>;
38
+ notYetDeclared?: string[];
39
+ };
40
+ /** A module as the server sees it: a name, and the `schema` object its server entry exports. */
41
+ export type DeclaringModule = {
42
+ name: string;
43
+ schema?: {
44
+ resolverClasses?: Function[];
45
+ openFields?: OpenFieldsDeclaration;
46
+ } | null;
47
+ };
48
+ /** For one root field of an opted-in module: which module, and which list names it (if any). */
49
+ export type Covered = {
50
+ module: string;
51
+ listed: 'by-design' | 'not-yet-declared' | null;
52
+ };
53
+ export type Coverage = Map<string, Covered>;
54
+ /**
55
+ * The modules the running server loaded, in the order it loaded them.
56
+ *
57
+ * Under a test runner `orderedModuleNames` is empty, so this answers nothing and no field is
58
+ * enforced — a test that wants enforcement passes its modules explicitly. A module whose entry
59
+ * cannot be loaded here is skipped: the server itself would have failed on it before this point.
60
+ */
61
+ export declare function modulesFromLoader(): DeclaringModule[];
62
+ /**
63
+ * The root fields each opted-in module's own resolver classes make, and what that module's lists
64
+ * say about each.
65
+ *
66
+ * Attribution comes from type-graphql's metadata — the same record `buildSchema` reads — so a
67
+ * field belongs to the module whose class declared it, not to whichever module happens to be
68
+ * built alongside it.
69
+ */
70
+ export declare function coverageOf(modules: DeclaringModule[]): Coverage;
71
+ /**
72
+ * What to do with a root field that carries no `@privilege`.
73
+ *
74
+ * open-by-design leave it open — its module says why
75
+ * open-not-yet-declared leave it open — its module has it on the list to close
76
+ * unlisted its module opted in and says nothing about it: this is the field
77
+ * that arrived quietly, and the one this exists to stop
78
+ * not-opted-in its module has not declared lists; unchanged from before
79
+ */
80
+ export type UndeclaredVerdict = 'open-by-design' | 'open-not-yet-declared' | 'unlisted' | 'not-opted-in';
81
+ export declare function judgeUndeclared(where: string, coverage: Coverage): UndeclaredVerdict;
82
+ /**
83
+ * Where an unlisted field is stopped (question sheet Q2).
84
+ *
85
+ * boot the schema refuses to build and names every such field at once. Development and
86
+ * tests, so the person who added it sees it on their own screen before it ships
87
+ * request the server builds, and that one field refuses every call and logs its name.
88
+ * Production, so a line somebody forgot costs one field and not the whole product
89
+ *
90
+ * Neither ever opens anything. Wrong in the worst case means one field refuses.
91
+ */
92
+ export type EnforcementMode = 'boot' | 'request';
93
+ export declare function enforcementMode(): EnforcementMode;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.modulesFromLoader = modulesFromLoader;
4
+ exports.coverageOf = coverageOf;
5
+ exports.judgeUndeclared = judgeUndeclared;
6
+ exports.enforcementMode = enforcementMode;
7
+ const type_graphql_1 = require("type-graphql");
8
+ const env_1 = require("@things-factory/env");
9
+ /**
10
+ * The modules the running server loaded, in the order it loaded them.
11
+ *
12
+ * Under a test runner `orderedModuleNames` is empty, so this answers nothing and no field is
13
+ * enforced — a test that wants enforcement passes its modules explicitly. A module whose entry
14
+ * cannot be loaded here is skipped: the server itself would have failed on it before this point.
15
+ */
16
+ function modulesFromLoader() {
17
+ return (env_1.orderedModuleNames || []).map((name) => {
18
+ try {
19
+ return { name, schema: (0, env_1.loader)(name)?.schema };
20
+ }
21
+ catch (e) {
22
+ return { name, schema: null };
23
+ }
24
+ });
25
+ }
26
+ const ROOTS = [
27
+ ['Query', 'queries'],
28
+ ['Mutation', 'mutations'],
29
+ ['Subscription', 'subscriptions']
30
+ ];
31
+ /**
32
+ * The root fields each opted-in module's own resolver classes make, and what that module's lists
33
+ * say about each.
34
+ *
35
+ * Attribution comes from type-graphql's metadata — the same record `buildSchema` reads — so a
36
+ * field belongs to the module whose class declared it, not to whichever module happens to be
37
+ * built alongside it.
38
+ */
39
+ function coverageOf(modules) {
40
+ const storage = (0, type_graphql_1.getMetadataStorage)();
41
+ const coverage = new Map();
42
+ for (const module of modules) {
43
+ const declaration = module.schema?.openFields;
44
+ if (!declaration) {
45
+ continue;
46
+ }
47
+ const classes = new Set(module.schema?.resolverClasses || []);
48
+ const byDesign = declaration.byDesign || {};
49
+ const notYet = new Set(declaration.notYetDeclared || []);
50
+ for (const [typeName, bucket] of ROOTS) {
51
+ for (const field of storage[bucket] || []) {
52
+ if (!classes.has(field.target)) {
53
+ continue;
54
+ }
55
+ const where = `${typeName}.${field.schemaName}`;
56
+ coverage.set(where, {
57
+ module: module.name,
58
+ listed: where in byDesign ? 'by-design' : notYet.has(where) ? 'not-yet-declared' : null
59
+ });
60
+ }
61
+ }
62
+ }
63
+ return coverage;
64
+ }
65
+ function judgeUndeclared(where, coverage) {
66
+ const covered = coverage.get(where);
67
+ if (!covered) {
68
+ return 'not-opted-in';
69
+ }
70
+ if (covered.listed === 'by-design') {
71
+ return 'open-by-design';
72
+ }
73
+ if (covered.listed === 'not-yet-declared') {
74
+ return 'open-not-yet-declared';
75
+ }
76
+ return 'unlisted';
77
+ }
78
+ function enforcementMode() {
79
+ return process.env.NODE_ENV === 'production' ? 'request' : 'boot';
80
+ }
81
+ //# sourceMappingURL=open-field-rule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-field-rule.js","sourceRoot":"","sources":["../../../server/service/privilege/open-field-rule.ts"],"names":[],"mappings":";;AAqEA,8CAQC;AAgBD,gCAgCC;AAaD,0CAgBC;AAcD,0CAEC;AA1KD,+CAAiD;AAEjD,6CAAgE;AA4DhE;;;;;;GAMG;AACH,SAAgB,iBAAiB;IAC/B,OAAO,CAAC,wBAAkB,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAY,EAAE,EAAE;QACrD,IAAI,CAAC;YACH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAA,YAAM,EAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;QAC/C,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;QAC/B,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,KAAK,GAAG;IACZ,CAAC,OAAO,EAAE,SAAS,CAAC;IACpB,CAAC,UAAU,EAAE,WAAW,CAAC;IACzB,CAAC,cAAc,EAAE,eAAe,CAAC;CACzB,CAAA;AAEV;;;;;;;GAOG;AACH,SAAgB,UAAU,CAAC,OAA0B;IACnD,MAAM,OAAO,GAAG,IAAA,iCAAkB,GAAS,CAAA;IAC3C,MAAM,QAAQ,GAAa,IAAI,GAAG,EAAE,CAAA;IAEpC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAA;QAE7C,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,SAAQ;QACV,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,IAAI,EAAE,CAAC,CAAA;QAC7D,MAAM,QAAQ,GAAG,WAAW,CAAC,QAAQ,IAAI,EAAE,CAAA;QAC3C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,cAAc,IAAI,EAAE,CAAC,CAAA;QAExD,KAAK,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACvC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;gBAC1C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC/B,SAAQ;gBACV,CAAC;gBAED,MAAM,KAAK,GAAG,GAAG,QAAQ,IAAI,KAAK,CAAC,UAAU,EAAE,CAAA;gBAE/C,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE;oBAClB,MAAM,EAAE,MAAM,CAAC,IAAI;oBACnB,MAAM,EAAE,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI;iBACxF,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC;AAaD,SAAgB,eAAe,CAAC,KAAa,EAAE,QAAkB;IAC/D,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IAEnC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,cAAc,CAAA;IACvB,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;QACnC,OAAO,gBAAgB,CAAA;IACzB,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,kBAAkB,EAAE,CAAC;QAC1C,OAAO,uBAAuB,CAAA;IAChC,CAAC;IAED,OAAO,UAAU,CAAA;AACnB,CAAC;AAcD,SAAgB,eAAe;IAC7B,OAAO,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAA;AACnE,CAAC","sourcesContent":["import { getMetadataStorage } from 'type-graphql'\n\nimport { loader, orderedModuleNames } from '@things-factory/env'\n\n/**\n * Which root fields are allowed to carry no `@privilege`, module by module\n * (`docs/design/auth-rebuild.md` §6-2 and §8 step 1; the question sheet of 2026-09-24, Q1 · Q2).\n *\n * ── The problem this answers ────────────────────────────────────────────────\n * `@privilege` is opt-in. A root field that declares nothing is open to anyone in the domain,\n * and it is recorded as open in exactly the way a field somebody chose to open is — no error, no\n * warning, nothing to grep for. On 2026-09-24 each product served about 300 of them, and new ones\n * kept arriving the same way; `deleteAppBinding`, which deleted any account in the installation,\n * went in like that.\n *\n * ── The declaration ─────────────────────────────────────────────────────────\n * A module states its undeclared fields on the `schema` object it already exports, beside its\n * `resolverClasses`, in two lists that mean different things:\n *\n * byDesign open on purpose, with the reason. `myRoles` answers about the caller;\n * `passwordRule` has to answer before the caller has any privilege at all.\n * These do not shrink.\n * notYetDeclared open because nobody has decided what they guard yet. Names only. This list\n * is meant to reach zero, and when it does the key goes with it.\n *\n * Mixing the two would turn the transition list into a permanent allow-list.\n *\n * ── Why a module opts in, rather than every field at once ───────────────────\n * The four products each serve resolvers of their own from another repository. Enforcing on every\n * field the day this lands would stop all four from booting in development. So a module that\n * declares `openFields` is held to it, and one that does not is left as it was — and counted\n * (`tests/open-fields-framework.test.ts`), so that \"not opted in\" is visible rather than silent.\n *\n * ── Why the data sits on `schema` and not behind a call into auth-base ──────\n * `shell` has resolvers and cannot depend on auth-base (auth-base depends on it), and five other\n * modules with resolvers do not depend on it either. The `schema` object every module already\n * exports needs no import, and it is what the server walks to build the schema in the first place.\n */\n\n/** What a module says about its undeclared root fields. Keys are `Type.field`, e.g. `Query.myRoles`. */\nexport type OpenFieldsDeclaration = {\n byDesign?: Record<string, string>\n notYetDeclared?: string[]\n}\n\n/** A module as the server sees it: a name, and the `schema` object its server entry exports. */\nexport type DeclaringModule = {\n name: string\n schema?: {\n resolverClasses?: Function[]\n openFields?: OpenFieldsDeclaration\n } | null\n}\n\n/** For one root field of an opted-in module: which module, and which list names it (if any). */\nexport type Covered = {\n module: string\n listed: 'by-design' | 'not-yet-declared' | null\n}\n\nexport type Coverage = Map<string, Covered>\n\n/**\n * The modules the running server loaded, in the order it loaded them.\n *\n * Under a test runner `orderedModuleNames` is empty, so this answers nothing and no field is\n * enforced — a test that wants enforcement passes its modules explicitly. A module whose entry\n * cannot be loaded here is skipped: the server itself would have failed on it before this point.\n */\nexport function modulesFromLoader(): DeclaringModule[] {\n return (orderedModuleNames || []).map((name: string) => {\n try {\n return { name, schema: loader(name)?.schema }\n } catch (e) {\n return { name, schema: null }\n }\n })\n}\n\nconst ROOTS = [\n ['Query', 'queries'],\n ['Mutation', 'mutations'],\n ['Subscription', 'subscriptions']\n] as const\n\n/**\n * The root fields each opted-in module's own resolver classes make, and what that module's lists\n * say about each.\n *\n * Attribution comes from type-graphql's metadata — the same record `buildSchema` reads — so a\n * field belongs to the module whose class declared it, not to whichever module happens to be\n * built alongside it.\n */\nexport function coverageOf(modules: DeclaringModule[]): Coverage {\n const storage = getMetadataStorage() as any\n const coverage: Coverage = new Map()\n\n for (const module of modules) {\n const declaration = module.schema?.openFields\n\n if (!declaration) {\n continue\n }\n\n const classes = new Set(module.schema?.resolverClasses || [])\n const byDesign = declaration.byDesign || {}\n const notYet = new Set(declaration.notYetDeclared || [])\n\n for (const [typeName, bucket] of ROOTS) {\n for (const field of storage[bucket] || []) {\n if (!classes.has(field.target)) {\n continue\n }\n\n const where = `${typeName}.${field.schemaName}`\n\n coverage.set(where, {\n module: module.name,\n listed: where in byDesign ? 'by-design' : notYet.has(where) ? 'not-yet-declared' : null\n })\n }\n }\n }\n\n return coverage\n}\n\n/**\n * What to do with a root field that carries no `@privilege`.\n *\n * open-by-design leave it open — its module says why\n * open-not-yet-declared leave it open — its module has it on the list to close\n * unlisted its module opted in and says nothing about it: this is the field\n * that arrived quietly, and the one this exists to stop\n * not-opted-in its module has not declared lists; unchanged from before\n */\nexport type UndeclaredVerdict = 'open-by-design' | 'open-not-yet-declared' | 'unlisted' | 'not-opted-in'\n\nexport function judgeUndeclared(where: string, coverage: Coverage): UndeclaredVerdict {\n const covered = coverage.get(where)\n\n if (!covered) {\n return 'not-opted-in'\n }\n\n if (covered.listed === 'by-design') {\n return 'open-by-design'\n }\n\n if (covered.listed === 'not-yet-declared') {\n return 'open-not-yet-declared'\n }\n\n return 'unlisted'\n}\n\n/**\n * Where an unlisted field is stopped (question sheet Q2).\n *\n * boot the schema refuses to build and names every such field at once. Development and\n * tests, so the person who added it sees it on their own screen before it ships\n * request the server builds, and that one field refuses every call and logs its name.\n * Production, so a line somebody forgot costs one field and not the whole product\n *\n * Neither ever opens anything. Wrong in the worst case means one field refuses.\n */\nexport type EnforcementMode = 'boot' | 'request'\n\nexport function enforcementMode(): EnforcementMode {\n return process.env.NODE_ENV === 'production' ? 'request' : 'boot'\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import { GraphQLSchema } from 'graphql';
2
+ import { DeclaringModule, EnforcementMode } from './open-field-rule.js';
2
3
  /** What `checkPermission` is asked at a field — the same four things the directive passes it. */
3
4
  export interface FieldGate {
4
5
  category?: string;
@@ -33,4 +34,11 @@ export declare function privilegeOfField(typeName: string, fieldName: string): {
33
34
  * 말하게** 한다.
34
35
  */
35
36
  export declare const privilegeDirectiveTypeDefs: import("graphql").DocumentNode;
36
- export declare const privilegeDirectiveResolver: (schema: GraphQLSchema) => GraphQLSchema;
37
+ /**
38
+ * The server calls this with the merged schema only. `modules` and `mode` are for tests, which
39
+ * have no module walk of their own (`orderedModuleNames` is empty under jest) — see `open-fields.ts`.
40
+ */
41
+ export declare const privilegeDirectiveResolver: (schema: GraphQLSchema, options?: {
42
+ modules?: DeclaringModule[];
43
+ mode?: EnforcementMode;
44
+ }) => GraphQLSchema;