@authup/server-core 1.0.0-beta.50 → 1.0.0-beta.51

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 (230) hide show
  1. package/dist/adapters/database/domains/client-permission/entity.d.ts +2 -1
  2. package/dist/adapters/database/domains/client-permission/entity.d.ts.map +1 -1
  3. package/dist/adapters/database/domains/client-permission/entity.mjs +6 -0
  4. package/dist/adapters/database/domains/client-permission/entity.mjs.map +1 -1
  5. package/dist/adapters/database/domains/robot-permission/entity.d.ts +2 -1
  6. package/dist/adapters/database/domains/robot-permission/entity.d.ts.map +1 -1
  7. package/dist/adapters/database/domains/robot-permission/entity.mjs +6 -0
  8. package/dist/adapters/database/domains/robot-permission/entity.mjs.map +1 -1
  9. package/dist/adapters/database/domains/robot-role/entity.mjs +1 -1
  10. package/dist/adapters/database/domains/robot-role/entity.mjs.map +1 -1
  11. package/dist/adapters/database/domains/role-permission/entity.d.ts +2 -1
  12. package/dist/adapters/database/domains/role-permission/entity.d.ts.map +1 -1
  13. package/dist/adapters/database/domains/role-permission/entity.mjs +6 -0
  14. package/dist/adapters/database/domains/role-permission/entity.mjs.map +1 -1
  15. package/dist/adapters/database/domains/user-permission/entity.d.ts +2 -1
  16. package/dist/adapters/database/domains/user-permission/entity.d.ts.map +1 -1
  17. package/dist/adapters/database/domains/user-permission/entity.mjs +6 -0
  18. package/dist/adapters/database/domains/user-permission/entity.mjs.map +1 -1
  19. package/dist/adapters/database/migrations/mysql/1779300000000-Default.d.ts +7 -0
  20. package/dist/adapters/database/migrations/mysql/1779300000000-Default.d.ts.map +1 -0
  21. package/dist/adapters/database/migrations/mysql/1779300000000-Default.mjs +85 -0
  22. package/dist/adapters/database/migrations/mysql/1779300000000-Default.mjs.map +1 -0
  23. package/dist/adapters/database/migrations/postgres/1779300000000-Default.d.ts +7 -0
  24. package/dist/adapters/database/migrations/postgres/1779300000000-Default.d.ts.map +1 -0
  25. package/dist/adapters/database/migrations/postgres/1779300000000-Default.mjs +87 -0
  26. package/dist/adapters/database/migrations/postgres/1779300000000-Default.mjs.map +1 -0
  27. package/dist/adapters/http/adapters/index.mjs +2 -2
  28. package/dist/adapters/http/adapters/oauth2/grant-types/index.mjs +2 -2
  29. package/dist/adapters/http/adapters/oauth2/grant-types/password.d.ts +2 -1
  30. package/dist/adapters/http/adapters/oauth2/grant-types/password.d.ts.map +1 -1
  31. package/dist/adapters/http/adapters/oauth2/grant-types/password.mjs +12 -4
  32. package/dist/adapters/http/adapters/oauth2/grant-types/password.mjs.map +1 -1
  33. package/dist/adapters/http/adapters/oauth2/grant-types/types.d.ts +2 -1
  34. package/dist/adapters/http/adapters/oauth2/grant-types/types.d.ts.map +1 -1
  35. package/dist/adapters/http/adapters/oauth2/grant-types/utils/credentials.d.ts +1 -0
  36. package/dist/adapters/http/adapters/oauth2/grant-types/utils/credentials.d.ts.map +1 -1
  37. package/dist/adapters/http/adapters/oauth2/grant-types/utils/credentials.mjs +1 -1
  38. package/dist/adapters/http/adapters/oauth2/grant-types/utils/credentials.mjs.map +1 -1
  39. package/dist/adapters/http/adapters/oauth2/grant-types/utils/index.mjs +2 -2
  40. package/dist/adapters/http/adapters/oauth2/index.mjs +2 -2
  41. package/dist/adapters/http/controllers/entities/identity-provider/module.d.ts.map +1 -1
  42. package/dist/adapters/http/controllers/entities/identity-provider/module.mjs +24 -9
  43. package/dist/adapters/http/controllers/entities/identity-provider/module.mjs.map +1 -1
  44. package/dist/adapters/http/controllers/workflows/token/module.d.ts.map +1 -1
  45. package/dist/adapters/http/controllers/workflows/token/module.mjs +2 -1
  46. package/dist/adapters/http/controllers/workflows/token/module.mjs.map +1 -1
  47. package/dist/adapters/http/controllers/workflows/token/types.d.ts +2 -1
  48. package/dist/adapters/http/controllers/workflows/token/types.d.ts.map +1 -1
  49. package/dist/adapters/http/index.mjs +3 -3
  50. package/dist/adapters/http/request/helpers/identity.d.ts +0 -1
  51. package/dist/adapters/http/request/helpers/identity.d.ts.map +1 -1
  52. package/dist/adapters/http/request/helpers/identity.mjs +2 -5
  53. package/dist/adapters/http/request/helpers/identity.mjs.map +1 -1
  54. package/dist/adapters/http/request/helpers/index.mjs +2 -2
  55. package/dist/adapters/http/request/index.mjs +2 -2
  56. package/dist/adapters/http/request/permission/module.mjs +2 -2
  57. package/dist/adapters/http/request/permission/module.mjs.map +1 -1
  58. package/dist/app/modules/database/repositories/bindings.d.ts +2 -0
  59. package/dist/app/modules/database/repositories/bindings.d.ts.map +1 -1
  60. package/dist/app/modules/database/repositories/bindings.mjs +3 -1
  61. package/dist/app/modules/database/repositories/bindings.mjs.map +1 -1
  62. package/dist/app/modules/database/repositories/identity-provider/repository.d.ts.map +1 -1
  63. package/dist/app/modules/database/repositories/identity-provider/repository.mjs +4 -2
  64. package/dist/app/modules/database/repositories/identity-provider/repository.mjs.map +1 -1
  65. package/dist/app/modules/database/repositories/permission-provider/module.d.ts +2 -2
  66. package/dist/app/modules/database/repositories/permission-provider/module.d.ts.map +1 -1
  67. package/dist/app/modules/database/repositories/permission-provider/module.mjs +4 -3
  68. package/dist/app/modules/database/repositories/permission-provider/module.mjs.map +1 -1
  69. package/dist/app/modules/database/repositories/realm/repository.d.ts.map +1 -1
  70. package/dist/app/modules/database/repositories/realm/repository.mjs +6 -2
  71. package/dist/app/modules/database/repositories/realm/repository.mjs.map +1 -1
  72. package/dist/app/modules/http/modules/controller.d.ts +2 -0
  73. package/dist/app/modules/http/modules/controller.d.ts.map +1 -1
  74. package/dist/app/modules/http/modules/controller.mjs +18 -0
  75. package/dist/app/modules/http/modules/controller.mjs.map +1 -1
  76. package/dist/app/modules/identity/repositories/client.mjs +2 -2
  77. package/dist/app/modules/identity/repositories/client.mjs.map +1 -1
  78. package/dist/app/modules/identity/repositories/robot.mjs +2 -2
  79. package/dist/app/modules/identity/repositories/robot.mjs.map +1 -1
  80. package/dist/app/modules/identity/repositories/user.mjs +2 -2
  81. package/dist/app/modules/identity/repositories/user.mjs.map +1 -1
  82. package/dist/app/modules/oauth2/repositories/client/repository.mjs +2 -2
  83. package/dist/app/modules/oauth2/repositories/client/repository.mjs.map +1 -1
  84. package/dist/app/modules/provisioning/module.d.ts.map +1 -1
  85. package/dist/app/modules/provisioning/module.mjs +0 -1
  86. package/dist/app/modules/provisioning/module.mjs.map +1 -1
  87. package/dist/app/modules/provisioning/sources/default/module.d.ts.map +1 -1
  88. package/dist/app/modules/provisioning/sources/default/module.mjs +14 -56
  89. package/dist/app/modules/provisioning/sources/default/module.mjs.map +1 -1
  90. package/dist/core/entities/client/service.mjs +25 -7
  91. package/dist/core/entities/client/service.mjs.map +1 -1
  92. package/dist/core/entities/client-permission/service.d.ts +7 -4
  93. package/dist/core/entities/client-permission/service.d.ts.map +1 -1
  94. package/dist/core/entities/client-permission/service.mjs +59 -19
  95. package/dist/core/entities/client-permission/service.mjs.map +1 -1
  96. package/dist/core/entities/client-role/service.d.ts +3 -2
  97. package/dist/core/entities/client-role/service.d.ts.map +1 -1
  98. package/dist/core/entities/client-role/service.mjs +12 -5
  99. package/dist/core/entities/client-role/service.mjs.map +1 -1
  100. package/dist/core/entities/client-scope/service.d.ts +3 -2
  101. package/dist/core/entities/client-scope/service.d.ts.map +1 -1
  102. package/dist/core/entities/client-scope/service.mjs +12 -5
  103. package/dist/core/entities/client-scope/service.mjs.map +1 -1
  104. package/dist/core/entities/identity-provider-role-mapping/service.d.ts +7 -4
  105. package/dist/core/entities/identity-provider-role-mapping/service.d.ts.map +1 -1
  106. package/dist/core/entities/identity-provider-role-mapping/service.mjs +29 -6
  107. package/dist/core/entities/identity-provider-role-mapping/service.mjs.map +1 -1
  108. package/dist/core/entities/permission/service.d.ts +3 -5
  109. package/dist/core/entities/permission/service.d.ts.map +1 -1
  110. package/dist/core/entities/permission/service.mjs +30 -24
  111. package/dist/core/entities/permission/service.mjs.map +1 -1
  112. package/dist/core/entities/permission-policy/service.d.ts +3 -2
  113. package/dist/core/entities/permission-policy/service.d.ts.map +1 -1
  114. package/dist/core/entities/permission-policy/service.mjs +12 -5
  115. package/dist/core/entities/permission-policy/service.mjs.map +1 -1
  116. package/dist/core/entities/policy/service.d.ts.map +1 -1
  117. package/dist/core/entities/policy/service.mjs +16 -7
  118. package/dist/core/entities/policy/service.mjs.map +1 -1
  119. package/dist/core/entities/realm/service.mjs +4 -4
  120. package/dist/core/entities/realm/service.mjs.map +1 -1
  121. package/dist/core/entities/realm/types.d.ts +4 -0
  122. package/dist/core/entities/realm/types.d.ts.map +1 -1
  123. package/dist/core/entities/robot/service.d.ts.map +1 -1
  124. package/dist/core/entities/robot/service.mjs +29 -11
  125. package/dist/core/entities/robot/service.mjs.map +1 -1
  126. package/dist/core/entities/robot-permission/service.d.ts +7 -4
  127. package/dist/core/entities/robot-permission/service.d.ts.map +1 -1
  128. package/dist/core/entities/robot-permission/service.mjs +59 -19
  129. package/dist/core/entities/robot-permission/service.mjs.map +1 -1
  130. package/dist/core/entities/robot-role/service.d.ts +3 -2
  131. package/dist/core/entities/robot-role/service.d.ts.map +1 -1
  132. package/dist/core/entities/robot-role/service.mjs +12 -5
  133. package/dist/core/entities/robot-role/service.mjs.map +1 -1
  134. package/dist/core/entities/role/service.d.ts.map +1 -1
  135. package/dist/core/entities/role/service.mjs +16 -7
  136. package/dist/core/entities/role/service.mjs.map +1 -1
  137. package/dist/core/entities/role-attribute/service.d.ts.map +1 -1
  138. package/dist/core/entities/role-attribute/service.mjs +24 -6
  139. package/dist/core/entities/role-attribute/service.mjs.map +1 -1
  140. package/dist/core/entities/role-permission/service.d.ts +7 -4
  141. package/dist/core/entities/role-permission/service.d.ts.map +1 -1
  142. package/dist/core/entities/role-permission/service.mjs +61 -21
  143. package/dist/core/entities/role-permission/service.mjs.map +1 -1
  144. package/dist/core/entities/scope/service.d.ts.map +1 -1
  145. package/dist/core/entities/scope/service.mjs +16 -7
  146. package/dist/core/entities/scope/service.mjs.map +1 -1
  147. package/dist/core/entities/user/service.d.ts.map +1 -1
  148. package/dist/core/entities/user/service.mjs +28 -10
  149. package/dist/core/entities/user/service.mjs.map +1 -1
  150. package/dist/core/entities/user-attribute/service.d.ts.map +1 -1
  151. package/dist/core/entities/user-attribute/service.mjs +18 -6
  152. package/dist/core/entities/user-attribute/service.mjs.map +1 -1
  153. package/dist/core/entities/user-permission/service.d.ts +7 -4
  154. package/dist/core/entities/user-permission/service.d.ts.map +1 -1
  155. package/dist/core/entities/user-permission/service.mjs +59 -19
  156. package/dist/core/entities/user-permission/service.mjs.map +1 -1
  157. package/dist/core/entities/user-role/service.d.ts +3 -2
  158. package/dist/core/entities/user-role/service.d.ts.map +1 -1
  159. package/dist/core/entities/user-role/service.mjs +12 -5
  160. package/dist/core/entities/user-role/service.mjs.map +1 -1
  161. package/dist/core/identity/index.mjs +2 -1
  162. package/dist/core/identity/permission/checker/service.d.ts.map +1 -1
  163. package/dist/core/identity/permission/checker/service.mjs +6 -4
  164. package/dist/core/identity/permission/checker/service.mjs.map +1 -1
  165. package/dist/core/identity/permission/index.d.ts +1 -0
  166. package/dist/core/identity/permission/index.d.ts.map +1 -1
  167. package/dist/core/identity/permission/index.mjs +2 -1
  168. package/dist/core/identity/permission/junction-grant.d.ts +49 -0
  169. package/dist/core/identity/permission/junction-grant.d.ts.map +1 -0
  170. package/dist/core/identity/permission/junction-grant.mjs +57 -0
  171. package/dist/core/identity/permission/junction-grant.mjs.map +1 -0
  172. package/dist/core/identity/permission/module.d.ts +2 -3
  173. package/dist/core/identity/permission/module.d.ts.map +1 -1
  174. package/dist/core/identity/permission/module.mjs +52 -14
  175. package/dist/core/identity/permission/module.mjs.map +1 -1
  176. package/dist/core/identity/permission/types.d.ts +22 -2
  177. package/dist/core/identity/permission/types.d.ts.map +1 -1
  178. package/dist/core/index.mjs +2 -1
  179. package/dist/core/provisioning/entities/role/types.d.ts +8 -5
  180. package/dist/core/provisioning/entities/role/types.d.ts.map +1 -1
  181. package/dist/core/provisioning/synchronizer/base.d.ts +3 -0
  182. package/dist/core/provisioning/synchronizer/base.d.ts.map +1 -1
  183. package/dist/core/provisioning/synchronizer/base.mjs +3 -0
  184. package/dist/core/provisioning/synchronizer/base.mjs.map +1 -1
  185. package/dist/core/provisioning/synchronizer/client/module.d.ts.map +1 -1
  186. package/dist/core/provisioning/synchronizer/client/module.mjs +1 -0
  187. package/dist/core/provisioning/synchronizer/client/module.mjs.map +1 -1
  188. package/dist/core/provisioning/synchronizer/entity-resolver.d.ts.map +1 -1
  189. package/dist/core/provisioning/synchronizer/entity-resolver.mjs +3 -0
  190. package/dist/core/provisioning/synchronizer/entity-resolver.mjs.map +1 -1
  191. package/dist/core/provisioning/synchronizer/index.mjs +1 -0
  192. package/dist/core/provisioning/synchronizer/permission/module.d.ts.map +1 -1
  193. package/dist/core/provisioning/synchronizer/permission/module.mjs +2 -1
  194. package/dist/core/provisioning/synchronizer/permission/module.mjs.map +1 -1
  195. package/dist/core/provisioning/synchronizer/policy/module.d.ts +2 -0
  196. package/dist/core/provisioning/synchronizer/policy/module.d.ts.map +1 -1
  197. package/dist/core/provisioning/synchronizer/policy/module.mjs +19 -1
  198. package/dist/core/provisioning/synchronizer/policy/module.mjs.map +1 -1
  199. package/dist/core/provisioning/synchronizer/realm/module.d.ts.map +1 -1
  200. package/dist/core/provisioning/synchronizer/realm/module.mjs +1 -0
  201. package/dist/core/provisioning/synchronizer/realm/module.mjs.map +1 -1
  202. package/dist/core/provisioning/synchronizer/robot/module.d.ts.map +1 -1
  203. package/dist/core/provisioning/synchronizer/robot/module.mjs +1 -0
  204. package/dist/core/provisioning/synchronizer/robot/module.mjs.map +1 -1
  205. package/dist/core/provisioning/synchronizer/role/module.d.ts +2 -3
  206. package/dist/core/provisioning/synchronizer/role/module.d.ts.map +1 -1
  207. package/dist/core/provisioning/synchronizer/role/module.mjs +14 -20
  208. package/dist/core/provisioning/synchronizer/role/module.mjs.map +1 -1
  209. package/dist/core/provisioning/synchronizer/role/types.d.ts +1 -2
  210. package/dist/core/provisioning/synchronizer/role/types.d.ts.map +1 -1
  211. package/dist/core/provisioning/synchronizer/scope/module.d.ts.map +1 -1
  212. package/dist/core/provisioning/synchronizer/scope/module.mjs +1 -0
  213. package/dist/core/provisioning/synchronizer/scope/module.mjs.map +1 -1
  214. package/dist/core/provisioning/synchronizer/user/module.d.ts.map +1 -1
  215. package/dist/core/provisioning/synchronizer/user/module.mjs +1 -0
  216. package/dist/core/provisioning/synchronizer/user/module.mjs.map +1 -1
  217. package/dist/core/security/policy/evaluator.d.ts +4 -4
  218. package/dist/core/security/policy/evaluator.d.ts.map +1 -1
  219. package/dist/core/security/policy/evaluator.mjs +19 -18
  220. package/dist/core/security/policy/evaluator.mjs.map +1 -1
  221. package/dist/index.mjs +3 -3
  222. package/dist/swagger.json +54 -0
  223. package/dist/ui/client/.vite/ssr-manifest.json +1017 -639
  224. package/dist/ui/client/assets/index-Cd3uWit-.js +127 -0
  225. package/dist/ui/client/assets/index-DoCTwJ8M.css +2 -0
  226. package/dist/ui/client/index.html +2 -2
  227. package/dist/ui/server/server.js +11086 -2362
  228. package/package.json +37 -35
  229. package/dist/ui/client/assets/index-BFXs98mu.js +0 -102
  230. package/dist/ui/client/assets/index-C_-HM1P-.css +0 -2
