@jantstack/adonis-authz 1.1.0 → 2.4.0-alpha.1

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 (236) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +837 -51
  3. package/build/commands/authz_catalog_diff.d.ts +28 -0
  4. package/build/commands/authz_catalog_diff.d.ts.map +1 -0
  5. package/build/commands/authz_catalog_diff.js +67 -0
  6. package/build/commands/authz_catalog_diff.js.map +1 -0
  7. package/build/commands/authz_catalog_prune_orphans.d.ts +78 -0
  8. package/build/commands/authz_catalog_prune_orphans.d.ts.map +1 -0
  9. package/build/commands/authz_catalog_prune_orphans.js +136 -0
  10. package/build/commands/authz_catalog_prune_orphans.js.map +1 -0
  11. package/build/commands/authz_catalog_sync.d.ts +37 -0
  12. package/build/commands/authz_catalog_sync.d.ts.map +1 -0
  13. package/build/commands/authz_catalog_sync.js +81 -0
  14. package/build/commands/authz_catalog_sync.js.map +1 -0
  15. package/build/commands/authz_freeze.d.ts +44 -0
  16. package/build/commands/authz_freeze.d.ts.map +1 -0
  17. package/build/commands/authz_freeze.js +95 -0
  18. package/build/commands/authz_freeze.js.map +1 -0
  19. package/build/commands/authz_reconcile.d.ts +102 -0
  20. package/build/commands/authz_reconcile.d.ts.map +1 -0
  21. package/build/commands/authz_reconcile.js +294 -0
  22. package/build/commands/authz_reconcile.js.map +1 -0
  23. package/build/commands/authz_relations_reconcile.d.ts +73 -0
  24. package/build/commands/authz_relations_reconcile.d.ts.map +1 -0
  25. package/build/commands/authz_relations_reconcile.js +225 -0
  26. package/build/commands/authz_relations_reconcile.js.map +1 -0
  27. package/build/commands/authz_scopes_relay.d.ts +47 -0
  28. package/build/commands/authz_scopes_relay.d.ts.map +1 -0
  29. package/build/commands/authz_scopes_relay.js +141 -0
  30. package/build/commands/authz_scopes_relay.js.map +1 -0
  31. package/build/commands/authz_unfreeze.d.ts +37 -0
  32. package/build/commands/authz_unfreeze.d.ts.map +1 -0
  33. package/build/commands/authz_unfreeze.js +92 -0
  34. package/build/commands/authz_unfreeze.js.map +1 -0
  35. package/build/commands/main.d.ts +8 -1
  36. package/build/commands/main.d.ts.map +1 -1
  37. package/build/commands/main.js +8 -1
  38. package/build/commands/main.js.map +1 -1
  39. package/build/commands/openfga_provision.d.ts +46 -4
  40. package/build/commands/openfga_provision.d.ts.map +1 -1
  41. package/build/commands/openfga_provision.js +91 -7
  42. package/build/commands/openfga_provision.js.map +1 -1
  43. package/build/configure.d.ts +11 -0
  44. package/build/configure.d.ts.map +1 -1
  45. package/build/configure.js +37 -1
  46. package/build/configure.js.map +1 -1
  47. package/build/index.d.ts +75 -7
  48. package/build/index.d.ts.map +1 -1
  49. package/build/index.js +67 -5
  50. package/build/index.js.map +1 -1
  51. package/build/providers/authz_provider.d.ts +26 -2
  52. package/build/providers/authz_provider.d.ts.map +1 -1
  53. package/build/providers/authz_provider.js +48 -2
  54. package/build/providers/authz_provider.js.map +1 -1
  55. package/build/services/relations.d.ts +14 -0
  56. package/build/services/relations.d.ts.map +1 -0
  57. package/build/services/relations.js +17 -0
  58. package/build/services/relations.js.map +1 -0
  59. package/build/src/catalog/catalog.d.ts +289 -0
  60. package/build/src/catalog/catalog.d.ts.map +1 -0
  61. package/build/src/catalog/catalog.js +859 -0
  62. package/build/src/catalog/catalog.js.map +1 -0
  63. package/build/src/catalog/catalog_cache.d.ts +324 -0
  64. package/build/src/catalog/catalog_cache.d.ts.map +1 -0
  65. package/build/src/catalog/catalog_cache.js +666 -0
  66. package/build/src/catalog/catalog_cache.js.map +1 -0
  67. package/build/src/clock.d.ts +24 -0
  68. package/build/src/clock.d.ts.map +1 -0
  69. package/build/src/clock.js +7 -0
  70. package/build/src/clock.js.map +1 -0
  71. package/build/src/define_config.d.ts +201 -7
  72. package/build/src/define_config.d.ts.map +1 -1
  73. package/build/src/define_config.js.map +1 -1
  74. package/build/src/drivers/database_driver.d.ts +324 -15
  75. package/build/src/drivers/database_driver.d.ts.map +1 -1
  76. package/build/src/drivers/database_driver.js +1107 -128
  77. package/build/src/drivers/database_driver.js.map +1 -1
  78. package/build/src/drivers/database_relations_driver.d.ts +75 -0
  79. package/build/src/drivers/database_relations_driver.d.ts.map +1 -0
  80. package/build/src/drivers/database_relations_driver.js +450 -0
  81. package/build/src/drivers/database_relations_driver.js.map +1 -0
  82. package/build/src/drivers/openfga_driver.d.ts +963 -55
  83. package/build/src/drivers/openfga_driver.d.ts.map +1 -1
  84. package/build/src/drivers/openfga_driver.js +2693 -372
  85. package/build/src/drivers/openfga_driver.js.map +1 -1
  86. package/build/src/drivers/openfga_facts.d.ts +369 -0
  87. package/build/src/drivers/openfga_facts.d.ts.map +1 -0
  88. package/build/src/drivers/openfga_facts.js +813 -0
  89. package/build/src/drivers/openfga_facts.js.map +1 -0
  90. package/build/src/drivers/openfga_relations_driver.d.ts +120 -0
  91. package/build/src/drivers/openfga_relations_driver.d.ts.map +1 -0
  92. package/build/src/drivers/openfga_relations_driver.js +466 -0
  93. package/build/src/drivers/openfga_relations_driver.js.map +1 -0
  94. package/build/src/errors.d.ts +586 -0
  95. package/build/src/errors.d.ts.map +1 -1
  96. package/build/src/errors.js +583 -0
  97. package/build/src/errors.js.map +1 -1
  98. package/build/src/expiry.d.ts +27 -0
  99. package/build/src/expiry.d.ts.map +1 -0
  100. package/build/src/expiry.js +50 -0
  101. package/build/src/expiry.js.map +1 -0
  102. package/build/src/freeze.d.ts +120 -0
  103. package/build/src/freeze.d.ts.map +1 -0
  104. package/build/src/freeze.js +172 -0
  105. package/build/src/freeze.js.map +1 -0
  106. package/build/src/hierarchical_resolver.d.ts +56 -0
  107. package/build/src/hierarchical_resolver.d.ts.map +1 -0
  108. package/build/src/hierarchical_resolver.js +87 -0
  109. package/build/src/hierarchical_resolver.js.map +1 -0
  110. package/build/src/{middleware → http}/app_access_middleware.d.ts +8 -6
  111. package/build/src/http/app_access_middleware.d.ts.map +1 -0
  112. package/build/src/{middleware → http}/app_access_middleware.js +10 -26
  113. package/build/src/http/app_access_middleware.js.map +1 -0
  114. package/build/src/http/resource_access_middleware.d.ts +105 -0
  115. package/build/src/http/resource_access_middleware.d.ts.map +1 -0
  116. package/build/src/http/resource_access_middleware.js +81 -0
  117. package/build/src/http/resource_access_middleware.js.map +1 -0
  118. package/build/src/identity.d.ts +228 -0
  119. package/build/src/identity.d.ts.map +1 -0
  120. package/build/src/identity.js +457 -0
  121. package/build/src/identity.js.map +1 -0
  122. package/build/src/manager.d.ts +536 -7
  123. package/build/src/manager.d.ts.map +1 -1
  124. package/build/src/manager.js +2676 -23
  125. package/build/src/manager.js.map +1 -1
  126. package/build/src/memoize_ancestors.d.ts +23 -0
  127. package/build/src/memoize_ancestors.d.ts.map +1 -0
  128. package/build/src/memoize_ancestors.js +42 -0
  129. package/build/src/memoize_ancestors.js.map +1 -0
  130. package/build/src/models/authz_assignment.d.ts +11 -11
  131. package/build/src/models/authz_assignment.d.ts.map +1 -1
  132. package/build/src/models/authz_deny.d.ts +11 -11
  133. package/build/src/models/authz_deny.d.ts.map +1 -1
  134. package/build/src/models/authz_permission.d.ts +17 -11
  135. package/build/src/models/authz_permission.d.ts.map +1 -1
  136. package/build/src/models/authz_permission.js +4 -0
  137. package/build/src/models/authz_permission.js.map +1 -1
  138. package/build/src/models/authz_role.d.ts +19 -12
  139. package/build/src/models/authz_role.d.ts.map +1 -1
  140. package/build/src/models/authz_role.js +6 -1
  141. package/build/src/models/authz_role.js.map +1 -1
  142. package/build/src/models/authz_role_permission.d.ts +11 -11
  143. package/build/src/models/authz_role_permission.d.ts.map +1 -1
  144. package/build/src/openfga.d.ts +29 -0
  145. package/build/src/openfga.d.ts.map +1 -0
  146. package/build/src/openfga.js +26 -0
  147. package/build/src/openfga.js.map +1 -0
  148. package/build/src/reconcile.d.ts +37 -0
  149. package/build/src/reconcile.d.ts.map +1 -0
  150. package/build/src/reconcile.js +69 -0
  151. package/build/src/reconcile.js.map +1 -0
  152. package/build/src/relation_partition_trigger.d.ts +8 -0
  153. package/build/src/relation_partition_trigger.d.ts.map +1 -0
  154. package/build/src/relation_partition_trigger.js +85 -0
  155. package/build/src/relation_partition_trigger.js.map +1 -0
  156. package/build/src/relations/define_relations_config.d.ts +58 -0
  157. package/build/src/relations/define_relations_config.d.ts.map +1 -0
  158. package/build/src/relations/define_relations_config.js +144 -0
  159. package/build/src/relations/define_relations_config.js.map +1 -0
  160. package/build/src/relations/manager.d.ts +38 -0
  161. package/build/src/relations/manager.d.ts.map +1 -0
  162. package/build/src/relations/manager.js +156 -0
  163. package/build/src/relations/manager.js.map +1 -0
  164. package/build/src/relations/reconcile.d.ts +62 -0
  165. package/build/src/relations/reconcile.d.ts.map +1 -0
  166. package/build/src/relations/reconcile.js +138 -0
  167. package/build/src/relations/reconcile.js.map +1 -0
  168. package/build/src/relations_config_store.d.ts +22 -0
  169. package/build/src/relations_config_store.d.ts.map +1 -0
  170. package/build/src/relations_config_store.js +74 -0
  171. package/build/src/relations_config_store.js.map +1 -0
  172. package/build/src/scope_outbox.d.ts +69 -0
  173. package/build/src/scope_outbox.d.ts.map +1 -0
  174. package/build/src/scope_outbox.js +291 -0
  175. package/build/src/scope_outbox.js.map +1 -0
  176. package/build/src/shared/backend_guard.d.ts +106 -0
  177. package/build/src/shared/backend_guard.d.ts.map +1 -0
  178. package/build/src/shared/backend_guard.js +246 -0
  179. package/build/src/shared/backend_guard.js.map +1 -0
  180. package/build/src/shared/sql_expiry.d.ts +53 -0
  181. package/build/src/shared/sql_expiry.d.ts.map +1 -0
  182. package/build/src/shared/sql_expiry.js +66 -0
  183. package/build/src/shared/sql_expiry.js.map +1 -0
  184. package/build/src/sql_descendants.d.ts +97 -0
  185. package/build/src/sql_descendants.d.ts.map +1 -0
  186. package/build/src/sql_descendants.js +203 -0
  187. package/build/src/sql_descendants.js.map +1 -0
  188. package/build/src/testing/contract.d.ts +212 -7
  189. package/build/src/testing/contract.d.ts.map +1 -1
  190. package/build/src/testing/contract.js +3449 -24
  191. package/build/src/testing/contract.js.map +1 -1
  192. package/build/src/testing/main.d.ts +10 -2
  193. package/build/src/testing/main.d.ts.map +1 -1
  194. package/build/src/testing/main.js +5 -1
  195. package/build/src/testing/main.js.map +1 -1
  196. package/build/src/testing/migration_contract.d.ts +284 -0
  197. package/build/src/testing/migration_contract.d.ts.map +1 -0
  198. package/build/src/testing/migration_contract.js +586 -0
  199. package/build/src/testing/migration_contract.js.map +1 -0
  200. package/build/src/testing/relations_contract.d.ts +51 -0
  201. package/build/src/testing/relations_contract.d.ts.map +1 -0
  202. package/build/src/testing/relations_contract.js +654 -0
  203. package/build/src/testing/relations_contract.js.map +1 -0
  204. package/build/src/testing/relations_reconcile_contract.d.ts +24 -0
  205. package/build/src/testing/relations_reconcile_contract.d.ts.map +1 -0
  206. package/build/src/testing/relations_reconcile_contract.js +172 -0
  207. package/build/src/testing/relations_reconcile_contract.js.map +1 -0
  208. package/build/src/testing/scope_tree.d.ts +65 -0
  209. package/build/src/testing/scope_tree.d.ts.map +1 -0
  210. package/build/src/testing/scope_tree.js +145 -0
  211. package/build/src/testing/scope_tree.js.map +1 -0
  212. package/build/src/traits/authz_scopes.d.ts +30 -6
  213. package/build/src/traits/authz_scopes.d.ts.map +1 -1
  214. package/build/src/traits/authz_scopes.js +30 -18
  215. package/build/src/traits/authz_scopes.js.map +1 -1
  216. package/build/src/traits/has_uuid.d.ts +12 -12
  217. package/build/src/traits/has_uuid.d.ts.map +1 -1
  218. package/build/src/types.d.ts +1313 -28
  219. package/build/src/types.d.ts.map +1 -1
  220. package/build/src/types.js +20 -4
  221. package/build/src/types.js.map +1 -1
  222. package/build/stubs/config/app_acl.stub +4 -2
  223. package/build/stubs/config/authorization.stub +168 -14
  224. package/build/stubs/migration.stub +183 -13
  225. package/build/stubs/scopes_outbox_migration.stub +57 -0
  226. package/package.json +14 -7
  227. package/build/commands/openfga_import.d.ts +0 -28
  228. package/build/commands/openfga_import.d.ts.map +0 -1
  229. package/build/commands/openfga_import.js +0 -74
  230. package/build/commands/openfga_import.js.map +0 -1
  231. package/build/src/catalog.d.ts +0 -3
  232. package/build/src/catalog.d.ts.map +0 -1
  233. package/build/src/catalog.js +0 -103
  234. package/build/src/catalog.js.map +0 -1
  235. package/build/src/middleware/app_access_middleware.d.ts.map +0 -1
  236. package/build/src/middleware/app_access_middleware.js.map +0 -1
