@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,57 @@
1
+ {{{
2
+ exports({ to: app.migrationsPath('create_authz_scope_outbox_table.ts') })
3
+ }}}
4
+ import { BaseSchema } from '@adonisjs/lucid/schema'
5
+
6
+ /**
7
+ * La OUTBOX del árbol de scopes (@jantstack/adonis-authz, 3b-2d).
8
+ *
9
+ * NO es una tabla del motor: es tuya. La publica el paquete porque
10
+ * `sqlScopeOutbox()` lee estas columnas, pero el contrato es el puerto
11
+ * `ScopeOutbox`, no esta tabla — cámbiala si tu cola vive en otro sitio.
12
+ *
13
+ * Para qué: con `scopes.outbox` declarada, `authorization.scopes.attached/
14
+ * moved/detached` NO escriben en el backend. Encolan aquí, dentro de TU
15
+ * transacción, y `node ace authz:scopes:relay` lo aplica después. Sin eso,
16
+ * un `rollback` de tu transacción deja el árbol del backend adelantado al
17
+ * tuyo y —en el driver openfga con `hierarchy: 'facts'`, donde FGA es quien
18
+ * decide— eso es una escalada persistente que tu base no puede ver.
19
+ *
20
+ * Las filas aplicadas se MARCAN (`applied_at`), no se borran: la cola es
21
+ * también el registro de qué se propagó y cuándo. Púrgalas tú cuando
22
+ * quieras.
23
+ */
24
+ export default class extends BaseSchema {
25
+ async up() {
26
+ this.schema.createTable('authz_scope_outbox', (table) => {
27
+ table.increments('id').primary()
28
+ // 'attached' | 'moved' | 'detached'.
29
+ table.string('op', 10).notNullable().collate('utf8mb4_bin')
30
+ // La identidad CANÓNICA del scope (la que devolvió tu resolveChain),
31
+ // con las decisiones de motor del paquete: varchar(64) utf8mb4_bin,
32
+ // para que un alias del uuid no se funda con otra fila.
33
+ table.string('child_type', 20).notNullable().collate('utf8mb4_bin')
34
+ table.string('child_uuid', 64).notNullable().collate('utf8mb4_bin')
35
+ // Nulos en 'detached' (no hay padre); 'app' con uuid nulo es la raíz.
36
+ table.string('parent_type', 20).nullable().collate('utf8mb4_bin')
37
+ table.string('parent_uuid', 64).nullable().collate('utf8mb4_bin')
38
+ // Quién lo ordenó, si el call-site lo declaró.
39
+ table.string('actor_type', 50).nullable().collate('utf8mb4_bin')
40
+ table.string('actor_uuid', 64).nullable().collate('utf8mb4_bin')
41
+ // Intentos fallidos y la última causa: el relay nunca es silencioso.
42
+ // Agotados los intentos (`sqlScopeOutbox({ maxAttempts })`, 5 por
43
+ // defecto) la fila queda APARCADA: deja de reintentarse, sale por
44
+ // `dead()` y el relay la reporta en todas las pasadas. Se queda aquí
45
+ // —el árbol del backend está divergente en ese nodo— hasta que la mires.
46
+ table.integer('attempts').notNullable().defaultTo(0)
47
+ table.text('last_error').nullable()
48
+ table.timestamp('created_at').notNullable()
49
+ table.timestamp('applied_at').nullable()
50
+ table.index(['applied_at', 'id'], 'authz_scope_outbox_pending_idx')
51
+ })
52
+ }
53
+
54
+ async down() {
55
+ this.schema.dropTable('authz_scope_outbox')
56
+ }
57
+ }
package/package.json CHANGED
@@ -1,17 +1,16 @@
1
1
  {
2
2
  "name": "@jantstack/adonis-authz",
3
- "version": "1.1.0",
3
+ "version": "2.4.0-alpha.1",
4
4
  "description": "Driver-based authorization engine for AdonisJS + Lucid: hierarchical scopes with downward inheritance, explicit denies, expiring assignments and polymorphic holders. Ships a self-contained database driver and an OpenFGA driver, both judged by the same executable contract suite.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
- "author": "Jose Tenorio",
8
7
  "repository": {
9
8
  "type": "git",
10
- "url": "git+https://github.com/JantStack/adonis-authz.git"
9
+ "url": "git+https://github.com/jantstack/adonis-authz.git"
11
10
  },
12
- "homepage": "https://github.com/JantStack/adonis-authz#readme",
11
+ "homepage": "https://github.com/jantstack/adonis-authz#readme",
13
12
  "bugs": {
14
- "url": "https://github.com/JantStack/adonis-authz/issues"
13
+ "url": "https://github.com/jantstack/adonis-authz/issues"
15
14
  },
16
15
  "keywords": [
17
16
  "adonisjs",
@@ -43,9 +42,12 @@
43
42
  "types": "./build/index.d.ts",
44
43
  "exports": {
45
44
  ".": "./build/index.js",
45
+ "./openfga": "./build/src/openfga.js",
46
46
  "./authz_provider": "./build/providers/authz_provider.js",
47
47
  "./services/main": "./build/services/main.js",
48
- "./app_access_middleware": "./build/src/middleware/app_access_middleware.js",
48
+ "./services/relations": "./build/services/relations.js",
49
+ "./app_access_middleware": "./build/src/http/app_access_middleware.js",
50
+ "./resource_access_middleware": "./build/src/http/resource_access_middleware.js",
49
51
  "./commands": "./build/commands/main.js",
50
52
  "./commands/*": "./build/commands/*.js",
51
53
  "./testing": "./build/src/testing/main.js",
@@ -61,7 +63,10 @@
61
63
  "prepublishOnly": "npm run build",
62
64
  "copy:stubs": "mkdir -p build/stubs/config && cp stubs/*.stub build/stubs/ && cp stubs/config/*.stub build/stubs/config/",
63
65
  "check:purity": "node scripts/check_purity.mjs",
64
- "test": "node --import @poppinss/ts-exec bin/test.ts",
66
+ "test": "npm run check:purity && node --import @poppinss/ts-exec bin/test.ts",
67
+ "test:sqlite-file": "TEST_DB=sqlite-file npm test",
68
+ "test:pg": "TEST_DB=pg npm test",
69
+ "test:mysql": "TEST_DB=mysql npm test",
65
70
  "typecheck:build": "tsc --noEmit"
66
71
  },
67
72
  "peerDependencies": {
@@ -87,6 +92,8 @@
87
92
  "@types/node": "^22.19.1",
88
93
  "better-sqlite3": "^13.0.1",
89
94
  "luxon": "^3.7.2",
95
+ "mysql2": "^3.24.2",
96
+ "pg": "^8.23.0",
90
97
  "typescript": "~5.9.3"
91
98
  },
92
99
  "publishConfig": {
@@ -1,28 +0,0 @@
1
- import { BaseCommand } from '@adonisjs/core/ace';
2
- import { CommandOptions } from '@adonisjs/core/types/ace';
3
- /**
4
- * Migra los HECHOS del driver `database` a un store OpenFGA: copia las
5
- * asignaciones vigentes (authz_assignments) y los denies (authz_denies)
6
- * como tuples. El catálogo y la jerarquía no migran: son metadata local
7
- * para ambos drivers.
8
- *
9
- * Proceso completo database → openfga:
10
- * 1. Levantar el servidor (profile openfga del compose).
11
- * 2. node ace openfga:provision → store + model (ids al .env)
12
- * 3. node ace openfga:import [--dry-run] → copia los hechos
13
- * 4. AUTHZ_DRIVER=openfga + reiniciar api/worker.
14
- *
15
- * Idempotente (re-ejecutar no duplica) y no destructivo (las tablas
16
- * locales quedan intactas → rollback = volver a AUTHZ_DRIVER=database).
17
- */
18
- export default class OpenFgaImport extends BaseCommand {
19
- static commandName: string;
20
- static description: string;
21
- static options: CommandOptions;
22
- url: string | undefined;
23
- storeId: string | undefined;
24
- modelId: string | undefined;
25
- dryRun: boolean | undefined;
26
- run(): Promise<void>;
27
- }
28
- //# sourceMappingURL=openfga_import.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"openfga_import.d.ts","sourceRoot":"","sources":["../../commands/openfga_import.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAS,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAEzD;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,WAAW;IACpD,MAAM,CAAC,WAAW,SAAmB;IACrC,MAAM,CAAC,WAAW,SAAgF;IAElG,MAAM,CAAC,OAAO,EAAE,cAAc,CAE7B;IAGO,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;IAGvB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAG3B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAG3B,MAAM,EAAE,OAAO,GAAG,SAAS,CAAA;IAE7B,GAAG;CA+BV"}
@@ -1,74 +0,0 @@
1
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6
- };
7
- var __metadata = (this && this.__metadata) || function (k, v) {
8
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
- };
10
- import { BaseCommand, flags } from '@adonisjs/core/ace';
11
- /**
12
- * Migra los HECHOS del driver `database` a un store OpenFGA: copia las
13
- * asignaciones vigentes (authz_assignments) y los denies (authz_denies)
14
- * como tuples. El catálogo y la jerarquía no migran: son metadata local
15
- * para ambos drivers.
16
- *
17
- * Proceso completo database → openfga:
18
- * 1. Levantar el servidor (profile openfga del compose).
19
- * 2. node ace openfga:provision → store + model (ids al .env)
20
- * 3. node ace openfga:import [--dry-run] → copia los hechos
21
- * 4. AUTHZ_DRIVER=openfga + reiniciar api/worker.
22
- *
23
- * Idempotente (re-ejecutar no duplica) y no destructivo (las tablas
24
- * locales quedan intactas → rollback = volver a AUTHZ_DRIVER=database).
25
- */
26
- export default class OpenFgaImport extends BaseCommand {
27
- static commandName = 'openfga:import';
28
- static description = 'Copy authz facts (assignments/denies) from the database driver into OpenFGA';
29
- static options = {
30
- startApp: true,
31
- };
32
- async run() {
33
- const config = this.app.config.get('authorization');
34
- const apiUrl = this.url ?? config?.openfga?.url;
35
- const storeId = this.storeId ?? config?.openfga?.storeId;
36
- if (!apiUrl || !storeId) {
37
- this.logger.error('Faltan la URL o el store: --url/--store-id o OPENFGA_URL/OPENFGA_STORE_ID (ver openfga:provision)');
38
- this.exitCode = 1;
39
- return;
40
- }
41
- const { importAuthzFactsToOpenFga } = await import('../src/drivers/openfga_driver.js');
42
- const holderTypes = config?.holderTypes ?? {};
43
- const result = await importAuthzFactsToOpenFga({
44
- apiUrl,
45
- storeId,
46
- modelId: this.modelId ?? config?.openfga?.modelId,
47
- dryRun: this.dryRun ?? false,
48
- holderTypes,
49
- });
50
- const verb = result.dryRun ? 'Se escribirían' : 'Escritos';
51
- this.logger.success(`${verb} ${result.assignments} assignments y ${result.denies} denies ` +
52
- `(${result.skippedExpired} asignaciones ya expiradas omitidas).`);
53
- if (!result.dryRun) {
54
- this.logger.log('Siguiente paso: AUTHZ_DRIVER=openfga y reiniciar api/worker.');
55
- }
56
- }
57
- }
58
- __decorate([
59
- flags.string({ description: 'OpenFGA API URL (defaults to OPENFGA_URL)' }),
60
- __metadata("design:type", Object)
61
- ], OpenFgaImport.prototype, "url", void 0);
62
- __decorate([
63
- flags.string({ alias: 's', description: 'Store id (defaults to OPENFGA_STORE_ID)' }),
64
- __metadata("design:type", Object)
65
- ], OpenFgaImport.prototype, "storeId", void 0);
66
- __decorate([
67
- flags.string({ description: 'Model id (defaults to OPENFGA_MODEL_ID)' }),
68
- __metadata("design:type", Object)
69
- ], OpenFgaImport.prototype, "modelId", void 0);
70
- __decorate([
71
- flags.boolean({ description: 'Count what would be written, without writing' }),
72
- __metadata("design:type", Object)
73
- ], OpenFgaImport.prototype, "dryRun", void 0);
74
- //# sourceMappingURL=openfga_import.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"openfga_import.js","sourceRoot":"","sources":["../../commands/openfga_import.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAGvD;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,WAAW;IACpD,MAAM,CAAC,WAAW,GAAG,gBAAgB,CAAA;IACrC,MAAM,CAAC,WAAW,GAAG,6EAA6E,CAAA;IAElG,MAAM,CAAC,OAAO,GAAmB;QAC/B,QAAQ,EAAE,IAAI;KACf,CAAA;IAcD,KAAK,CAAC,GAAG;QACP,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,CAAQ,CAAA;QAC1D,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,MAAM,EAAE,OAAO,EAAE,GAAG,CAAA;QAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,MAAM,EAAE,OAAO,EAAE,OAAO,CAAA;QACxD,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,mGAAmG,CACpG,CAAA;YACD,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAA;YACjB,OAAM;QACR,CAAC;QAED,MAAM,EAAE,yBAAyB,EAAE,GAAG,MAAM,MAAM,CAAC,kCAAkC,CAAC,CAAA;QACtF,MAAM,WAAW,GAAG,MAAM,EAAE,WAAW,IAAI,EAAE,CAAA;QAC7C,MAAM,MAAM,GAAG,MAAM,yBAAyB,CAAC;YAC7C,MAAM;YACN,OAAO;YACP,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,MAAM,EAAE,OAAO,EAAE,OAAO;YACjD,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK;YAC5B,WAAW;SACZ,CAAC,CAAA;QAEF,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,UAAU,CAAA;QAC1D,IAAI,CAAC,MAAM,CAAC,OAAO,CACjB,GAAG,IAAI,IAAI,MAAM,CAAC,WAAW,kBAAkB,MAAM,CAAC,MAAM,UAAU;YACpE,IAAI,MAAM,CAAC,cAAc,uCAAuC,CACnE,CAAA;QACD,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAA;QACjF,CAAC;IACH,CAAC;;AAzCO;IADP,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,2CAA2C,EAAE,CAAC;;0CAC5C;AAGvB;IADP,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,yCAAyC,EAAE,CAAC;;8CAClD;AAG3B;IADP,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,yCAAyC,EAAE,CAAC;;8CACtC;AAG3B;IADP,KAAK,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,8CAA8C,EAAE,CAAC;;6CAC5C"}
@@ -1,3 +0,0 @@
1
- import type { CatalogSpec } from './types.js';
2
- export declare function syncAuthzCatalog(catalog: CatalogSpec): Promise<void>;
3
- //# sourceMappingURL=catalog.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/catalog.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AA+B7C,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA8E1E"}
@@ -1,103 +0,0 @@
1
- import db from '@adonisjs/lucid/services/db';
2
- import { Exception } from '@adonisjs/core/exceptions';
3
- import { v7 as uuidv7 } from 'uuid';
4
- /**
5
- * Sincroniza un catálogo (roles + permisos + vínculos) a las tablas `authz_*`.
6
- *
7
- * Idempotente y aditivo (mismo contrato que el organization_acl_seeder): lo
8
- * existente se respeta, solo se insertan los diffs nuevos. Quitar algo del
9
- * catálogo NO lo borra de DB (limpieza explícita vía migración).
10
- *
11
- * Lo usan el seeder del nivel app (`app_acl_seeder`) y el harness de la
12
- * suite de contrato. El catálogo es metadata compartida entre drivers:
13
- * un driver externo (openfga) materializa aparte solo los hechos.
14
- */
15
- /**
16
- * Slugs válidos del catálogo: minúsculas, dígitos y . _ - con `:` opcional
17
- * (gramática recurso:accion). Prohibidos `~` y `|`: son los caracteres de
18
- * encoding/separador del driver openfga — un slug que los contenga
19
- * corrompería el mapeo de bindings.
20
- */
21
- const CATALOG_SLUG_FORMAT = /^[a-z0-9][a-z0-9_.-]*(:[a-z0-9][a-z0-9_.-]*)?$/;
22
- function assertValidSlug(kind, slug) {
23
- if (!CATALOG_SLUG_FORMAT.test(slug)) {
24
- throw new Exception(`Slug de ${kind} inválido: '${slug}'. Formato: minúsculas/dígitos/._- ` +
25
- `con ':' único opcional (recurso:accion); '~' y '|' están prohibidos.`, { status: 500 });
26
- }
27
- }
28
- export async function syncAuthzCatalog(catalog) {
29
- for (const perm of catalog.permissions)
30
- assertValidSlug('permiso', perm.slug);
31
- for (const role of catalog.roles)
32
- assertValidSlug('rol', role.slug);
33
- // Todo o nada. Un fallo a mitad (una constraint, la conexión) dejaba el
34
- // catálogo escrito a medias: roles sin sus permisos, es decir holders que
35
- // "tienen" un rol que no concede nada. Es idempotente y re-ejecutarlo lo
36
- // arreglaba, pero mientras tanto la autorización respondía de menos.
37
- await db.transaction(async (trx) => {
38
- // 1. Permisos: upsert por slug.
39
- for (const perm of catalog.permissions) {
40
- const existing = await trx.from('authz_permissions').where('slug', perm.slug).first();
41
- if (existing)
42
- continue;
43
- await trx.table('authz_permissions').insert({
44
- uuid: uuidv7(),
45
- slug: perm.slug,
46
- description: perm.description ?? null,
47
- created_at: new Date(),
48
- updated_at: new Date(),
49
- });
50
- }
51
- const dbPerms = await trx
52
- .from('authz_permissions')
53
- .whereIn('slug', catalog.permissions.map((p) => p.slug))
54
- .select('uuid', 'slug');
55
- const permUuidBySlug = new Map(dbPerms.map((p) => [p.slug, p.uuid]));
56
- // 2. Roles: upsert por (slug, scope_type).
57
- for (const role of catalog.roles) {
58
- const existing = await trx
59
- .from('authz_roles')
60
- .where('slug', role.slug)
61
- .where('scope_type', role.scopeType)
62
- .first();
63
- const roleUuid = existing?.uuid ?? role.uuid ?? uuidv7();
64
- if (!existing) {
65
- await trx.table('authz_roles').insert({
66
- uuid: roleUuid,
67
- slug: role.slug,
68
- name: role.name ?? role.slug,
69
- description: role.description ?? null,
70
- scope_type: role.scopeType,
71
- rank: role.rank ?? 0,
72
- created_at: new Date(),
73
- updated_at: new Date(),
74
- });
75
- }
76
- else if (role.rank !== undefined && existing.rank !== role.rank) {
77
- // El rank es metadata de policy: el config manda (a diferencia de los
78
- // vínculos, que son aditivos).
79
- await trx.from('authz_roles').where('uuid', roleUuid).update({ rank: role.rank });
80
- }
81
- // 3. Vínculos rol→permiso: upsert por par.
82
- for (const permSlug of role.permissions) {
83
- const permUuid = permUuidBySlug.get(permSlug);
84
- if (!permUuid)
85
- continue;
86
- const linked = await trx
87
- .from('authz_role_permissions')
88
- .where('role_uuid', roleUuid)
89
- .where('permission_uuid', permUuid)
90
- .first();
91
- if (linked)
92
- continue;
93
- await trx.table('authz_role_permissions').insert({
94
- uuid: uuidv7(),
95
- role_uuid: roleUuid,
96
- permission_uuid: permUuid,
97
- created_at: new Date(),
98
- });
99
- }
100
- }
101
- });
102
- }
103
- //# sourceMappingURL=catalog.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/catalog.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"app_access_middleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/app_access_middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAKvD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,OAAO,OAAO,mBAAmB;IAChC,MAAM,CACV,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO;CAqDvD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"app_access_middleware.js","sourceRoot":"","sources":["../../../src/middleware/app_access_middleware.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AACrD,OAAO,aAAa,MAAM,wBAAwB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvC;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,OAAO,OAAO,mBAAmB;IACtC,KAAK,CAAC,MAAM,CACV,GAAgB,EAChB,IAAY,EACZ,UAAkD,EAAE;QAEpD,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YACzC,MAAM,IAAI,SAAS,CAAC,qDAAqD,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;QAC7F,CAAC;QAED,MAAM,MAAM,GAAI,GAAW,CAAC,IAAI,EAAE,IAA6D,CAAA;QAC/F,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAA;QAC9F,CAAC;QAED,mEAAmE;QACnE,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAA;QACnC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,SAAS,CAAC,mEAAmE,EAAE;gBACvF,MAAM,EAAE,GAAG;aACZ,CAAC,CAAA;QACJ,CAAC;QAED,kEAAkE;QAClE,wEAAwE;QACxE,yEAAyE;QACzE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,IAAI,SAAS,CACjB,cAAc,KAAK,mEAAmE;gBACpF,sCAAsC,EACxC,EAAE,MAAM,EAAE,GAAG,EAAE,CAChB,CAAA;QACH,CAAC;QAED,MAAM,OAAO,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAA;QAElD,IAAI,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC;YACrF,OAAO,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAC5B,OAAO,EAAE,sBAAsB;gBAC/B,UAAU,EAAE,GAAG;gBACf,MAAM,EAAE,EAAE;aACX,CAAC,CAAA;QACJ,CAAC;QAED,IACE,OAAO,CAAC,UAAU;YAClB,CAAC,CAAC,MAAM,aAAa,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,EACxE,CAAC;YACD,OAAO,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAC5B,OAAO,EAAE,sBAAsB;gBAC/B,UAAU,EAAE,GAAG;gBACf,MAAM,EAAE,EAAE;aACX,CAAC,CAAA;QACJ,CAAC;QAED,OAAO,IAAI,EAAE,CAAA;IACf,CAAC;CACF"}