@@ -0,0 +1,49 @@
1
+ import { RealmScope } from '@authup/access';
2
+ import type { ResolveJunctionGrantResult } from './types.ts';
3
+ /**
4
+ * The privilege-escalation boundary for permission-binding junctions, shared verbatim by
5
+ * role/user/client/robot-permission. Kept in ONE place (rather than copied into each service)
6
+ * so the fail-closed cap/inherit rule is audited and changed once — see #3160 / #3158 / #3159,
7
+ * which each had to patch this rule across all four services.
8
+ *
9
+ * It depends on a server-core type (`ResolveJunctionGrantResult`) and so cannot live on the
10
+ * generic `JunctionEntityService` in `@authup/server-kit` (that would invert the layering).
11
+ */
12
+ /**
13
+ * Cap a junction CREATE to the actor's request-relative selected grant and inherit its policy.
14
+ *
15
+ * - `realm_scope` is capped to the actor's selected reach (`min(requested, grant.realmScope)`).
16
+ * - The policy follows the grant: only a genuinely unrestricted actor (an UNCAPPED `any` reach
17
+ * AND no policy) may keep an explicitly-requested `policy_id`; any restricted/policy-bound
18
+ * actor inherits its own grant's policy (it can neither attach an unowned policy nor detach to
19
+ * widen). The check reads `grant.realmScope` UNCAPPED precisely to distinguish that case — the
20
+ * grant is returned uncapped by `resolveJunctionGrant` for exactly this reason.
21
+ *
22
+ * Mutates `validated` in place (mirrors the surrounding service style).
23
+ */
24
+ export declare function applyJunctionCreateGrant(validated: Record<string, any>, grant: ResolveJunctionGrantResult): void;
25
+ export type BuildJunctionUpdateDataInput = {
26
+ /** The validated update payload (UPDATE group) — presence of a key signals caller intent. */
27
+ data: Record<string, any>;
28
+ /** The persisted junction's current reach (used to re-cap a policy-only edit). */
29
+ existingScope: `${RealmScope}`;
30
+ /** The actor's UNCAPPED selected reach for this permission (`any` for system context). */
31
+ actorScope: `${RealmScope}`;
32
+ /** Whether the actor's selected grant carries no policy. */
33
+ actorPolicyFree: boolean;
34
+ /** The id of the actor's selected grant policy (null when policy-free). */
35
+ actorPolicyId: string | null;
36
+ };
37
+ /**
38
+ * Build the `{ realm_scope?, policy_id? }` delta for a junction UPDATE, capped to the actor's
39
+ * request-relative grant.
40
+ *
41
+ * - An unrestricted (`any`, policy-free) actor may set/clear `policy_id` explicitly and is never
42
+ * narrowed.
43
+ * - A restricted/policy-bound actor that touches the binding inherits its own grant's policy AND
44
+ * has the reach re-capped — including the EXISTING `realm_scope` when the update omits it, so a
45
+ * policy-only edit cannot leave a wider pre-existing reach standing (fail-OPEN otherwise: the
46
+ * persisted binding must always be dominated by a grant the actor really holds — #3160).
47
+ */
48
+ export declare function buildJunctionUpdateData(input: BuildJunctionUpdateDataInput): Record<string, any>;
49
+ //# sourceMappingURL=junction-grant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"junction-grant.d.ts","sourceRoot":"","sources":["../../../../src/core/identity/permission/junction-grant.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,UAAU,EAAiB,MAAM,gBAAgB,CAAC;AAE3D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAE7D;;;;;;;;GAQG;AAEH;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CACpC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9B,KAAK,EAAE,0BAA0B,GAClC,IAAI,CAMN;AAED,MAAM,MAAM,4BAA4B,GAAG;IACvC,6FAA6F;IAC7F,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC1B,kFAAkF;IAClF,aAAa,EAAE,GAAG,UAAU,EAAE,CAAC;IAC/B,0FAA0F;IAC1F,UAAU,EAAE,GAAG,UAAU,EAAE,CAAC;IAC5B,4DAA4D;IAC5D,eAAe,EAAE,OAAO,CAAC;IACzB,2EAA2E;IAC3E,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,4BAA4B,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAkChG"}
@@ -0,0 +1,57 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { hasOwnProperty } from "@authup/kit";
5
+ import { RealmScope, minRealmScope } from "@authup/access";
6
+ //#region src/core/identity/permission/junction-grant.ts
7
+ /**
8
+ * The privilege-escalation boundary for permission-binding junctions, shared verbatim by
9
+ * role/user/client/robot-permission. Kept in ONE place (rather than copied into each service)
10
+ * so the fail-closed cap/inherit rule is audited and changed once — see #3160 / #3158 / #3159,
11
+ * which each had to patch this rule across all four services.
12
+ *
13
+ * It depends on a server-core type (`ResolveJunctionGrantResult`) and so cannot live on the
14
+ * generic `JunctionEntityService` in `@authup/server-kit` (that would invert the layering).
15
+ */ /**
16
+ * Cap a junction CREATE to the actor's request-relative selected grant and inherit its policy.
17
+ *
18
+ * - `realm_scope` is capped to the actor's selected reach (`min(requested, grant.realmScope)`).
19
+ * - The policy follows the grant: only a genuinely unrestricted actor (an UNCAPPED `any` reach
20
+ * AND no policy) may keep an explicitly-requested `policy_id`; any restricted/policy-bound
21
+ * actor inherits its own grant's policy (it can neither attach an unowned policy nor detach to
22
+ * widen). The check reads `grant.realmScope` UNCAPPED precisely to distinguish that case — the
23
+ * grant is returned uncapped by `resolveJunctionGrant` for exactly this reason.
24
+ *
25
+ * Mutates `validated` in place (mirrors the surrounding service style).
26
+ */ function applyJunctionCreateGrant(validated, grant) {
27
+ validated.realm_scope = minRealmScope([validated.realm_scope ?? RealmScope.OWN, grant.realmScope]);
28
+ if (grant.realmScope !== RealmScope.ANY || grant.policy) validated.policy_id = grant.policy ? grant.policy.id : null;
29
+ }
30
+ /**
31
+ * Build the `{ realm_scope?, policy_id? }` delta for a junction UPDATE, capped to the actor's
32
+ * request-relative grant.
33
+ *
34
+ * - An unrestricted (`any`, policy-free) actor may set/clear `policy_id` explicitly and is never
35
+ * narrowed.
36
+ * - A restricted/policy-bound actor that touches the binding inherits its own grant's policy AND
37
+ * has the reach re-capped — including the EXISTING `realm_scope` when the update omits it, so a
38
+ * policy-only edit cannot leave a wider pre-existing reach standing (fail-OPEN otherwise: the
39
+ * persisted binding must always be dominated by a grant the actor really holds — #3160).
40
+ */ function buildJunctionUpdateData(input) {
41
+ const { data, existingScope, actorScope, actorPolicyFree, actorPolicyId } = input;
42
+ const updateData = {};
43
+ const touchesScope = hasOwnProperty(data, "realm_scope");
44
+ const touchesPolicy = hasOwnProperty(data, "policy_id");
45
+ if (touchesScope) updateData.realm_scope = minRealmScope([data.realm_scope, actorScope]);
46
+ if (actorScope === RealmScope.ANY && actorPolicyFree) {
47
+ if (touchesPolicy) updateData.policy_id = data.policy_id;
48
+ } else if (touchesScope || touchesPolicy) {
49
+ updateData.policy_id = actorPolicyId;
50
+ if (!touchesScope) updateData.realm_scope = minRealmScope([existingScope, actorScope]);
51
+ }
52
+ return updateData;
53
+ }
54
+ //#endregion
55
+ export { applyJunctionCreateGrant, buildJunctionUpdateData };
56
+
57
+ //# sourceMappingURL=junction-grant.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"junction-grant.mjs","names":["RealmScope","minRealmScope","hasOwnProperty","applyJunctionCreateGrant","validated","grant","realm_scope","OWN","realmScope","ANY","policy","policy_id","id","buildJunctionUpdateData","input","data","existingScope","actorScope","actorPolicyFree","actorPolicyId","updateData","touchesScope","touchesPolicy"],"sources":["../../../../src/core/identity/permission/junction-grant.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { RealmScope, minRealmScope } from '@authup/access';\nimport { hasOwnProperty } from '@authup/kit';\nimport type { ResolveJunctionGrantResult } from './types.ts';\n\n/**\n * The privilege-escalation boundary for permission-binding junctions, shared verbatim by\n * role/user/client/robot-permission. Kept in ONE place (rather than copied into each service)\n * so the fail-closed cap/inherit rule is audited and changed once — see #3160 / #3158 / #3159,\n * which each had to patch this rule across all four services.\n *\n * It depends on a server-core type (`ResolveJunctionGrantResult`) and so cannot live on the\n * generic `JunctionEntityService` in `@authup/server-kit` (that would invert the layering).\n */\n\n/**\n * Cap a junction CREATE to the actor's request-relative selected grant and inherit its policy.\n *\n * - `realm_scope` is capped to the actor's selected reach (`min(requested, grant.realmScope)`).\n * - The policy follows the grant: only a genuinely unrestricted actor (an UNCAPPED `any` reach\n * AND no policy) may keep an explicitly-requested `policy_id`; any restricted/policy-bound\n * actor inherits its own grant's policy (it can neither attach an unowned policy nor detach to\n * widen). The check reads `grant.realmScope` UNCAPPED precisely to distinguish that case — the\n * grant is returned uncapped by `resolveJunctionGrant` for exactly this reason.\n *\n * Mutates `validated` in place (mirrors the surrounding service style).\n */\nexport function applyJunctionCreateGrant(\n validated: Record<string, any>,\n grant: ResolveJunctionGrantResult,\n): void {\n validated.realm_scope = minRealmScope([validated.realm_scope ?? RealmScope.OWN, grant.realmScope]);\n\n if (grant.realmScope !== RealmScope.ANY || grant.policy) {\n validated.policy_id = grant.policy ? grant.policy.id : null;\n }\n}\n\nexport type BuildJunctionUpdateDataInput = {\n /** The validated update payload (UPDATE group) — presence of a key signals caller intent. */\n data: Record<string, any>,\n /** The persisted junction's current reach (used to re-cap a policy-only edit). */\n existingScope: `${RealmScope}`,\n /** The actor's UNCAPPED selected reach for this permission (`any` for system context). */\n actorScope: `${RealmScope}`,\n /** Whether the actor's selected grant carries no policy. */\n actorPolicyFree: boolean,\n /** The id of the actor's selected grant policy (null when policy-free). */\n actorPolicyId: string | null,\n};\n\n/**\n * Build the `{ realm_scope?, policy_id? }` delta for a junction UPDATE, capped to the actor's\n * request-relative grant.\n *\n * - An unrestricted (`any`, policy-free) actor may set/clear `policy_id` explicitly and is never\n * narrowed.\n * - A restricted/policy-bound actor that touches the binding inherits its own grant's policy AND\n * has the reach re-capped — including the EXISTING `realm_scope` when the update omits it, so a\n * policy-only edit cannot leave a wider pre-existing reach standing (fail-OPEN otherwise: the\n * persisted binding must always be dominated by a grant the actor really holds — #3160).\n */\nexport function buildJunctionUpdateData(input: BuildJunctionUpdateDataInput): Record<string, any> {\n const {\n data, \n existingScope, \n actorScope, \n actorPolicyFree, \n actorPolicyId,\n } = input;\n\n const updateData: Record<string, any> = {};\n\n const touchesScope = hasOwnProperty(data, 'realm_scope');\n const touchesPolicy = hasOwnProperty(data, 'policy_id');\n\n // CAP to the actor's ceiling — a restricted actor may narrow but never widen.\n // (`data` is the validated UPDATE payload, so realm_scope is already a valid enum value;\n // minRealmScope additionally normalizes any out-of-band input fail-closed to `own`.)\n if (touchesScope) {\n updateData.realm_scope = minRealmScope([data.realm_scope as `${RealmScope}`, actorScope]);\n }\n\n if (actorScope === RealmScope.ANY && actorPolicyFree) {\n if (touchesPolicy) {\n updateData.policy_id = data.policy_id;\n }\n } else if (touchesScope || touchesPolicy) {\n updateData.policy_id = actorPolicyId;\n\n if (!touchesScope) {\n updateData.realm_scope = minRealmScope([existingScope, actorScope]);\n }\n }\n\n return updateData;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;GAiCA,SAAgBG,yBACZC,WACAC,OAAiC;CAEjCD,UAAUE,cAAcL,cAAc,CAACG,UAAUE,eAAeN,WAAWO,KAAKF,MAAMG,UAAAA,CAAW;CAEjG,IAAIH,MAAMG,eAAeR,WAAWS,OAAOJ,MAAMK,QAC7CN,UAAUO,YAAYN,MAAMK,SAASL,MAAMK,OAAOE,KAAK;AAE/D;;;;;;;;;;;GA0BA,SAAgBC,wBAAwBC,OAAmC;CACvE,MAAM,EACFC,MACAC,eACAC,YACAC,iBACAC,kBACAL;CAEJ,MAAMM,aAAkC,CAAC;CAEzC,MAAMC,eAAenB,eAAea,MAAM,aAAA;CAC1C,MAAMO,gBAAgBpB,eAAea,MAAM,WAAA;CAK3C,IAAIM,cACAD,WAAWd,cAAcL,cAAc,CAACc,KAAKT,aAAgCW,UAAAA,CAAW;CAG5F,IAAIA,eAAejB,WAAWS,OAAOS;MAC7BI,eACAF,WAAWT,YAAYI,KAAKJ;CAAAA,OAE7B,IAAIU,gBAAgBC,eAAe;EACtCF,WAAWT,YAAYQ;EAEvB,IAAI,CAACE,cACDD,WAAWd,cAAcL,cAAc,CAACe,eAAeC,UAAAA,CAAW;CAE1E;CAEA,OAAOG;AACX"}
@@ -1,11 +1,10 @@
1
1
  import type { IdentityPolicyData, PermissionPolicyBinding } from '@authup/access';