@@ -0,0 +1,105 @@
1
+ import type { HttpContext } from '@adonisjs/core/http';
2
+ import type { NextFn } from '@adonisjs/core/types/http';
3
+ import type { ScopeRef } from '../types.js';
4
+ /**
5
+ * Enforcement de acceso A UN RECURSO concreto. COMPONE el `authorize` que ya
6
+ * existe: el consumidor dice CÓMO cargar el recurso (`load(ctx, id)`) y en qué
7
+ * `{ scope }` vive, y el middleware pregunta al motor sobre ese scope. No es un
8
+ * modelo ni un driver nuevo: es el borde HTTP del scope de un recurso.
9
+ *
10
+ * El ORDEN de las respuestas ES la propiedad de seguridad — un 403 donde
11
+ * debería ir un 404 filtra qué recursos existen (enumeración):
12
+ *
13
+ * 1. 401 si no hay usuario autenticado (antes de nada).
14
+ * 2. 403 si el `gate(ctx)` del consumidor (una ability previa, p. ej.
15
+ * «es admin del tenant») lo niega.
16
+ * 3. 404 si el CONTENEDOR no existe (`containerParam`, cuando se declara).
17
+ * 4. 404 si el RECURSO no existe (`load` ⇒ null) — MISMO cuerpo que el 404
18
+ * del contenedor, para no distinguir «no existe» de «no es tuyo».
19
+ * 5. authorize sobre el `{ scope }` que devolvió `load`, con `permission`
20
+ * (o `readPermission` en métodos seguros): un `false` aquí también
21
+ * es 404 con el MISMO cuerpo, NO un 403 — que no puedas verlo no
22
+ * revela que existe.
23
+ *
24
+ * Invariantes que NO se negocian:
25
+ * - `AuthorizationBackendError` (503) NUNCA se disfraza de 404/403: si el
26
+ * backend cae (`gate`/`load`/`authorize` lanzan), el error sube tal cual.
27
+ * Es lo que distingue «denegado» de «no pude comprobar». El middleware NO
28
+ * envuelve estas llamadas en try/catch a propósito.
29
+ * - Prohibido `role`: como `appAccess`, solo acepta `permission`/
30
+ * `readPermission`. `{ role }` es 500 con receta (membresía no es acceso, y
31
+ * el deny no la gobierna: toda decisión pasa por `authorize`).
32
+ * - Sin segundo `authorize`: se llama UNA sola vez. No re-autoriza.
33
+ *
34
+ * Limitación conocida — canal de TEMPORIZACIÓN (auditor Fase 5, ⚪): el status
35
+ * y el cuerpo de «no existe» y «existe pero no es tuyo» son idénticos, pero el
36
+ * TIEMPO no: un id inexistente responde 404 sin round-trip a `authorize`; uno
37
+ * ajeno responde el mismo 404 tras esa llamada. Es inherente a load→authorize
38
+ * (no se puede autorizar el scope de algo que no se ha cargado), no un defecto;
39
+ * quien necesite cerrar el canal iguala el tiempo en su capa (delay constante),
40
+ * no en el middleware.
41
+ *
42
+ * Pureza: el middleware NO importa aliases del consumidor; `load`/`gate` llegan
43
+ * INYECTADOS en las opciones de la ruta.
44
+ *
45
+ * Uso:
46
+ * router
47
+ * .get('/orgs/:orgId/documents/:id', [DocumentsController, 'show'])
48
+ * .use(middleware.resourceAccess({
49
+ * resource: 'document',
50
+ * param: 'id',
51
+ * containerParam: 'orgId',
52
+ * permission: 'documents:write',
53
+ * readPermission: 'documents:read',
54
+ * load: (ctx, id) => Document.query().where('id', id).first(),
55
+ * gate: (ctx) => ctx.auth.user!.isMemberOf(ctx.params.orgId),
56
+ * }))
57
+ */
58
+ export interface ResourceAccessOptions {
59
+ /**
60
+ * Nombre del recurso. Es la clave bajo la que el recurso cargado (con su
61
+ * scope) se PROPAGA al `ctx` para el controlador (`ctx[resource]`), así que
62
+ * dos `resourceAccess` anidados no se pisan.
63
+ */
64
+ resource: string;
65
+ /** Nombre del param de ruta que trae el id del recurso (`ctx.params[param]`). */
66
+ param: string;
67
+ /**
68
+ * Nombre del param de ruta del contenedor (p. ej. el tenant de una ruta
69
+ * anidada). Cuando se declara, un contenedor ausente en la ruta es 404 —el
70
+ * MISMO cuerpo que un recurso ausente— y se comprueba ANTES de cargar el
71
+ * recurso. La pertenencia real del recurso al contenedor la comprueba
72
+ * `load` (que ve `ctx.params`): devolver `null` allí es el mismo 404, así que
73
+ * «no existe» y «no está en este contenedor» son indistinguibles.
74
+ */
75
+ containerParam?: string;
76
+ /** Permiso requerido (métodos que mutan, y lectura si no hay `readPermission`). */
77
+ permission: string;
78
+ /**
79
+ * Permiso requerido en métodos SEGUROS (GET/HEAD). Si se omite, una lectura
80
+ * exige `permission` — nunca es gratis. Suele ser el permiso MENOS
81
+ * privilegiado (ver vs escribir).
82
+ */
83
+ readPermission?: string;
84
+ /**
85
+ * Carga el recurso por su id y devuelve dónde vive (`{ scope }`) o `null` si
86
+ * no existe / no es visible. Del consumidor: ve el `ctx` entero (params,
87
+ * usuario). Un throw es «no pude comprobar» (sube como 503), NO un 404.
88
+ */
89
+ load: (ctx: HttpContext, id: string) => Promise<{
90
+ scope: ScopeRef;
91
+ } | null> | {
92
+ scope: ScopeRef;
93
+ } | null;
94
+ /**
95
+ * Ability previa del consumidor (p. ej. «es admin del tenant»). Opcional; si
96
+ * se declara y devuelve `false`, es 403 —una restricción ADICIONAL, evaluada
97
+ * antes de tocar el recurso—. Si se omite, no hay pre-check: la seguridad la
98
+ * da igualmente `authorize`.
99
+ */
100
+ gate?: (ctx: HttpContext) => Promise<boolean> | boolean;
101
+ }
102
+ export default class ResourceAccessMiddleware {
103
+ handle(ctx: HttpContext, next: NextFn, options: ResourceAccessOptions): Promise<any>;
104
+ }
105
+ //# sourceMappingURL=resource_access_middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource_access_middleware.d.ts","sourceRoot":"","sources":["../../../src/http/resource_access_middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAGvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAG3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,mFAAmF;IACnF,UAAU,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,IAAI,EAAE,CACJ,GAAG,EAAE,WAAW,EAChB,EAAE,EAAE,MAAM,KACP,OAAO,CAAC;QAAE,KAAK,EAAE,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAC,GAAG;QAAE,KAAK,EAAE,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAA;IACrE;;;;;OAKG;IACH,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;CACxD;AAKD,MAAM,CAAC,OAAO,OAAO,wBAAwB;IACrC,MAAM,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB;CA6F5E"}
@@ -0,0 +1,81 @@
1
+ import { Exception } from '@adonisjs/core/exceptions';
2
+ import authorization from '../../services/main.js';
3
+ import { AuthorizationConfigError, RoleIsNotAccessError } from '../errors.js';
4
+ /** Métodos HTTP seguros: usan `readPermission` si se declara. */
5
+ const SAFE_METHODS = new Set(['GET', 'HEAD']);
6
+ export default class ResourceAccessMiddleware {
7
+ async handle(ctx, next, options) {
8
+ // Config PRIMERO, antes de mirar si hay usuario: una ruta mal declarada
9
+ // tiene que saltar en el primer request de desarrollo, autenticado o no.
10
+ if (options && 'role' in options) {
11
+ throw new RoleIsNotAccessError(options.role);
12
+ }
13
+ if (!options?.permission) {
14
+ throw new AuthorizationConfigError("middleware.resourceAccess requiere `{ permission }` (p. ej. resourceAccess({ permission: 'documents:write', ... }))");
15
+ }
16
+ if (!options.param) {
17
+ throw new AuthorizationConfigError('middleware.resourceAccess requiere `{ param }` — el nombre del param de ruta con el id del recurso');
18
+ }
19
+ if (typeof options.load !== 'function') {
20
+ throw new AuthorizationConfigError('middleware.resourceAccess requiere `load(ctx, id)` — cómo cargar el recurso y en qué `{ scope }` vive');
21
+ }
22
+ if (!options.resource) {
23
+ throw new AuthorizationConfigError('middleware.resourceAccess requiere `{ resource }` — el nombre bajo el que se propaga el recurso al controlador');
24
+ }
25
+ // ── 1. 401 ────────────────────────────────────────────────────────────
26
+ const holder = ctx.auth?.user;
27
+ if (!holder) {
28
+ return ctx.response.unauthorized({ message: 'No autenticado', statusCode: 401, errors: [] });
29
+ }
30
+ // El holder tiene que ser un subject válido (mismos guards que appAccess).
31
+ // Son errores de programación (500), no de acceso: nunca degradan a 404.
32
+ const morph = holder.__morphMapName;
33
+ if (!morph) {
34
+ throw new Exception('El modelo autenticado no tiene @MorphMap — no es un holder válido', {
35
+ status: 500,
36
+ });
37
+ }
38
+ if (!holder.uuid) {
39
+ throw new Exception(`El holder '${morph}' no expone 'uuid' — el motor identifica a los holders por uuid, ` +
40
+ `no por la clave primaria del modelo.`, { status: 500 });
41
+ }
42
+ const subject = { type: morph, uuid: holder.uuid };
43
+ // ── 2. 403 gate del consumidor ────────────────────────────────────────
44
+ // Sin try/catch: si el gate lanza (backend caído), el 503 sube tal cual.
45
+ if (options.gate && !(await options.gate(ctx))) {
46
+ return ctx.response.forbidden({ message: 'Permiso insuficiente', statusCode: 403, errors: [] });
47
+ }
48
+ // El 404 del contenedor, del recurso ausente y del authorize negativo
49
+ // comparten EXACTAMENTE el mismo cuerpo: nada distingue los tres.
50
+ const notFound = () => ctx.response.notFound({ message: 'Recurso no encontrado', statusCode: 404, errors: [] });
51
+ // ── 3. 404 contenedor (ANTES que el recurso) ──────────────────────────
52
+ if (options.containerParam) {
53
+ const containerId = ctx.params?.[options.containerParam];
54
+ if (!containerId)
55
+ return notFound();
56
+ }
57
+ // ── 4. 404 recurso ────────────────────────────────────────────────────
58
+ const id = ctx.params?.[options.param];
59
+ if (!id)
60
+ return notFound();
61
+ // Un throw de `load` NO se atrapa: «no pude comprobar» (503) ≠ «no existe».
62
+ const loaded = await options.load(ctx, String(id));
63
+ if (!loaded)
64
+ return notFound();
65
+ // ── 5. authorize (UNA vez) sobre el scope del recurso ─────────────────
66
+ const method = String(ctx.request?.method?.() ?? 'GET').toUpperCase();
67
+ const permission = SAFE_METHODS.has(method) && options.readPermission
68
+ ? options.readPermission
69
+ : options.permission;
70
+ if (!(await authorization.authorize(subject, permission, loaded.scope))) {
71
+ // Negado ⇒ 404 con el MISMO cuerpo, NO 403: que no puedas verlo no
72
+ // revela que existe.
73
+ return notFound();
74
+ }
75
+ // El recurso cargado (con su scope) llega al controlador sin recargarlo.
76
+ ;
77
+ ctx[options.resource] = loaded;
78
+ return next();
79
+ }
80
+ }
81
+ //# sourceMappingURL=resource_access_middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource_access_middleware.js","sourceRoot":"","sources":["../../../src/http/resource_access_middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,228 @@
1
+ import type { NormalizedRoleQuery, RoleQuery, ScopeRef, SubjectRef } from './types.js';
2
+ /**
3
+ * Validación de identidad del motor: holder, scope, rol y permiso.
4
+ *
5
+ * Vive en un solo sitio y la aplican el manager (una vez por llamada) Y los
6
+ * drivers (defensa en profundidad: el juez del contrato habla con el driver
7
+ * directamente, y un driver de terceros puede usarse sin manager). Nadie
8
+ * duplica la regla: se importa.
9
+ *
10
+ * Por qué es 422 y no "se acepta lo que venga":
11
+ * - `uuid: undefined` en el driver openfga escribía `user:undefined`, y a
12
+ * partir de ahí CUALQUIER holder sin uuid heredaba ese permiso (L0.5).
13
+ * - `{ type: 'app', uuid: 'X' }` colapsaba a la raíz global en openfga y no
14
+ * en database: escalada de tenant a plataforma en un driver y `false` en
15
+ * el otro (L0.10).
16
+ * - `#`, `:`, `|`, `~`, `*` y espacios son sintaxis de FGA o separadores de
17
+ * los ids de binding: un componente que los contenga puede fabricar un
18
+ * userset, un comodín o la clave de OTRO scope (L0.5, L0.8). Desde 3A
19
+ * (2.2) el slug ya NO viaja en los ids de FGA (van por uuid de catálogo),
20
+ * pero los tipos y uuids de scope sí, y los slugs serán relaciones del
21
+ * modelo `facts` (3b): la lista blanca se mantiene entera.
22
+ * - Las longitudes son las de las columnas `authz_*`: lo que no cabe se
23
+ * truncaría o fallaría con un error de SQL ilegible.
24
+ */
25
+ /**
26
+ * Longitudes de columna del esquema publicado (`stubs/migration.stub`) que
27
+ * la identidad aplica tal cual. El slug NO está aquí: su límite es
28
+ * `MAX_SLUG_LENGTH` (42), más estricto que la columna (100) para que el
29
+ * catálogo sea publicable en FGA (E2).
30
+ */
31
+ export declare const IDENTITY_LIMITS: Readonly<{
32
+ holderType: 50;
33
+ scopeType: 20;
34
+ uuid: 36;
35
+ }>;
36
+ /**
37
+ * Centinela con el que el driver `database` almacena el uuid del scope `app`
38
+ * (NOT NULL + unique). Fuera de `app` sería una identidad de scope que
39
+ * colisiona con la raíz: se rechaza (L0.15).
40
+ */
41
+ export declare const SENTINEL_UUID = "00000000-0000-0000-0000-000000000000";
42
+ /** El uuid de un rol o permiso del catálogo (opcional en el spec): canónico y en minúsculas, o 422. */
43
+ export declare function assertCatalogUuid(kind: string, value: unknown): asserts value is string;
44
+ /** Versión booleana para caminos de LECTURA (ids que vienen del backend): no lanza. */
45
+ export declare function isCatalogUuid(value: unknown): value is string;
46
+ /** `SubjectRef` bien formado: type y uuid presentes, con formato y longitud. */
47
+ export declare function assertSubject(subject: SubjectRef): void;
48
+ /**
49
+ * `ScopeRef` bien formado. `app` es la raíz y NO admite uuid (L0.10): un
50
+ * `{ app, 'X' }` no es "otro app", es una identidad que un driver colapsaría
51
+ * a la global. Fuera de `app` el uuid es obligatorio y no puede ser el
52
+ * centinela con el que `database` representa la raíz (L0.15).
53
+ */
54
+ export declare function assertScope(scope: ScopeRef): void;
55
+ /** Tipo de scope suelto (el de `listRoleScopes`). */
56
+ export declare function assertScopeType(scopeType: string): void;
57
+ /**
58
+ * Longitud máxima del id de un objeto/holder de RELACIÓN (columna
59
+ * `varchar(64)` de `authz_relations`). Más ancha que `IDENTITY_LIMITS.uuid`
60
+ * (36): el `object.id` de ReBAC lo elige el consumidor y no tiene por qué ser
61
+ * un uuid.
62
+ */
63
+ export declare const RELATION_ID_LIMIT = 64;
64
+ /**
65
+ * **La gramática del id de un objeto de RELACIÓN** (R-16, Fase 4 · lote 4-7).
66
+ *
67
+ * Un `object.id` de relaciones va en la columna `varchar(64)` de
68
+ * `authz_relations` y, en el store compartido, en el id de objeto de FGA
69
+ * (`document:<partitionKey>|<object.id>`). El driver `database` ya lo validaba
70
+ * (`RELATION_ID_FORMAT`), pero ni el manager ni el driver `openfga` lo hacían:
71
+ * el `openfga` solo medía la LONGITUD. Un `object.id` con un `|` dentro
72
+ * (`'a|b'`) se ESCRIBÍA y `check` lo honraba, pero al parsear de vuelta (corte
73
+ * por el ÚLTIMO `|`) la partición salía mal, así que `listObjects` lo filtraba
74
+ * y `enumerateRelations` lo descartaba: una relación que concede y que NO se ve
75
+ * en la enumeración ni migra —pérdida SILENCIOSA en `reconcile`, justo lo que
76
+ * el driver promete que no pasa— y una divergencia con `database`, que lo
77
+ * rechaza con 422. Y los caracteres estructurales (`:`/`#`/espacio) salían como
78
+ * 503 mal clasificado, cuando por invariante 5 una entrada malformada es 422.
79
+ *
80
+ * Misma lista blanca que el resto de la identidad (`COMPONENT_FORMAT`):
81
+ * minúsculas, dígitos y `._-`; excluye `|`, `#`, `:`, `@`, `*`, `~`, espacios y
82
+ * control. La aplican el manager (una vez por llamada) Y cada driver (defensa
83
+ * en profundidad), como el resto de la gramática.
84
+ */
85
+ export declare function assertRelationId(kind: string, value: unknown): asserts value is string;
86
+ /** Versión booleana para caminos de LECTURA (ids que vienen del backend): no lanza. */
87
+ export declare function isValidRelationId(value: unknown): value is string;
88
+ /**
89
+ * Clave textual de un scope: `app` para la raíz, `<tipo>|<uuid>` para el
90
+ * resto (≤ 57 caracteres). Es la MISMA en todo el paquete: el id de los
91
+ * bindings de FGA (`role_binding:<scopeKey>|<roleUuid>`) y el owner de un
92
+ * rol local (`authz_roles.owner_scope_key`, 3B · B1). `|` es el separador:
93
+ * como ningún componente lo admite (`assertScope`), dos scopes distintos no
94
+ * pueden producir la misma clave, y la clave reservada `global` (el catálogo
95
+ * del config) no la produce ningún scope — la raíz da `app` y todo lo demás
96
+ * lleva `|`. Un scope mal formado (`{app, uuid}`, centinela, mayúsculas) es
97
+ * 422 aquí, antes de serializarse a nada.
98
+ */
99
+ export declare function scopeKey(scope: ScopeRef): string;
100
+ /**
101
+ * **Las ORTOGRAFÍAS de un scope con las que hay que limpiar cuando ya no hay
102
+ * cadena con la que canonizar** (3b-2h · 🟠 3, auditor R2).
103
+ *
104
+ * La identidad de un scope es la canónica que devuelve el resolutor
105
+ * (invariante 17) y el paquete la usa para TODOS los hechos… mientras la fila
106
+ * del consumidor existe. `scopes.detached` es justo la operación que admite
107
+ * que ya NO exista (3F · S1: se purga igual, con el scope tal cual), y ahí no
108
+ * hay con qué canonizar: se usa la ortografía del llamante. Medido contra
109
+ * PostgreSQL: el tipo `uuid` funde `01a0…-…` con el mismo uuid SIN guiones,
110
+ * así que el `DELETE` del controlador acierta con el alias, el `detached`
111
+ * llega con el alias, `purgeScope` demuestra cero sobre un objeto que no
112
+ * existe y el scope REAL conserva su `parent` y su `binding`: en modo
113
+ * `facts`, **concede para siempre**. En lectura el mismo alias es
114
+ * fail-closed; en escritura era fail-OPEN.
115
+ *
116
+ * La salida es no elegir: una purga sin cadena limpia la ortografía del
117
+ * llamante **y** la canónica de la que puede ser alias. Solo se expande el
118
+ * alias SIN guiones (32 hex ⇒ también la forma 8-4-4-4-12), que es la única
119
+ * que un motor produce fundiendo —PostgreSQL canoniza CON guiones; el alias
120
+ * por mayúsculas ya muere en la puerta (`assertScope`)—; así el consumidor
121
+ * que guarda sus uuids con guiones (el caso normal) no paga NADA, y el que
122
+ * los guarda sin guiones sigue purgando su clave y, además, una que nunca
123
+ * tiene hechos. Normalizar en vez de expandir no vale: sin la fila no se sabe
124
+ * cuál de las dos es la buena, y elegir mal deja los hechos vivos, que es el
125
+ * fallo que se está cerrando.
126
+ */
127
+ export declare function scopeSpellings(scope: ScopeRef): ScopeRef[];
128
+ /**
129
+ * La inversa de `scopeKey` para claves que vienen de la BASE (el owner de un
130
+ * rol): `app` ⇒ la raíz; `<tipo>|<uuid>` ⇒ el scope; cualquier otra forma
131
+ * (incluida `global`, que no es un scope) ⇒ `null`. No lanza: es camino de
132
+ * lectura, y una clave que no es un scope simplemente no resuelve.
133
+ */
134
+ export declare function scopeFromKey(key: string): ScopeRef | null;
135
+ /**
136
+ * Nombres que el modelo FGA del modo `facts` usa como relaciones propias
137
+ * (`scope#parent`, `role_binding#assignee`, `scope#denied_<P>`…). Un
138
+ * permiso llamado `parent` invalidaría el modelo entero (S14): se rechaza en
139
+ * el núcleo, para ambos drivers, no el día de la migración.
140
+ */
141
+ export declare const RESERVED_SLUGS: ReadonlySet<string>;
142
+ /**
143
+ * Familias de relaciones derivadas del modelo (`can_<P>`, `denied_<P>`,
144
+ * `permits_<P>`). Un slug que EMPIECE por una de ellas colisiona con la
145
+ * relación derivada de otro permiso y el generador la colapsaría en silencio
146
+ * (S4: `can_docs_write` anulaba el deny de `docs:write`).
147
+ */
148
+ export declare const RESERVED_SLUG_PREFIXES: readonly string[];
149
+ /**
150
+ * **Los TIPOS de objeto que el modelo `facts` (y el `group` de relaciones) YA
151
+ * declaran** (Fase 4 · ⚪4). Un tipo de objeto de relaciones que se llame como
152
+ * uno de estos DUPLICARÍA un tipo del store compartido: `role_binding` sobre
153
+ * todo —si el driver de relaciones pudiera declararlo, podría componer el id
154
+ * de un binding real y `relate` sería una escalada a `roles.authorize`—. Vive
155
+ * en `identity.ts` (la gramática compartida) porque lo consumen DOS módulos
156
+ * que la frontera de pureza mantiene disjuntos: el generador del modelo
157
+ * (`src/drivers/openfga_facts.ts`, que no puede depender de `relations/`) y
158
+ * `defineRelationsConfig` (`src/relations/`, que no puede depender de
159
+ * `drivers/`). `deny_binding` sigue reservado aunque el modelo (c2r) ya no lo
160
+ * emita: un catálogo migrado del modo `resolver` no puede resucitarlo.
161
+ */
162
+ export declare const RESERVED_FACTS_TYPES: ReadonlySet<string>;
163
+ /**
164
+ * Longitud máxima de un slug. Un nombre de relación en FGA admite 50
165
+ * caracteres y el modelo deriva `permits_<slug>` (el prefijo más largo, 8):
166
+ * 42 es lo que cabe con cualquier prefijo. Es más estricto que la columna
167
+ * (100) a propósito: un catálogo legal en `database` tiene que ser publicable
168
+ * en `openfga` (S13), o el cambio de driver no es una migración de hechos.
169
+ */
170
+ export declare const MAX_SLUG_LENGTH: number;
171
+ export type SlugKind = 'rol' | 'permiso';
172
+ /** Slug de rol o permiso válido para AMBOS drivers (formato, longitud, reservados). */
173
+ export declare function assertValidSlug(kind: SlugKind, slug: unknown): asserts slug is string;
174
+ /**
175
+ * Cómo se proyecta un slug a un nombre de relación FGA (`:` → `_`). Dos slugs
176
+ * distintos con la misma proyección (`docs:write` / `docs_write`) serían UNA
177
+ * relación: el catálogo los rechaza juntos (L0.8a).
178
+ */
179
+ export declare function slugAsRelation(slug: string): string;
180
+ /** Ningún par de slugs del conjunto colisiona tras proyectarse a relación. */
181
+ export declare function assertNoSlugCollisions(kind: SlugKind, slugs: Iterable<string>): void;
182
+ /** Versiones booleanas para caminos de LECTURA que no deben lanzar (ids que vienen del backend). */
183
+ export declare function isValidScope(scope: ScopeRef): boolean;
184
+ export declare function isValidScopeType(scopeType: unknown): scopeType is string;
185
+ export declare function isValidSlug(kind: SlugKind, slug: string): boolean;
186
+ /**
187
+ * La pregunta de `hasRole`, validada: string ⇒ `{ slug }`; objeto ⇒
188
+ * `{ slug, scopeType }` (ambos con la gramática del motor).
189
+ */
190
+ export declare function normalizeRoleQuery(role: RoleQuery): NormalizedRoleQuery;
191
+ /**
192
+ * Las claves de la cadena desde cada nivel hacia la raíz (3B · B2; movida
193
+ * aquí en 3D · N5 para que exista UNA sola codificación de clave de scope):
194
+ * `keysFrom[i]` = `chain.slice(i).map(scopeKey)`. Un rol es visible en el
195
+ * nivel `i` si es global o su owner está en `keysFrom[i]` — el owner tiene
196
+ * que ser ancestro-o-igual del scope de la ASIGNACIÓN, no de la pregunta.
197
+ */
198
+ export declare function chainKeysFrom(chain: ScopeRef[]): string[][];
199
+ /**
200
+ * `expiresAt` de un `grant`, en sus tres estados legales: omitido, `null` o
201
+ * una `Date` válida. Una cadena, un número o un `Invalid Date` no son una
202
+ * caducidad: un driver lanzaba un TypeError al serializar y el otro persistía
203
+ * basura (D7). Se rechaza como identidad mal formada (422).
204
+ */
205
+ export declare function assertExpiresAt(value: unknown): asserts value is Date | null | undefined;
206
+ export interface IdentityParts {
207
+ subject?: SubjectRef;
208
+ scope?: ScopeRef;
209
+ scopeType?: string;
210
+ /** Pregunta de `hasRole`: string u objeto `{ slug, scopeType }`. */
211
+ role?: RoleQuery;
212
+ /**
213
+ * Slug de rol donde el contrato pide un string (`grant`, `revoke`,
214
+ * `listSubjects`): un `RoleQuery` objeto aquí es 422, no un 503 ni un
215
+ * TypeError (D11).
216
+ */
217
+ roleSlug?: unknown;
218
+ permission?: string;
219
+ /** Caducidad de un `grant`: `undefined` | `null` | `Date` válida. */
220
+ expiresAt?: unknown;
221
+ }
222
+ /**
223
+ * Valida en un solo paso los componentes que recibe una operación. Se llama
224
+ * ANTES de tocar catálogo, árbol o backend: una pregunta mal formada no
225
+ * cuesta una consulta ni deja rastro.
226
+ */
227
+ export declare function assertIdentity(parts: IdentityParts): void;
228
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/identity.ts"],"names":[],"mappings":"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"}