2
- import type { Policy } from '@authup/core-kit';
3
2
  import type { IClientRepository } from '../../entities/client/types.ts';
4
3
  import type { IRobotRepository } from '../../entities/robot/types.ts';
5
4
  import type { IRoleRepository } from '../../entities/role/types.ts';
6
5
  import type { IUserRepository } from '../../entities/user/types.ts';
7
6
  import type { IIdentityRoleProvider } from '../role/types.ts';
8
- import type { IIdentityPermissionProvider, IdentityPermissionProviderContext, ResolveJunctionPolicyOptions } from './types.ts';
7
+ import type { IIdentityPermissionProvider, IdentityPermissionProviderContext, ResolveJunctionGrantResult, ResolveJunctionPolicyOptions } from './types.ts';
9
8
  export declare class IdentityPermissionProvider implements IIdentityPermissionProvider {
10
9
  protected clientRepository: IClientRepository;
11
10
  protected userRepository: IUserRepository;
@@ -14,7 +13,7 @@ export declare class IdentityPermissionProvider implements IIdentityPermissionPr
14
13
  protected roleProvider: IIdentityRoleProvider;
15
14
  constructor(ctx: IdentityPermissionProviderContext);
16
15
  isSuperset(parent: IdentityPolicyData, child: IdentityPolicyData): Promise<boolean>;
17
- resolveJunctionPolicy(identity: IdentityPolicyData, options: ResolveJunctionPolicyOptions): Promise<Policy | undefined>;
16
+ resolveJunctionGrant(identity: IdentityPolicyData, options: ResolveJunctionPolicyOptions): Promise<ResolveJunctionGrantResult>;
18
17
  getFor(identity: IdentityPolicyData): Promise<PermissionPolicyBinding[]>;
19
18
  getForClient(identity: IdentityPolicyData): Promise<PermissionPolicyBinding[]>;
20
19
  getForUser(identity: IdentityPolicyData): Promise<PermissionPolicyBinding[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../src/core/identity/permission/module.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACR,kBAAkB,EAClB,uBAAuB,EAC1B,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,KAAK,EACR,2BAA2B,EAC3B,iCAAiC,EACjC,4BAA4B,EAC/B,MAAM,YAAY,CAAC;AAEpB,qBAAa,0BAA2B,YAAW,2BAA2B;IAC1E,SAAS,CAAC,gBAAgB,EAAE,iBAAiB,CAAC;IAE9C,SAAS,CAAC,cAAc,EAAE,eAAe,CAAC;IAE1C,SAAS,CAAC,cAAc,EAAE,eAAe,CAAC;IAE1C,SAAS,CAAC,eAAe,EAAE,gBAAgB,CAAC;IAE5C,SAAS,CAAC,YAAY,EAAE,qBAAqB,CAAC;gBAElC,GAAG,EAAE,iCAAiC;IAQ5C,UAAU,CAAC,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,kBAAkB,GAAI,OAAO,CAAC,OAAO,CAAC;IA2BpF,qBAAqB,CACvB,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,4BAA4B,GACtC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAsCxB,MAAM,CAAC,QAAQ,EAAE,kBAAkB,GAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAmBzE,YAAY,CAAC,QAAQ,EAAE,kBAAkB,GAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAO/E,UAAU,CAAC,QAAQ,EAAE,kBAAkB,GAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAQ7E,WAAW,CAAC,QAAQ,EAAE,kBAAkB,GAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAQ9E,UAAU,CAAC,QAAQ,EAAE,kBAAkB,GAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;YAKrE,uBAAuB;IAmBrC,OAAO,CAAC,8BAA8B;CAUzC"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../src/core/identity/permission/module.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACR,kBAAkB,EAElB,uBAAuB,EAC1B,MAAM,gBAAgB,CAAC;AAWxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,KAAK,EACR,2BAA2B,EAC3B,iCAAiC,EACjC,0BAA0B,EAC1B,4BAA4B,EAC/B,MAAM,YAAY,CAAC;AAEpB,qBAAa,0BAA2B,YAAW,2BAA2B;IAC1E,SAAS,CAAC,gBAAgB,EAAE,iBAAiB,CAAC;IAE9C,SAAS,CAAC,cAAc,EAAE,eAAe,CAAC;IAE1C,SAAS,CAAC,cAAc,EAAE,eAAe,CAAC;IAE1C,SAAS,CAAC,eAAe,EAAE,gBAAgB,CAAC;IAE5C,SAAS,CAAC,YAAY,EAAE,qBAAqB,CAAC;gBAElC,GAAG,EAAE,iCAAiC;IAQ5C,UAAU,CAAC,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,kBAAkB,GAAI,OAAO,CAAC,OAAO,CAAC;IAgCpF,oBAAoB,CACtB,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,4BAA4B,GACtC,OAAO,CAAC,0BAA0B,CAAC;IA6DhC,MAAM,CAAC,QAAQ,EAAE,kBAAkB,GAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAmBzE,YAAY,CAAC,QAAQ,EAAE,kBAAkB,GAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAO/E,UAAU,CAAC,QAAQ,EAAE,kBAAkB,GAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAQ7E,WAAW,CAAC,QAAQ,EAAE,kBAAkB,GAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAQ9E,UAAU,CAAC,QAAQ,EAAE,kBAAkB,GAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;YAKrE,uBAAuB;IAmBrC,OAAO,CAAC,8BAA8B;CAUzC"}
@@ -2,7 +2,7 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { isPolicy } from "@authup/core-kit";
5
- import { isPermissionPolicyBindingEqual, mergePermissionPolicyBindings } from "@authup/access";
5
+ import { RealmScope, aggregatePermissionPolicyBindings, compareRealmScope, grantDominates, isPermissionPolicyBindingEqual, minRealmScope } from "@authup/access";
6
6
  //#region src/core/identity/permission/module.ts
7
7
  var IdentityPermissionProvider = class {
8
8
  clientRepository;
@@ -18,18 +18,16 @@ var IdentityPermissionProvider = class {
18
18
  this.roleProvider = ctx.roleProvider;
19
19
  }
20
20
  async isSuperset(parent, child) {
21
- const parentBindings = await this.getFor(parent);
22
- const childBindings = await this.getFor(child);
23
- const parentMerged = mergePermissionPolicyBindings(parentBindings);
24
- const childMerged = mergePermissionPolicyBindings(childBindings);
25
- for (const childItem of childMerged) {
26
- const parentItem = parentMerged.find((p) => isPermissionPolicyBindingEqual(p, childItem));
21
+ const parentAggregated = aggregatePermissionPolicyBindings(await this.getFor(parent));
22
+ const childAggregated = aggregatePermissionPolicyBindings(await this.getFor(child));
23
+ for (const childItem of childAggregated) {
24
+ const parentItem = parentAggregated.find((p) => isPermissionPolicyBindingEqual(p, childItem));
27
25
  if (!parentItem) return false;
28
- if (parentItem.policies && parentItem.policies.length > 0 && (!childItem.policies || childItem.policies.length === 0)) return false;
26
+ for (const childGrant of childItem.grants) if (!parentItem.grants.some((parentGrant) => grantDominates(parentGrant, childGrant))) return false;
29
27
  }
30
28
  return true;
31
29
  }
32
- async resolveJunctionPolicy(identity, options) {
30
+ async resolveJunctionGrant(identity, options) {
33
31
  const matching = (await this.getFor(identity)).filter((b) => {
34
32
  if (b.permission.name !== options.name) return false;
35
33
  if (typeof options.realmId !== "undefined") {
@@ -40,12 +38,19 @@ var IdentityPermissionProvider = class {
40
38
  }
41
39
  return true;
42
40
  });
43
- if (matching.length === 0) return;
44
- const merged = mergePermissionPolicyBindings(matching);
45
- if (merged.length > 0 && merged[0].policies && merged[0].policies.length > 0) {
46
- const policy = merged[0].policies[0];
47
- if (isPolicy(policy)) return policy;
41
+ if (matching.length === 0) return { realmScope: RealmScope.OWN };
42
+ const grants = aggregatePermissionPolicyBindings(matching).flatMap((item) => item.grants);
43
+ if (grants.length === 0) return { realmScope: RealmScope.OWN };
44
+ const selected = selectGrantForRequest(grants, options.realmScope ?? RealmScope.OWN);
45
+ let policy;
46
+ if (selected.policy) {
47
+ if (!isPolicy(selected.policy)) return { realmScope: RealmScope.NONE };
48
+ policy = selected.policy;
48
49
  }
50
+ return {
51
+ policy,
52
+ realmScope: selected.realm_scope
53
+ };
49
54
  }
50
55
  async getFor(identity) {
51
56
  switch (identity.type) {
@@ -79,6 +84,39 @@ var IdentityPermissionProvider = class {
79
84
  return bindings.filter((binding) => binding.permission.client_id === identity.clientId);
80
85
  }
81
86
  };
87
+ /**
88
+ * Select the held grant that confers the least-restrictive junction for `requested` reach.
89
+ *
90
+ * Each grant is ranked by the reach it can actually confer for THIS request — its own
91
+ * `realm_scope` capped to `requested` — so a lower-scoped policy-free grant beats a
92
+ * higher-scoped policy-bound grant when both cap to the same requested reach (the mixed-grant
93
+ * fix, #3160). On equal capped reach, a policy-free grant wins (least restrictive). The
94
+ * ORIGINAL (uncapped) grant is returned: the consumer applies the `min` cap itself, and its
95
+ * uncapped `realm_scope`/`policy` drive the "actor is unrestricted-any?" check that decides
96
+ * whether an explicitly-requested `policy_id` may stand.
97
+ *
98
+ * The ordering is TOTAL and deterministic (independent of grant/repository order): ties between
99
+ * two policy-bound grants on equal capped reach are broken by higher uncapped reach, then a
100
+ * propagatable policy (one with an id) over an id-less composite that would fail closed, then
101
+ * lexicographic policy id.
102
+ */ function selectGrantForRequest(grants, requested) {
103
+ return grants.reduce((best, grant) => isGrantPreferred(grant, best, requested) ? grant : best);
104
+ }
105
+ /** True when `candidate` is a strictly better propagation source than `incumbent` for `requested`. */ function isGrantPreferred(candidate, incumbent, requested) {
106
+ const byCapped = compareRealmScope(minRealmScope([candidate.realm_scope, requested]), minRealmScope([incumbent.realm_scope, requested]));
107
+ if (byCapped !== 0) return byCapped > 0;
108
+ if (!candidate.policy !== !incumbent.policy) return !candidate.policy;
109
+ const byUncapped = compareRealmScope(candidate.realm_scope, incumbent.realm_scope);
110
+ if (byUncapped !== 0) return byUncapped > 0;
111
+ const candidateId = candidate.policy && isPolicy(candidate.policy) ? candidate.policy.id : void 0;
112
+ const incumbentId = incumbent.policy && isPolicy(incumbent.policy) ? incumbent.policy.id : void 0;
113
+ if (candidateId !== incumbentId) {
114
+ if (typeof candidateId === "undefined") return false;
115
+ if (typeof incumbentId === "undefined") return true;
116
+ return candidateId < incumbentId;
117
+ }
118
+ return false;
119
+ }
82
120
  //#endregion
83
121
  export { IdentityPermissionProvider };
84
122
 
@@ -1 +1 @@
1
- {"version":3,"file":"module.mjs","names":["isPermissionPolicyBindingEqual","mergePermissionPolicyBindings","isPolicy","IdentityPermissionProvider","clientRepository","userRepository","roleRepository","robotRepository","roleProvider","ctx","isSuperset","parent","child","parentBindings","getFor","childBindings","parentMerged","childMerged","childItem","parentItem","find","p","policies","length","resolveJunctionPolicy","identity","options","bindings","matching","filter","b","permission","name","realmId","realm_id","clientId","client_id","undefined","merged","policy","type","getForClient","getForUser","getForRobot","getForRole","combineWithRoleBindings","getBoundPermissions","id","then","data","reduceBindingsByIdentityClient","bindingsPromise","roles","Promise","all","getRolesFor","roleBindings","getBoundPermissionsForMany","binding"],"sources":["../../../../src/core/identity/permission/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2024.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type {\n IdentityPolicyData,\n PermissionPolicyBinding,\n} from '@authup/access';\nimport { isPermissionPolicyBindingEqual, mergePermissionPolicyBindings } from '@authup/access';\nimport type { Policy } from '@authup/core-kit';\nimport { isPolicy } from '@authup/core-kit';\nimport type { IClientRepository } from '../../entities/client/types.ts';\nimport type { IRobotRepository } from '../../entities/robot/types.ts';\nimport type { IRoleRepository } from '../../entities/role/types.ts';\nimport type { IUserRepository } from '../../entities/user/types.ts';\nimport type { IIdentityRoleProvider } from '../role/types.ts';\nimport type {\n IIdentityPermissionProvider,\n IdentityPermissionProviderContext,\n ResolveJunctionPolicyOptions,\n} from './types.ts';\n\nexport class IdentityPermissionProvider implements IIdentityPermissionProvider {\n protected clientRepository: IClientRepository;\n\n protected userRepository: IUserRepository;\n\n protected roleRepository: IRoleRepository;\n\n protected robotRepository: IRobotRepository;\n\n protected roleProvider: IIdentityRoleProvider;\n\n constructor(ctx: IdentityPermissionProviderContext) {\n this.clientRepository = ctx.clientRepository;\n this.userRepository = ctx.userRepository;\n this.roleRepository = ctx.roleRepository;\n this.robotRepository = ctx.robotRepository;\n this.roleProvider = ctx.roleProvider;\n }\n\n async isSuperset(parent: IdentityPolicyData, child: IdentityPolicyData) : Promise<boolean> {\n const parentBindings = await this.getFor(parent);\n const childBindings = await this.getFor(child);\n\n const parentMerged = mergePermissionPolicyBindings(parentBindings);\n const childMerged = mergePermissionPolicyBindings(childBindings);\n\n for (const childItem of childMerged) {\n const parentItem = parentMerged.find(\n (p) => isPermissionPolicyBindingEqual(p, childItem),\n );\n\n if (!parentItem) {\n return false;\n }\n\n if (\n parentItem.policies && parentItem.policies.length > 0 &&\n (!childItem.policies || childItem.policies.length === 0)\n ) {\n return false;\n }\n }\n\n return true;\n }\n\n async resolveJunctionPolicy(\n identity: IdentityPolicyData,\n options: ResolveJunctionPolicyOptions,\n ): Promise<Policy | undefined> {\n const bindings = await this.getFor(identity);\n const matching = bindings.filter((b) => {\n if (b.permission.name !== options.name) {\n return false;\n }\n\n if (typeof options.realmId !== 'undefined') {\n if ((b.permission.realm_id ?? null) !== (options.realmId ?? null)) {\n return false;\n }\n }\n\n if (typeof options.clientId !== 'undefined') {\n if ((b.permission.client_id ?? null) !== (options.clientId ?? null)) {\n return false;\n }\n }\n\n return true;\n });\n\n if (matching.length === 0) {\n return undefined;\n }\n\n const merged = mergePermissionPolicyBindings(matching);\n\n if (merged.length > 0 && merged[0].policies && merged[0].policies.length > 0) {\n const policy = merged[0].policies[0];\n if (isPolicy(policy)) {\n return policy;\n }\n }\n\n return undefined;\n }\n\n async getFor(identity: IdentityPolicyData) : Promise<PermissionPolicyBinding[]> {\n switch (identity.type) {\n case 'client': {\n return this.getForClient(identity);\n }\n case 'user': {\n return this.getForUser(identity);\n }\n case 'robot': {\n return this.getForRobot(identity);\n }\n case 'role': {\n return this.getForRole(identity);\n }\n }\n\n return [];\n }\n\n async getForClient(identity: IdentityPolicyData) : Promise<PermissionPolicyBinding[]> {\n return this.combineWithRoleBindings(\n this.clientRepository.getBoundPermissions(identity.id),\n identity,\n );\n }\n\n async getForUser(identity: IdentityPolicyData) : Promise<PermissionPolicyBinding[]> {\n return this.combineWithRoleBindings(\n this.userRepository.getBoundPermissions(identity.id)\n .then((data) => this.reduceBindingsByIdentityClient(data, identity)),\n identity,\n );\n }\n\n async getForRobot(identity: IdentityPolicyData) : Promise<PermissionPolicyBinding[]> {\n return this.combineWithRoleBindings(\n this.robotRepository.getBoundPermissions(identity.id)\n .then((data) => this.reduceBindingsByIdentityClient(data, identity)),\n identity,\n );\n }\n\n async getForRole(identity: IdentityPolicyData) : Promise<PermissionPolicyBinding[]> {\n return this.roleRepository.getBoundPermissions(identity.id)\n .then((data) => this.reduceBindingsByIdentityClient(data, identity));\n }\n\n private async combineWithRoleBindings(\n bindingsPromise: Promise<PermissionPolicyBinding[]>,\n identity: IdentityPolicyData,\n ): Promise<PermissionPolicyBinding[]> {\n const [bindings, roles] = await Promise.all([\n bindingsPromise,\n this.roleProvider.getRolesFor(identity),\n ]);\n const roleBindings = await this.roleRepository.getBoundPermissionsForMany(roles);\n if (roleBindings.length === 0) {\n return bindings;\n }\n\n return [\n ...bindings,\n ...roleBindings,\n ];\n }\n\n private reduceBindingsByIdentityClient(\n bindings: PermissionPolicyBinding[],\n identity: IdentityPolicyData,\n ): PermissionPolicyBinding[] {\n if (!identity.clientId) {\n return bindings;\n }\n\n return bindings.filter((binding) => binding.permission.client_id === identity.clientId);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"module.mjs","names":["RealmScope","aggregatePermissionPolicyBindings","compareRealmScope","grantDominates","isPermissionPolicyBindingEqual","minRealmScope","isPolicy","IdentityPermissionProvider","clientRepository","userRepository","roleRepository","robotRepository","roleProvider","ctx","isSuperset","parent","child","parentAggregated","getFor","childAggregated","childItem","parentItem","find","p","childGrant","grants","dominated","some","parentGrant","resolveJunctionGrant","identity","options","bindings","matching","filter","b","permission","name","realmId","realm_id","clientId","client_id","length","realmScope","OWN","flatMap","item","requested","selected","selectGrantForRequest","policy","NONE","realm_scope","type","getForClient","getForUser","getForRobot","getForRole","combineWithRoleBindings","getBoundPermissions","id","then","data","reduceBindingsByIdentityClient","bindingsPromise","roles","Promise","all","getRolesFor","roleBindings","getBoundPermissionsForMany","binding","reduce","best","grant","isGrantPreferred","candidate","incumbent","byCapped","byUncapped","candidateId","undefined","incumbentId"],"sources":["../../../../src/core/identity/permission/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2024.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type {\n IdentityPolicyData,\n PermissionGrant,\n PermissionPolicyBinding,\n} from '@authup/access';\nimport {\n RealmScope,\n aggregatePermissionPolicyBindings,\n compareRealmScope,\n grantDominates,\n isPermissionPolicyBindingEqual,\n minRealmScope,\n} from '@authup/access';\nimport type { Policy } from '@authup/core-kit';\nimport { isPolicy } from '@authup/core-kit';\nimport type { IClientRepository } from '../../entities/client/types.ts';\nimport type { IRobotRepository } from '../../entities/robot/types.ts';\nimport type { IRoleRepository } from '../../entities/role/types.ts';\nimport type { IUserRepository } from '../../entities/user/types.ts';\nimport type { IIdentityRoleProvider } from '../role/types.ts';\nimport type {\n IIdentityPermissionProvider,\n IdentityPermissionProviderContext,\n ResolveJunctionGrantResult,\n ResolveJunctionPolicyOptions,\n} from './types.ts';\n\nexport class IdentityPermissionProvider implements IIdentityPermissionProvider {\n protected clientRepository: IClientRepository;\n\n protected userRepository: IUserRepository;\n\n protected roleRepository: IRoleRepository;\n\n protected robotRepository: IRobotRepository;\n\n protected roleProvider: IIdentityRoleProvider;\n\n constructor(ctx: IdentityPermissionProviderContext) {\n this.clientRepository = ctx.clientRepository;\n this.userRepository = ctx.userRepository;\n this.roleRepository = ctx.roleRepository;\n this.robotRepository = ctx.robotRepository;\n this.roleProvider = ctx.roleProvider;\n }\n\n async isSuperset(parent: IdentityPolicyData, child: IdentityPolicyData) : Promise<boolean> {\n const parentAggregated = aggregatePermissionPolicyBindings(await this.getFor(parent));\n const childAggregated = aggregatePermissionPolicyBindings(await this.getFor(child));\n\n for (const childItem of childAggregated) {\n const parentItem = parentAggregated.find(\n (p) => isPermissionPolicyBindingEqual(p, childItem),\n );\n\n if (!parentItem) {\n return false;\n }\n\n // Disjunction-aware superset: EVERY child grant must be dominated by SOME parent\n // grant (reach >= AND the parent's policy provably covers the child's — see\n // grantDominates). A child grant the parent cannot match — an unconditional `any`\n // the parent only holds policy-gated, or a policy the parent does not itself hold\n // (#3159) — fails the check; the actor cannot assign more than it holds.\n for (const childGrant of childItem.grants) {\n const dominated = parentItem.grants.some(\n (parentGrant) => grantDominates(parentGrant, childGrant),\n );\n\n if (!dominated) {\n return false;\n }\n }\n }\n\n return true;\n }\n\n async resolveJunctionGrant(\n identity: IdentityPolicyData,\n options: ResolveJunctionPolicyOptions,\n ): Promise<ResolveJunctionGrantResult> {\n const bindings = await this.getFor(identity);\n const matching = bindings.filter((b) => {\n if (b.permission.name !== options.name) {\n return false;\n }\n\n if (typeof options.realmId !== 'undefined') {\n if ((b.permission.realm_id ?? null) !== (options.realmId ?? null)) {\n return false;\n }\n }\n\n if (typeof options.clientId !== 'undefined') {\n if ((b.permission.client_id ?? null) !== (options.clientId ?? null)) {\n return false;\n }\n }\n\n return true;\n });\n\n if (matching.length === 0) {\n return { realmScope: RealmScope.OWN };\n }\n\n // Consider EVERY matching grant (a caller omitting realmId/clientId may leave more\n // than one permission-key group); combined with the total ordering in\n // selectGrantForRequest, the selection never depends on grant/repository order.\n const grants = aggregatePermissionPolicyBindings(matching)\n .flatMap((item) => item.grants);\n if (grants.length === 0) {\n return { realmScope: RealmScope.OWN };\n }\n\n // Select the actor's grant RELATIVE to the requested reach (#3160): the grant that\n // confers the least-restrictive junction once capped to `realmScope` (highest capped\n // reach, policy-free preferred on a tie) — NOT a global \"ceiling\". A mixed-grant actor\n // (e.g. own+no-policy and any+policy) thus propagates its policy-free own grant for an\n // own request instead of inheriting the wider grant's policy. The selected grant is\n // returned UNCAPPED so the consumer's own cap (`min(requested, realmScope)`) and its\n // \"actor is unrestricted-any?\" check (which honours an explicit policy_id) still hold.\n const requested = options.realmScope ?? RealmScope.OWN;\n const selected = selectGrantForRequest(grants, requested);\n\n let policy: Policy | undefined;\n if (selected.policy) {\n // The selected grant is policy-restricted but its policy is not a propagatable\n // Policy (e.g. a composite with no id). Fail CLOSED: a `none` reach blocks\n // propagation rather than silently dropping the restriction and widening to an\n // unrestricted grant.\n if (!isPolicy(selected.policy)) {\n return { realmScope: RealmScope.NONE };\n }\n\n policy = selected.policy;\n }\n\n return { policy, realmScope: selected.realm_scope };\n }\n\n async getFor(identity: IdentityPolicyData) : Promise<PermissionPolicyBinding[]> {\n switch (identity.type) {\n case 'client': {\n return this.getForClient(identity);\n }\n case 'user': {\n return this.getForUser(identity);\n }\n case 'robot': {\n return this.getForRobot(identity);\n }\n case 'role': {\n return this.getForRole(identity);\n }\n }\n\n return [];\n }\n\n async getForClient(identity: IdentityPolicyData) : Promise<PermissionPolicyBinding[]> {\n return this.combineWithRoleBindings(\n this.clientRepository.getBoundPermissions(identity.id),\n identity,\n );\n }\n\n async getForUser(identity: IdentityPolicyData) : Promise<PermissionPolicyBinding[]> {\n return this.combineWithRoleBindings(\n this.userRepository.getBoundPermissions(identity.id)\n .then((data) => this.reduceBindingsByIdentityClient(data, identity)),\n identity,\n );\n }\n\n async getForRobot(identity: IdentityPolicyData) : Promise<PermissionPolicyBinding[]> {\n return this.combineWithRoleBindings(\n this.robotRepository.getBoundPermissions(identity.id)\n .then((data) => this.reduceBindingsByIdentityClient(data, identity)),\n identity,\n );\n }\n\n async getForRole(identity: IdentityPolicyData) : Promise<PermissionPolicyBinding[]> {\n return this.roleRepository.getBoundPermissions(identity.id)\n .then((data) => this.reduceBindingsByIdentityClient(data, identity));\n }\n\n private async combineWithRoleBindings(\n bindingsPromise: Promise<PermissionPolicyBinding[]>,\n identity: IdentityPolicyData,\n ): Promise<PermissionPolicyBinding[]> {\n const [bindings, roles] = await Promise.all([\n bindingsPromise,\n this.roleProvider.getRolesFor(identity),\n ]);\n const roleBindings = await this.roleRepository.getBoundPermissionsForMany(roles);\n if (roleBindings.length === 0) {\n return bindings;\n }\n\n return [\n ...bindings,\n ...roleBindings,\n ];\n }\n\n private reduceBindingsByIdentityClient(\n bindings: PermissionPolicyBinding[],\n identity: IdentityPolicyData,\n ): PermissionPolicyBinding[] {\n if (!identity.clientId) {\n return bindings;\n }\n\n return bindings.filter((binding) => binding.permission.client_id === identity.clientId);\n }\n}\n\n/**\n * Select the held grant that confers the least-restrictive junction for `requested` reach.\n *\n * Each grant is ranked by the reach it can actually confer for THIS request — its own\n * `realm_scope` capped to `requested` — so a lower-scoped policy-free grant beats a\n * higher-scoped policy-bound grant when both cap to the same requested reach (the mixed-grant\n * fix, #3160). On equal capped reach, a policy-free grant wins (least restrictive). The\n * ORIGINAL (uncapped) grant is returned: the consumer applies the `min` cap itself, and its\n * uncapped `realm_scope`/`policy` drive the \"actor is unrestricted-any?\" check that decides\n * whether an explicitly-requested `policy_id` may stand.\n *\n * The ordering is TOTAL and deterministic (independent of grant/repository order): ties between\n * two policy-bound grants on equal capped reach are broken by higher uncapped reach, then a\n * propagatable policy (one with an id) over an id-less composite that would fail closed, then\n * lexicographic policy id.\n */\nfunction selectGrantForRequest(\n grants: PermissionGrant[],\n requested: `${RealmScope}`,\n): PermissionGrant {\n return grants.reduce((best, grant) => (isGrantPreferred(grant, best, requested) ? grant : best));\n}\n\n/** True when `candidate` is a strictly better propagation source than `incumbent` for `requested`. */\nfunction isGrantPreferred(\n candidate: PermissionGrant,\n incumbent: PermissionGrant,\n requested: `${RealmScope}`,\n): boolean {\n const byCapped = compareRealmScope(\n minRealmScope([candidate.realm_scope, requested]),\n minRealmScope([incumbent.realm_scope, requested]),\n );\n if (byCapped !== 0) {\n return byCapped > 0;\n }\n\n // Least restrictive: a policy-free grant beats a policy-bound one on equal capped reach.\n if (!candidate.policy !== !incumbent.policy) {\n return !candidate.policy;\n }\n\n // Deterministic tie-break for two policy-bound grants on equal capped reach.\n const byUncapped = compareRealmScope(candidate.realm_scope, incumbent.realm_scope);\n if (byUncapped !== 0) {\n return byUncapped > 0;\n }\n\n // Prefer a propagatable policy (has an id) over an id-less composite that would fail closed.\n const candidateId = candidate.policy && isPolicy(candidate.policy) ? candidate.policy.id : undefined;\n const incumbentId = incumbent.policy && isPolicy(incumbent.policy) ? incumbent.policy.id : undefined;\n if (candidateId !== incumbentId) {\n if (typeof candidateId === 'undefined') {\n return false;\n }\n if (typeof incumbentId === 'undefined') {\n return true;\n }\n return candidateId < incumbentId;\n }\n\n return false;\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import type { Policy } from '@authup/core-kit';
2
- import type { IdentityPolicyData, PermissionPolicyBinding } from '@authup/access';
2
+ import type { IdentityPolicyData, PermissionPolicyBinding, RealmScope } from '@authup/access';
3
3
  import type { IClientRepository } from '../../entities/client/types.ts';
4
4
  import type { IRobotRepository } from '../../entities/robot/types.ts';
5
5
  import type { IRoleRepository } from '../../entities/role/types.ts';
@@ -9,11 +9,31 @@ export type ResolveJunctionPolicyOptions = {
9
9
  name: string;
10
10
  realmId?: string | null;
11
11
  clientId?: string | null;
12
+ /**
13
+ * The realm reach the junction is requested to confer (default `own`). The actor's
14
+ * grant disjunction is selected RELATIVE to this request: the grant chosen is the one
15
+ * that confers the least-restrictive junction once capped to `realmScope`, not the
16
+ * actor's absolute most-permissive grant. Lets a mixed-grant actor propagate a
17
+ * policy-free `own` grant for an `own` request even while holding a wider policy-bound
18
+ * grant (#3160).
19
+ */
20
+ realmScope?: `${RealmScope}`;
21
+ };
22
+ /**
23
+ * The actor's grant SELECTED for the requested reach (`ResolveJunctionPolicyOptions.realmScope`):
24
+ * the policy to inherit (if any) and the grant's UNCAPPED `realmScope`. Returned uncapped on
25
+ * purpose — the consumer applies the `min(requested, realmScope)` cap itself, and the uncapped
26
+ * reach distinguishes a genuinely unrestricted actor (see `applyJunctionCreateGrant` /
27
+ * `buildJunctionUpdateData`). This is not a global ceiling; it is request-relative (#3160).
28
+ */
29
+ export type ResolveJunctionGrantResult = {
30
+ policy?: Policy;
31
+ realmScope: `${RealmScope}`;
12
32
  };
13
33
  export interface IIdentityPermissionProvider {
14
34
  getFor(identity: IdentityPolicyData): Promise<PermissionPolicyBinding[]>;
15
35
  isSuperset(parent: IdentityPolicyData, child: IdentityPolicyData): Promise<boolean>;
16
- resolveJunctionPolicy(identity: IdentityPolicyData, options: ResolveJunctionPolicyOptions): Promise<Policy | undefined>;
36
+ resolveJunctionGrant(identity: IdentityPolicyData, options: ResolveJunctionPolicyOptions): Promise<ResolveJunctionGrantResult>;
17
37
  }
18
38
  export type IdentityPermissionProviderContext = {
19
39
  clientRepository: IClientRepository;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/core/identity/permission/types.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EACR,kBAAkB,EAClB,uBAAuB,EAC1B,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAE9D,MAAM,MAAM,4BAA4B,GAAG;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,MAAM,WAAW,2BAA2B;IACxC,MAAM,CAAC,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAAC;IACzE,UAAU,CAAC,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpF,qBAAqB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CAC3H;AAED,MAAM,MAAM,iCAAiC,GAAG;IAC5C,gBAAgB,EAAE,iBAAiB,CAAC;IACpC,cAAc,EAAE,eAAe,CAAC;IAChC,eAAe,EAAE,gBAAgB,CAAC;IAClC,cAAc,EAAE,eAAe,CAAC;IAChC,YAAY,EAAE,qBAAqB,CAAC;CACvC,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/core/identity/permission/types.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EACR,kBAAkB,EAClB,uBAAuB,EACvB,UAAU,EACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAE9D,MAAM,MAAM,4BAA4B,GAAG;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,GAAG,UAAU,EAAE,CAAC;CAChC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,GAAG,UAAU,EAAE,CAAC;CAC/B,CAAC;AAEF,MAAM,WAAW,2BAA2B;IACxC,MAAM,CAAC,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAAC;IACzE,UAAU,CAAC,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpF,oBAAoB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAC;CAClI;AAED,MAAM,MAAM,iCAAiC,GAAG;IAC5C,gBAAgB,EAAE,iBAAiB,CAAC;IACpC,cAAc,EAAE,eAAe,CAAC;IAChC,eAAe,EAAE,gBAAgB,CAAC;IAClC,cAAc,EAAE,eAAe,CAAC;IAChC,YAAY,EAAE,qBAAqB,CAAC;CACvC,CAAC"}
@@ -13,6 +13,7 @@ import { SessionManager } from "./authentication/session/module.mjs";
13
13
  import "./authentication/index.mjs";
14
14
  import { ClientService } from "./entities/client/service.mjs";
15
15
  import { WebClientProvisioner, buildWebClientAttributes } from "./entities/client/web-client.mjs";
16
+ import { applyJunctionCreateGrant, buildJunctionUpdateData } from "./identity/permission/junction-grant.mjs";
16
17
  import { ClientPermissionService } from "./entities/client-permission/service.mjs";
17
18
  import { ClientRoleService } from "./entities/client-role/service.mjs";
18
19
  import { ClientScopeService } from "./entities/client-scope/service.mjs";
@@ -114,4 +115,4 @@ import { ScopeProvisioningSynchronizer } from "./provisioning/synchronizer/scope
114
115
  import { UserProvisioningSynchronizer } from "./provisioning/synchronizer/user/module.mjs";
115
116
  import "./provisioning/index.mjs";
116
117
  import "./security/index.mjs";
117
- export { BaseCredentialsAuthenticator, ClientAuthenticator, ClientCredentialsGrant, ClientCredentialsService, ClientPermissionService, ClientProvisioningSynchronizer, ClientProvisioningValidator, ClientRoleService, ClientScopeService, ClientService, CredentialsAuthenticator, GraphProvisioningSynchronizer, IDENTITY_PROVIDER_LDAP_COLLECTION_AUTHENTICATOR_TOKEN, IdentityGrantType, IdentityPermissionProvider, IdentityProviderAccountBaseMapper, IdentityProviderAccountManager, IdentityProviderAttributeMapper, IdentityProviderFacebookAuthenticator, IdentityProviderGithubAuthenticator, IdentityProviderGoogleAuthenticator, IdentityProviderInstagramAuthenticator, IdentityProviderLdapAuthenticator, IdentityProviderLdapCollectionAuthenticator, IdentityProviderMapperOperation, IdentityProviderOAuth2Authenticator, IdentityProviderOpenIDAuthenticator, IdentityProviderPaypalAuthenticator, IdentityProviderPermissionMapper, IdentityProviderRoleMapper, IdentityProviderRoleMappingService, IdentityResolver, IdentityRoleProvider, MAIL_TEMPLATE_REGISTRY, MailTemplateName, MailTemplateRenderer, OAuth2AccessTokenIssuer, OAuth2Authorization, OAuth2AuthorizationCodeIssuer, OAuth2AuthorizationCodeRequestValidator, OAuth2AuthorizationCodeRequestVerifier, OAuth2AuthorizationCodeVerifier, OAuth2AuthorizationStateManager, OAuth2AuthorizeGrant, OAuth2ClientAuthenticator, OAuth2OpenIDClaimsBuilder, OAuth2OpenIDTokenIssuer, OAuth2RefreshTokenGrant, OAuth2RefreshTokenIssuer, OAuth2ScopeAttributesResolver, OAuth2TokenRevoker, OAuth2TokenSigner, OAuth2TokenVerifier, PASSWORD_RESET_EXPIRES_IN_MINUTES, PasswordGrantType, PasswordRecoveryService, PermissionBindingPolicyEvaluator, PermissionCheckerService, PermissionPolicyService, PermissionProvisioningSynchronizer, PermissionProvisioningValidator, PermissionService, PolicyCheckerService, PolicyEngine, PolicyProvisioningSynchronizer, PolicyService, ProvisioningEntityResolver, ProvisioningEntityStrategyType, ProvisioningJunctionSynchronizer, ProvisioningStrategyValidator, RealmProvisioningSynchronizer, RealmProvisioningValidator, RealmService, RegistrationService, RobotAuthenticator, RobotCredentialsGrant, RobotCredentialsService, RobotPermissionService, RobotProvisioningSynchronizer, RobotProvisioningValidator, RobotRoleService, RobotService, RoleAttributeService, RolePermissionService, RoleProvisioningSynchronizer, RoleProvisioningValidator, RoleService, RootProvisioningValidator, ScopeProvisioningSynchronizer, ScopeProvisioningValidator, ScopeService, SessionManager, UserAttributeService, UserAuthenticator, UserCredentialsService, UserPermissionService, UserProvisioningSynchronizer, UserProvisioningValidator, UserRoleService, UserService, WebClientProvisioner, base64URLEncode, buildOAuth2BearerTokenResponse, buildOAuth2CodeChallenge, buildOAuth2TokenHash, buildWebClientAttributes, createIdentityProviderOAuth2Authenticator, defineMailTemplate, generateOAuth2CodeVerifier, normalizeEntityProvisioningStrategy };
118
+ export { BaseCredentialsAuthenticator, ClientAuthenticator, ClientCredentialsGrant, ClientCredentialsService, ClientPermissionService, ClientProvisioningSynchronizer, ClientProvisioningValidator, ClientRoleService, ClientScopeService, ClientService, CredentialsAuthenticator, GraphProvisioningSynchronizer, IDENTITY_PROVIDER_LDAP_COLLECTION_AUTHENTICATOR_TOKEN, IdentityGrantType, IdentityPermissionProvider, IdentityProviderAccountBaseMapper, IdentityProviderAccountManager, IdentityProviderAttributeMapper, IdentityProviderFacebookAuthenticator, IdentityProviderGithubAuthenticator, IdentityProviderGoogleAuthenticator, IdentityProviderInstagramAuthenticator, IdentityProviderLdapAuthenticator, IdentityProviderLdapCollectionAuthenticator, IdentityProviderMapperOperation, IdentityProviderOAuth2Authenticator, IdentityProviderOpenIDAuthenticator, IdentityProviderPaypalAuthenticator, IdentityProviderPermissionMapper, IdentityProviderRoleMapper, IdentityProviderRoleMappingService, IdentityResolver, IdentityRoleProvider, MAIL_TEMPLATE_REGISTRY, MailTemplateName, MailTemplateRenderer, OAuth2AccessTokenIssuer, OAuth2Authorization, OAuth2AuthorizationCodeIssuer, OAuth2AuthorizationCodeRequestValidator, OAuth2AuthorizationCodeRequestVerifier, OAuth2AuthorizationCodeVerifier, OAuth2AuthorizationStateManager, OAuth2AuthorizeGrant, OAuth2ClientAuthenticator, OAuth2OpenIDClaimsBuilder, OAuth2OpenIDTokenIssuer, OAuth2RefreshTokenGrant, OAuth2RefreshTokenIssuer, OAuth2ScopeAttributesResolver, OAuth2TokenRevoker, OAuth2TokenSigner, OAuth2TokenVerifier, PASSWORD_RESET_EXPIRES_IN_MINUTES, PasswordGrantType, PasswordRecoveryService, PermissionBindingPolicyEvaluator, PermissionCheckerService, PermissionPolicyService, PermissionProvisioningSynchronizer, PermissionProvisioningValidator, PermissionService, PolicyCheckerService, PolicyEngine, PolicyProvisioningSynchronizer, PolicyService, ProvisioningEntityResolver, ProvisioningEntityStrategyType, ProvisioningJunctionSynchronizer, ProvisioningStrategyValidator, RealmProvisioningSynchronizer, RealmProvisioningValidator, RealmService, RegistrationService, RobotAuthenticator, RobotCredentialsGrant, RobotCredentialsService, RobotPermissionService, RobotProvisioningSynchronizer, RobotProvisioningValidator, RobotRoleService, RobotService, RoleAttributeService, RolePermissionService, RoleProvisioningSynchronizer, RoleProvisioningValidator, RoleService, RootProvisioningValidator, ScopeProvisioningSynchronizer, ScopeProvisioningValidator, ScopeService, SessionManager, UserAttributeService, UserAuthenticator, UserCredentialsService, UserPermissionService, UserProvisioningSynchronizer, UserProvisioningValidator, UserRoleService, UserService, WebClientProvisioner, applyJunctionCreateGrant, base64URLEncode, buildJunctionUpdateData, buildOAuth2BearerTokenResponse, buildOAuth2CodeChallenge, buildOAuth2TokenHash, buildWebClientAttributes, createIdentityProviderOAuth2Authenticator, defineMailTemplate, generateOAuth2CodeVerifier, normalizeEntityProvisioningStrategy };
@@ -1,3 +1,4 @@
1
+ import type { RealmScope } from '@authup/access';
1
2
  import type { Role } from '@authup/core-kit';
2
3
  import type { BaseProvisioningEntity } from '../types.ts';
3
4
  export type RoleProvisioningRelations = {
@@ -10,14 +11,16 @@ export type RoleProvisioningRelations = {
10
11
  */
11
12
  globalPermissionsExclude?: string[];
12
13
  /**
13
- * Default policy name to set on each role-permission junction entry.
14
+ * Default realm_scope (own | ownOrNull | any) to stamp on each
15
+ * role-permission junction entry. Realm reach is a coarse, actor-relative
16
+ * enum on the junction (not a policy).
14
17
  */
15
- globalPermissionsPolicyName?: string;
18
+ globalPermissionsRealmScope?: RealmScope;
16
19
  /**
17
- * Per-permission policy overrides. Maps policy name to permission names
18
- * that should use that policy instead of the default.
20
+ * Per-permission realm_scope overrides. Maps a realm_scope value to the
21
+ * permission names that should use that scope instead of the default.
19
22
  */
20
- globalPermissionsPolicyOverrides?: Record<string, string[]>;
23
+ globalPermissionsRealmScopeOverrides?: Partial<Record<RealmScope, string[]>>;
21
24
  /**
22
25
  * Assign role to realm permissions.
23
26
  */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/core/provisioning/entities/role/types.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,MAAM,yBAAyB,GAAG;IACpC;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE7B;;OAEG;IACH,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpC;;OAEG;IACH,2BAA2B,CAAC,EAAE,MAAM,CAAC;IAErC;;;OAGG;IACH,gCAAgC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAE5D;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,sBAAsB,CAAC,IAAI,CAAC,GAAG;IAChE,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,SAAS,CAAC,EAAE,yBAAyB,CAAA;CACxC,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/core/provisioning/entities/role/types.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,MAAM,yBAAyB,GAAG;IACpC;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE7B;;OAEG;IACH,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpC;;;;OAIG;IACH,2BAA2B,CAAC,EAAE,UAAU,CAAC;IAEzC;;;OAGG;IACH,oCAAoC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAE7E;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,sBAAsB,CAAC,IAAI,CAAC,GAAG;IAChE,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,SAAS,CAAC,EAAE,yBAAyB,CAAA;CACxC,CAAC"}
@@ -2,5 +2,8 @@ import type { IProvisioningSynchronizer } from '../types.ts';
2
2
  export declare abstract class BaseProvisioningSynchronizer<T> implements IProvisioningSynchronizer<T> {
3
3
  synchronizeMany(input: T[]): Promise<T[]>;
4
4
  abstract synchronize(input: T): Promise<T>;
5
+ protected canonicalizeName(attributes: {
6
+ name?: string | null;
7
+ } | undefined): void;
5
8
  }
6
9
  //# sourceMappingURL=base.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/core/provisioning/synchronizer/base.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAE7D,8BAAsB,4BAA4B,CAAC,CAAC,CAAE,YAAW,yBAAyB,CAAC,CAAC,CAAC;IAGnF,eAAe,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAQ/C,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAC7C"}
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/core/provisioning/synchronizer/base.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAE7D,8BAAsB,4BAA4B,CAAC,CAAC,CAAE,YAAW,yBAAyB,CAAC,CAAC,CAAC;IAGnF,eAAe,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAQ/C,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAM1C,SAAS,CAAC,gBAAgB,CAAC,UAAU,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,SAAS,GAAG,IAAI;CAKrF"}
@@ -8,6 +8,9 @@ var BaseProvisioningSynchronizer = class {
8
8
  for (const entity of input) output.push(await this.synchronize(entity));
9
9
  return output;
10
10
  }
11
+ canonicalizeName(attributes) {
12
+ if (attributes && typeof attributes.name === "string") attributes.name = attributes.name.trim().toLowerCase();
13
+ }
11
14
  };
12
15
  //#endregion
13
16
  export { BaseProvisioningSynchronizer };
@@ -1 +1 @@
1
- {"version":3,"file":"base.mjs","names":["BaseProvisioningSynchronizer","synchronizeMany","input","output","entity","push","synchronize"],"sources":["../../../../src/core/provisioning/synchronizer/base.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { IProvisioningSynchronizer } from '../types.ts';\n\nexport abstract class BaseProvisioningSynchronizer<T> implements IProvisioningSynchronizer<T> {\n // Sequential to avoid race conditions on concurrent upserts.\n // Can be switched to Promise.all() if callers guarantee unique inputs.\n async synchronizeMany(input: T[]): Promise<T[]> {\n const output: T[] = [];\n for (const entity of input) {\n output.push(await this.synchronize(entity));\n }\n return output;\n }\n\n abstract synchronize(input: T): Promise<T>;\n}\n"],"mappings":";;;;AASA,IAAsBA,+BAAtB,MAAsBA;CAGlB,MAAMC,gBAAgBC,OAA0B;EAC5C,MAAMC,SAAc,CAAA;EACpB,KAAK,MAAMC,UAAUF,OACjBC,OAAOE,KAAK,MAAM,KAAKC,YAAYF,MAAAA,CAAAA;EAEvC,OAAOD;CACX;AAGJ"}
1
+ {"version":3,"file":"base.mjs","names":["BaseProvisioningSynchronizer","synchronizeMany","input","output","entity","push","synchronize","canonicalizeName","attributes","name","trim","toLowerCase"],"sources":["../../../../src/core/provisioning/synchronizer/base.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { IProvisioningSynchronizer } from '../types.ts';\n\nexport abstract class BaseProvisioningSynchronizer<T> implements IProvisioningSynchronizer<T> {\n // Sequential to avoid race conditions on concurrent upserts.\n // Can be switched to Promise.all() if callers guarantee unique inputs.\n async synchronizeMany(input: T[]): Promise<T[]> {\n const output: T[] = [];\n for (const entity of input) {\n output.push(await this.synchronize(entity));\n }\n return output;\n }\n\n abstract synchronize(input: T): Promise<T>;\n\n // canonical identifier form: provisioning sources may carry raw names\n // (the file-source entity validators run group-less, so the validator\n // transforms don't apply); lookups against canonically stored rows\n // and canonical persistence both require it.\n protected canonicalizeName(attributes: { name?: string | null } | undefined): void {\n if (attributes && typeof attributes.name === 'string') {\n attributes.name = attributes.name.trim().toLowerCase();\n }\n }\n}\n"],"mappings":";;;;AASA,IAAsBA,+BAAtB,MAAsBA;CAGlB,MAAMC,gBAAgBC,OAA0B;EAC5C,MAAMC,SAAc,CAAA;EACpB,KAAK,MAAMC,UAAUF,OACjBC,OAAOE,KAAK,MAAM,KAAKC,YAAYF,MAAAA,CAAAA;EAEvC,OAAOD;CACX;CAQUI,iBAAiBC,YAAwD;EAC/E,IAAIA,cAAc,OAAOA,WAAWC,SAAS,UACzCD,WAAWC,OAAOD,WAAWC,KAAKC,KAAI,CAAA,CAAGC,YAAW;CAE5D;AACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../../src/core/provisioning/synchronizer/client/module.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACR,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,IAAI,EACP,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAC9E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAElE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,EAAE,gCAAgC,EAAE,MAAM,6BAA6B,CAAC;AAC/E,OAAO,KAAK,EAAE,qCAAqC,EAAE,MAAM,YAAY,CAAC;AAExE,qBAAa,8BAA+B,SAAQ,4BAA4B,CAAC,wBAAwB,CAAC;IACtG,SAAS,CAAC,gBAAgB,EAAE,iBAAiB,CAAC;IAE9C,SAAS,CAAC,kBAAkB,EAAE,0BAA0B,CAAC,UAAU,CAAC,CAAC;IAErE,SAAS,CAAC,YAAY,EAAE,0BAA0B,CAAC,IAAI,CAAC,CAAC;IAEzD,SAAS,CAAC,kBAAkB,EAAE,gCAAgC,CAAC,gBAAgB,CAAC,CAAC;IAEjF,SAAS,CAAC,YAAY,EAAE,gCAAgC,CAAC,UAAU,CAAC,CAAC;IAErE,SAAS,CAAC,sBAAsB,EAAE,yBAAyB,CAAC,4BAA4B,CAAC,CAAC;IAE1F,SAAS,CAAC,gBAAgB,EAAE,yBAAyB,CAAC,sBAAsB,CAAC,CAAC;gBAElE,GAAG,EAAE,qCAAqC;IAuBhD,WAAW,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,wBAAwB,CAAC;CA6GxF"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../../src/core/provisioning/synchronizer/client/module.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACR,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,IAAI,EACP,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAC9E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAElE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,EAAE,gCAAgC,EAAE,MAAM,6BAA6B,CAAC;AAC/E,OAAO,KAAK,EAAE,qCAAqC,EAAE,MAAM,YAAY,CAAC;AAExE,qBAAa,8BAA+B,SAAQ,4BAA4B,CAAC,wBAAwB,CAAC;IACtG,SAAS,CAAC,gBAAgB,EAAE,iBAAiB,CAAC;IAE9C,SAAS,CAAC,kBAAkB,EAAE,0BAA0B,CAAC,UAAU,CAAC,CAAC;IAErE,SAAS,CAAC,YAAY,EAAE,0BAA0B,CAAC,IAAI,CAAC,CAAC;IAEzD,SAAS,CAAC,kBAAkB,EAAE,gCAAgC,CAAC,gBAAgB,CAAC,CAAC;IAEjF,SAAS,CAAC,YAAY,EAAE,gCAAgC,CAAC,UAAU,CAAC,CAAC;IAErE,SAAS,CAAC,sBAAsB,EAAE,yBAAyB,CAAC,4BAA4B,CAAC,CAAC;IAE1F,SAAS,CAAC,gBAAgB,EAAE,yBAAyB,CAAC,sBAAsB,CAAC,CAAC;gBAElE,GAAG,EAAE,qCAAqC;IAuBhD,WAAW,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,wBAAwB,CAAC;CA+GxF"}
@@ -36,6 +36,7 @@ var ClientProvisioningSynchronizer = class extends BaseProvisioningSynchronizer
36
36
  this.roleSynchronizer = ctx.roleSynchronizer;
37
37
  }
38
38
  async synchronize(input) {
39
+ this.canonicalizeName(input.attributes);
39
40
  const strategy = normalizeEntityProvisioningStrategy(input.strategy);
40
41
  let attributes = await this.clientRepository.findOneBy({
41
42
  name: input.attributes.name,
@@ -1 +1 @@
1
- {"version":3,"file":"module.mjs","names":["pickRecord","ProvisioningEntityStrategyType","normalizeEntityProvisioningStrategy","BaseProvisioningSynchronizer","ProvisioningEntityResolver","ProvisioningJunctionSynchronizer","ClientProvisioningSynchronizer","clientRepository","permissionResolver","roleResolver","permissionJunction","roleJunction","permissionSynchronizer","roleSynchronizer","ctx","permissionRepository","roleRepository","repository","clientPermissionRepository","ownerKey","ownerRealmKey","clientRoleRepository","synchronize","input","strategy","attributes","findOneBy","name","realm_id","type","ABSENT","remove","MERGE","merge","save","REPLACE","id","create","permissions","resolveGlobal","relations","globalPermissions","resolveRealm","realmPermissions","length","roles","globalRoles","realmRoles","children","map","child","client_id","client","synchronizeMany"],"sources":["../../../../../src/core/provisioning/synchronizer/client/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2026-2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type {\n ClientPermission, \n ClientRole, \n Permission, \n Role,\n} from '@authup/core-kit';\nimport { pickRecord } from '@authup/kit';\nimport type { IClientRepository } from '../../../entities/index.ts';\nimport type { ClientProvisioningEntity } from '../../entities/client';\nimport type { PermissionProvisioningEntity } from '../../entities/permission';\nimport type { RoleProvisioningEntity } from '../../entities/role';\nimport { ProvisioningEntityStrategyType, normalizeEntityProvisioningStrategy } from '../../strategy/index.ts';\nimport type { IProvisioningSynchronizer } from '../../types.ts';\nimport { BaseProvisioningSynchronizer } from '../base.ts';\nimport { ProvisioningEntityResolver } from '../entity-resolver.ts';\nimport { ProvisioningJunctionSynchronizer } from '../junction-synchronizer.ts';\nimport type { ClientProvisioningSynchronizerContext } from './types.ts';\n\nexport class ClientProvisioningSynchronizer extends BaseProvisioningSynchronizer<ClientProvisioningEntity> {\n protected clientRepository: IClientRepository;\n\n protected permissionResolver: ProvisioningEntityResolver<Permission>;\n\n protected roleResolver: ProvisioningEntityResolver<Role>;\n\n protected permissionJunction: ProvisioningJunctionSynchronizer<ClientPermission>;\n\n protected roleJunction: ProvisioningJunctionSynchronizer<ClientRole>;\n\n protected permissionSynchronizer: IProvisioningSynchronizer<PermissionProvisioningEntity>;\n\n protected roleSynchronizer: IProvisioningSynchronizer<RoleProvisioningEntity>;\n\n constructor(ctx: ClientProvisioningSynchronizerContext) {\n super();\n\n this.clientRepository = ctx.clientRepository;\n\n this.permissionResolver = new ProvisioningEntityResolver(ctx.permissionRepository);\n this.roleResolver = new ProvisioningEntityResolver(ctx.roleRepository);\n\n this.permissionJunction = new ProvisioningJunctionSynchronizer({\n repository: ctx.clientPermissionRepository,\n ownerKey: 'client_id',\n ownerRealmKey: 'client_realm_id',\n });\n this.roleJunction = new ProvisioningJunctionSynchronizer({\n repository: ctx.clientRoleRepository,\n ownerKey: 'client_id',\n ownerRealmKey: 'client_realm_id',\n });\n\n this.permissionSynchronizer = ctx.permissionSynchronizer;\n this.roleSynchronizer = ctx.roleSynchronizer;\n }\n\n async synchronize(input: ClientProvisioningEntity): Promise<ClientProvisioningEntity> {\n const strategy = normalizeEntityProvisioningStrategy(input.strategy);\n\n let attributes = await this.clientRepository.findOneBy({\n name: input.attributes.name,\n realm_id: input.attributes.realm_id || null,\n });\n\n if (strategy.type === ProvisioningEntityStrategyType.ABSENT) {\n if (attributes) {\n await this.clientRepository.remove(attributes);\n }\n return {\n ...input,\n attributes: attributes || input.attributes, \n };\n }\n\n if (attributes) {\n switch (strategy.type) {\n case ProvisioningEntityStrategyType.MERGE:\n attributes = this.clientRepository.merge(\n attributes,\n strategy.attributes ?\n pickRecord(input.attributes, strategy.attributes) :\n input.attributes,\n );\n attributes = await this.clientRepository.save(attributes);\n break;\n case ProvisioningEntityStrategyType.REPLACE:\n input.attributes.id = attributes.id;\n attributes = await this.clientRepository.save(this.clientRepository.create(input.attributes));\n break;\n }\n } else {\n attributes = await this.clientRepository.save(this.clientRepository.create(input.attributes));\n }\n\n // Permissions (Global + Realm)\n const permissions = [\n ...await this.permissionResolver.resolveGlobal(\n input.relations && input.relations.globalPermissions,\n ),\n ...(attributes.realm_id ?\n await this.permissionResolver.resolveRealm(\n input.relations && input.relations.realmPermissions,\n attributes.realm_id,\n ) :\n []),\n ];\n\n if (permissions.length > 0) {\n await this.permissionJunction.synchronize(\n attributes,\n permissions,\n 'permission_id',\n 'permission_realm_id',\n );\n }\n\n // Roles (Global + Realm)\n const roles = [\n ...await this.roleResolver.resolveGlobal(\n input.relations && input.relations.globalRoles,\n ),\n ...(attributes.realm_id ?\n await this.roleResolver.resolveRealm(\n input.relations && input.relations.realmRoles,\n attributes.realm_id,\n ) :\n []),\n ];\n\n if (roles.length > 0) {\n await this.roleJunction.synchronize(\n attributes,\n roles,\n 'role_id',\n 'role_realm_id',\n );\n }\n\n // Containers\n\n if (input.relations && input.relations.permissions) {\n const children = input.relations.permissions.map((child) => {\n child.attributes.client_id = attributes.id;\n child.attributes.client = attributes;\n return child;\n });\n\n await this.permissionSynchronizer.synchronizeMany(children);\n }\n\n if (input.relations && input.relations.roles) {\n const children = input.relations.roles.map((child) => {\n child.attributes.client_id = attributes.id;\n child.attributes.client = attributes;\n return child;\n });\n\n await this.roleSynchronizer.synchronizeMany(children);\n }\n\n return {\n ...input,\n attributes,\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"module.mjs","names":["pickRecord","ProvisioningEntityStrategyType","normalizeEntityProvisioningStrategy","BaseProvisioningSynchronizer","ProvisioningEntityResolver","ProvisioningJunctionSynchronizer","ClientProvisioningSynchronizer","clientRepository","permissionResolver","roleResolver","permissionJunction","roleJunction","permissionSynchronizer","roleSynchronizer","ctx","permissionRepository","roleRepository","repository","clientPermissionRepository","ownerKey","ownerRealmKey","clientRoleRepository","synchronize","input","canonicalizeName","attributes","strategy","findOneBy","name","realm_id","type","ABSENT","remove","MERGE","merge","save","REPLACE","id","create","permissions","resolveGlobal","relations","globalPermissions","resolveRealm","realmPermissions","length","roles","globalRoles","realmRoles","children","map","child","client_id","client","synchronizeMany"],"sources":["../../../../../src/core/provisioning/synchronizer/client/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2026-2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type {\n ClientPermission, \n ClientRole, \n Permission, \n Role,\n} from '@authup/core-kit';\nimport { pickRecord } from '@authup/kit';\nimport type { IClientRepository } from '../../../entities/index.ts';\nimport type { ClientProvisioningEntity } from '../../entities/client';\nimport type { PermissionProvisioningEntity } from '../../entities/permission';\nimport type { RoleProvisioningEntity } from '../../entities/role';\nimport { ProvisioningEntityStrategyType, normalizeEntityProvisioningStrategy } from '../../strategy/index.ts';\nimport type { IProvisioningSynchronizer } from '../../types.ts';\nimport { BaseProvisioningSynchronizer } from '../base.ts';\nimport { ProvisioningEntityResolver } from '../entity-resolver.ts';\nimport { ProvisioningJunctionSynchronizer } from '../junction-synchronizer.ts';\nimport type { ClientProvisioningSynchronizerContext } from './types.ts';\n\nexport class ClientProvisioningSynchronizer extends BaseProvisioningSynchronizer<ClientProvisioningEntity> {\n protected clientRepository: IClientRepository;\n\n protected permissionResolver: ProvisioningEntityResolver<Permission>;\n\n protected roleResolver: ProvisioningEntityResolver<Role>;\n\n protected permissionJunction: ProvisioningJunctionSynchronizer<ClientPermission>;\n\n protected roleJunction: ProvisioningJunctionSynchronizer<ClientRole>;\n\n protected permissionSynchronizer: IProvisioningSynchronizer<PermissionProvisioningEntity>;\n\n protected roleSynchronizer: IProvisioningSynchronizer<RoleProvisioningEntity>;\n\n constructor(ctx: ClientProvisioningSynchronizerContext) {\n super();\n\n this.clientRepository = ctx.clientRepository;\n\n this.permissionResolver = new ProvisioningEntityResolver(ctx.permissionRepository);\n this.roleResolver = new ProvisioningEntityResolver(ctx.roleRepository);\n\n this.permissionJunction = new ProvisioningJunctionSynchronizer({\n repository: ctx.clientPermissionRepository,\n ownerKey: 'client_id',\n ownerRealmKey: 'client_realm_id',\n });\n this.roleJunction = new ProvisioningJunctionSynchronizer({\n repository: ctx.clientRoleRepository,\n ownerKey: 'client_id',\n ownerRealmKey: 'client_realm_id',\n });\n\n this.permissionSynchronizer = ctx.permissionSynchronizer;\n this.roleSynchronizer = ctx.roleSynchronizer;\n }\n\n async synchronize(input: ClientProvisioningEntity): Promise<ClientProvisioningEntity> {\n this.canonicalizeName(input.attributes);\n\n const strategy = normalizeEntityProvisioningStrategy(input.strategy);\n\n let attributes = await this.clientRepository.findOneBy({\n name: input.attributes.name,\n realm_id: input.attributes.realm_id || null,\n });\n\n if (strategy.type === ProvisioningEntityStrategyType.ABSENT) {\n if (attributes) {\n await this.clientRepository.remove(attributes);\n }\n return {\n ...input,\n attributes: attributes || input.attributes, \n };\n }\n\n if (attributes) {\n switch (strategy.type) {\n case ProvisioningEntityStrategyType.MERGE:\n attributes = this.clientRepository.merge(\n attributes,\n strategy.attributes ?\n pickRecord(input.attributes, strategy.attributes) :\n input.attributes,\n );\n attributes = await this.clientRepository.save(attributes);\n break;\n case ProvisioningEntityStrategyType.REPLACE:\n input.attributes.id = attributes.id;\n attributes = await this.clientRepository.save(this.clientRepository.create(input.attributes));\n break;\n }\n } else {\n attributes = await this.clientRepository.save(this.clientRepository.create(input.attributes));\n }\n\n // Permissions (Global + Realm)\n const permissions = [\n ...await this.permissionResolver.resolveGlobal(\n input.relations && input.relations.globalPermissions,\n ),\n ...(attributes.realm_id ?\n await this.permissionResolver.resolveRealm(\n input.relations && input.relations.realmPermissions,\n attributes.realm_id,\n ) :\n []),\n ];\n\n if (permissions.length > 0) {\n await this.permissionJunction.synchronize(\n attributes,\n permissions,\n 'permission_id',\n 'permission_realm_id',\n );\n }\n\n // Roles (Global + Realm)\n const roles = [\n ...await this.roleResolver.resolveGlobal(\n input.relations && input.relations.globalRoles,\n ),\n ...(attributes.realm_id ?\n await this.roleResolver.resolveRealm(\n input.relations && input.relations.realmRoles,\n attributes.realm_id,\n ) :\n []),\n ];\n\n if (roles.length > 0) {\n await this.roleJunction.synchronize(\n attributes,\n roles,\n 'role_id',\n 'role_realm_id',\n );\n }\n\n // Containers\n\n if (input.relations && input.relations.permissions) {\n const children = input.relations.permissions.map((child) => {\n child.attributes.client_id = attributes.id;\n child.attributes.client = attributes;\n return child;\n });\n\n await this.permissionSynchronizer.synchronizeMany(children);\n }\n\n if (input.relations && input.relations.roles) {\n const children = input.relations.roles.map((child) => {\n child.attributes.client_id = attributes.id;\n child.attributes.client = attributes;\n return child;\n });\n\n await this.roleSynchronizer.synchronizeMany(children);\n }\n\n return {\n ...input,\n attributes,\n };\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"entity-resolver.d.ts","sourceRoot":"","sources":["../../../../src/core/provisioning/synchronizer/entity-resolver.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,qBAAa,0BAA0B,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa;IAC3E,SAAS,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;gBAE/B,UAAU,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAItC,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAoB7C,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAoBxE,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;CAmBpG"}
1
+ {"version":3,"file":"entity-resolver.d.ts","sourceRoot":"","sources":["../../../../src/core/provisioning/synchronizer/entity-resolver.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,qBAAa,0BAA0B,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa;IAC3E,SAAS,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;gBAE/B,UAAU,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAItC,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAsB7C,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAsBxE,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;CAqBpG"}
@@ -9,6 +9,7 @@ var ProvisioningEntityResolver = class {
9
9
  }
10
10
  async resolveGlobal(names) {
11
11
  if (!names || names.length === 0) return [];
12
+ names = names.map((name) => name.trim().toLowerCase());
12
13
  if (names.includes("*")) return this.repository.findManyBy({
13
14
  realm_id: null,
14
15
  client_id: null
@@ -21,6 +22,7 @@ var ProvisioningEntityResolver = class {
21
22
  }
22
23
  async resolveRealm(names, realmId) {
23
24
  if (!names || names.length === 0) return [];
25
+ names = names.map((name) => name.trim().toLowerCase());
24
26
  if (names.includes("*")) return this.repository.findManyBy({
25
27
  realm_id: realmId,
26
28
  client_id: null
@@ -33,6 +35,7 @@ var ProvisioningEntityResolver = class {
33
35
  }
34
36
  async resolveClient(names, realmId, clientId) {
35
37
  if (!names || names.length === 0) return [];
38
+ names = names.map((name) => name.trim().toLowerCase());
36
39
  if (names.includes("*")) return this.repository.findManyBy({
37
40
  realm_id: realmId,
38
41
  client_id: clientId