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

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 (108) hide show
  1. package/README.md +299 -19
  2. package/build/commands/authz_relations_reconcile.d.ts +25 -1
  3. package/build/commands/authz_relations_reconcile.d.ts.map +1 -1
  4. package/build/commands/authz_relations_reconcile.js +94 -6
  5. package/build/commands/authz_relations_reconcile.js.map +1 -1
  6. package/build/index.d.ts +13 -2
  7. package/build/index.d.ts.map +1 -1
  8. package/build/index.js +15 -1
  9. package/build/index.js.map +1 -1
  10. package/build/providers/authz_provider.d.ts +6 -1
  11. package/build/providers/authz_provider.d.ts.map +1 -1
  12. package/build/providers/authz_provider.js +18 -2
  13. package/build/providers/authz_provider.js.map +1 -1
  14. package/build/src/define_config.d.ts +73 -1
  15. package/build/src/define_config.d.ts.map +1 -1
  16. package/build/src/define_config.js.map +1 -1
  17. package/build/src/drivers/database_driver.d.ts +53 -14
  18. package/build/src/drivers/database_driver.d.ts.map +1 -1
  19. package/build/src/drivers/database_driver.js +104 -32
  20. package/build/src/drivers/database_driver.js.map +1 -1
  21. package/build/src/drivers/database_relations_driver.d.ts +57 -9
  22. package/build/src/drivers/database_relations_driver.d.ts.map +1 -1
  23. package/build/src/drivers/database_relations_driver.js +344 -89
  24. package/build/src/drivers/database_relations_driver.js.map +1 -1
  25. package/build/src/drivers/openfga_driver.d.ts +17 -4
  26. package/build/src/drivers/openfga_driver.d.ts.map +1 -1
  27. package/build/src/drivers/openfga_driver.js +38 -3
  28. package/build/src/drivers/openfga_driver.js.map +1 -1
  29. package/build/src/drivers/openfga_facts.d.ts +15 -0
  30. package/build/src/drivers/openfga_facts.d.ts.map +1 -1
  31. package/build/src/drivers/openfga_facts.js +25 -2
  32. package/build/src/drivers/openfga_facts.js.map +1 -1
  33. package/build/src/drivers/openfga_relations_driver.d.ts +40 -6
  34. package/build/src/drivers/openfga_relations_driver.d.ts.map +1 -1
  35. package/build/src/drivers/openfga_relations_driver.js +244 -44
  36. package/build/src/drivers/openfga_relations_driver.js.map +1 -1
  37. package/build/src/errors.d.ts +83 -4
  38. package/build/src/errors.d.ts.map +1 -1
  39. package/build/src/errors.js +139 -3
  40. package/build/src/errors.js.map +1 -1
  41. package/build/src/freeze.d.ts +37 -16
  42. package/build/src/freeze.d.ts.map +1 -1
  43. package/build/src/freeze.js +53 -8
  44. package/build/src/freeze.js.map +1 -1
  45. package/build/src/manager.d.ts +16 -1
  46. package/build/src/manager.d.ts.map +1 -1
  47. package/build/src/manager.js +116 -25
  48. package/build/src/manager.js.map +1 -1
  49. package/build/src/models/authz_assignment.d.ts +2 -2
  50. package/build/src/models/authz_assignment.d.ts.map +1 -1
  51. package/build/src/models/authz_deny.d.ts +2 -2
  52. package/build/src/models/authz_deny.d.ts.map +1 -1
  53. package/build/src/models/authz_permission.d.ts +2 -2
  54. package/build/src/models/authz_permission.d.ts.map +1 -1
  55. package/build/src/models/authz_role.d.ts +2 -2
  56. package/build/src/models/authz_role.d.ts.map +1 -1
  57. package/build/src/models/authz_role_permission.d.ts +2 -2
  58. package/build/src/models/authz_role_permission.d.ts.map +1 -1
  59. package/build/src/relation_partition_trigger.d.ts.map +1 -1
  60. package/build/src/relation_partition_trigger.js +11 -3
  61. package/build/src/relation_partition_trigger.js.map +1 -1
  62. package/build/src/relations/define_relations_config.d.ts +51 -0
  63. package/build/src/relations/define_relations_config.d.ts.map +1 -1
  64. package/build/src/relations/define_relations_config.js +68 -1
  65. package/build/src/relations/define_relations_config.js.map +1 -1
  66. package/build/src/relations/manager.d.ts +71 -6
  67. package/build/src/relations/manager.d.ts.map +1 -1
  68. package/build/src/relations/manager.js +301 -29
  69. package/build/src/relations/manager.js.map +1 -1
  70. package/build/src/relations/reconcile.d.ts +53 -4
  71. package/build/src/relations/reconcile.d.ts.map +1 -1
  72. package/build/src/relations/reconcile.js +92 -30
  73. package/build/src/relations/reconcile.js.map +1 -1
  74. package/build/src/scope_outbox.d.ts.map +1 -1
  75. package/build/src/scope_outbox.js +17 -10
  76. package/build/src/scope_outbox.js.map +1 -1
  77. package/build/src/shared/backend_guard.d.ts +28 -0
  78. package/build/src/shared/backend_guard.d.ts.map +1 -1
  79. package/build/src/shared/backend_guard.js +52 -0
  80. package/build/src/shared/backend_guard.js.map +1 -1
  81. package/build/src/shared/transaction_guard.d.ts +50 -0
  82. package/build/src/shared/transaction_guard.d.ts.map +1 -0
  83. package/build/src/shared/transaction_guard.js +60 -0
  84. package/build/src/shared/transaction_guard.js.map +1 -0
  85. package/build/src/testing/contract.d.ts +55 -4
  86. package/build/src/testing/contract.d.ts.map +1 -1
  87. package/build/src/testing/contract.js +240 -8
  88. package/build/src/testing/contract.js.map +1 -1
  89. package/build/src/testing/main.d.ts +4 -4
  90. package/build/src/testing/main.d.ts.map +1 -1
  91. package/build/src/testing/main.js +2 -2
  92. package/build/src/testing/main.js.map +1 -1
  93. package/build/src/testing/relations_contract.d.ts +23 -1
  94. package/build/src/testing/relations_contract.d.ts.map +1 -1
  95. package/build/src/testing/relations_contract.js +603 -19
  96. package/build/src/testing/relations_contract.js.map +1 -1
  97. package/build/src/testing/relations_reconcile_contract.d.ts.map +1 -1
  98. package/build/src/testing/relations_reconcile_contract.js +49 -1
  99. package/build/src/testing/relations_reconcile_contract.js.map +1 -1
  100. package/build/src/traits/has_uuid.d.ts +2 -2
  101. package/build/src/traits/has_uuid.d.ts.map +1 -1
  102. package/build/src/types.d.ts +284 -16
  103. package/build/src/types.d.ts.map +1 -1
  104. package/build/src/types.js +4 -0
  105. package/build/src/types.js.map +1 -1
  106. package/build/stubs/config/authorization.stub +45 -1
  107. package/build/stubs/migration.stub +20 -10
  108. package/package.json +1 -1
@@ -183,13 +183,20 @@ export default class extends BaseSchema {
183
183
  table.timestamp('updated_at').notNullable()
184
184
  })
185
185
 
186
- // Tuplas de ReBAC (Fase 4): una fila por relación, INSERT/DELETE-ONLY (sin
187
- // `expires_at`: la caducidad de relaciones quedó fuera de la 2.4). Sin FK:
188
- // las relaciones NO cuelgan del catálogo (viven en su propio espacio de
189
- // ids, disjunto de `authz_*` por construcción). Identidad byte a byte
186
+ // Tuplas de ReBAC (Fase 4): una fila por relación, INSERT/DELETE-ONLY
187
+ // (renovar la caducidad es delete+insert, nunca UPDATE). Sin FK: las
188
+ // relaciones NO cuelgan del catálogo (viven en su propio espacio de ids,
189
+ // disjunto de `authz_*` por construcción). Identidad byte a byte
190
190
  // (`utf8mb4_bin`) como el resto (2.5 · J3). La partición (tenant) es la
191
191
  // clave del scope; `subject_relation` con valor = userset (`group:g#member`),
192
- // `subject_partition` = la partición del userset.
192
+ // `''` = holder (L-4b: **NOT NULL DEFAULT ''** — con NULL el UNIQUE
193
+ // `authz_rel_tuple_uq` solo defendía usersets, porque NULL ≠ NULL en los
194
+ // tres motores, y dos `relate` concurrentes del mismo holder confirmaban
195
+ // DOS filas; `''` es la única forma con paridad: el índice parcial no
196
+ // existe en MySQL y `NULLS NOT DISTINCT` es solo PG 15+),
197
+ // `subject_partition` = la partición del userset. `expires_at` (R-15,
198
+ // 2.4.0-alpha.2) es la caducidad de la tupla: NULL = no caduca; misma
199
+ // decisión de motor que `authz_assignments.expires_at` (DATETIME(3)).
193
200
  this.schema.createTable('authz_relations', (table) => {
194
201
  table.uuid('uuid').primary().notNullable()
195
202
  table.string('partition_key', 64).notNullable().collate('utf8mb4_bin')
@@ -198,8 +205,9 @@ export default class extends BaseSchema {
198
205
  table.string('relation', 50).notNullable().collate('utf8mb4_bin')
199
206
  table.string('subject_type', 50).notNullable().collate('utf8mb4_bin')
200
207
  table.string('subject_uuid', 64).notNullable().collate('utf8mb4_bin')
201
- table.string('subject_relation', 50).nullable().collate('utf8mb4_bin')
208
+ table.string('subject_relation', 50).notNullable().defaultTo('').collate('utf8mb4_bin')
202
209
  table.string('subject_partition', 64).nullable().collate('utf8mb4_bin')
210
+ table.datetime('expires_at', { precision: 3 }).nullable()
203
211
  table.timestamp('created_at').notNullable()
204
212
 
205
213
  table.unique(
@@ -211,7 +219,9 @@ export default class extends BaseSchema {
211
219
  })
212
220
 
213
221
  // El TRIGGER de partición, por dialecto. Defiende al escritor «a mano»: una
214
- // fila con userset (`subject_relation` con valor) de OTRA partición que la
222
+ // fila con userset (`subject_relation <> ''`; un holder lleva `''` desde
223
+ // L-4b, y una fila vieja con NULL que nadie rellenó tampoco dispara: en los
224
+ // tres motores `NULL <> ''` es NULL, o sea falso) de OTRA partición que la
215
225
  // de la tupla se rechaza en el motor. La DDL es radicalmente distinta en
216
226
  // cada uno (SQLite `RAISE(ABORT)`, PostgreSQL función plpgsql + trigger,
217
227
  // MySQL `SIGNAL SQLSTATE`), y el cuerpo `BEGIN…END` lleva `;` dentro: cada
@@ -263,7 +273,7 @@ function relationPartitionTriggerStatements(dialect) {
263
273
  const body = (event) =>
264
274
  `CREATE TRIGGER authz_relations_partition_b${event === 'INSERT' ? 'i' : 'u'} ` +
265
275
  `BEFORE ${event} ON authz_relations FOR EACH ROW ` +
266
- `WHEN NEW.subject_relation IS NOT NULL AND NEW.subject_partition IS NOT NEW.partition_key ` +
276
+ `WHEN NEW.subject_relation <> '' AND NEW.subject_partition IS NOT NEW.partition_key ` +
267
277
  `BEGIN SELECT RAISE(ABORT, '${msg}'); END`
268
278
  return [body('INSERT'), body('UPDATE')]
269
279
  }
@@ -271,7 +281,7 @@ function relationPartitionTriggerStatements(dialect) {
271
281
  return [
272
282
  `CREATE OR REPLACE FUNCTION authz_relations_partition_guard() RETURNS trigger LANGUAGE plpgsql AS $authz$ ` +
273
283
  `BEGIN ` +
274
- `IF NEW.subject_relation IS NOT NULL AND NEW.subject_partition IS DISTINCT FROM NEW.partition_key THEN ` +
284
+ `IF NEW.subject_relation <> '' AND NEW.subject_partition IS DISTINCT FROM NEW.partition_key THEN ` +
275
285
  `RAISE EXCEPTION '${msg} (subject_partition=%, partition_key=%)', NEW.subject_partition, NEW.partition_key; ` +
276
286
  `END IF; RETURN NEW; END; $authz$`,
277
287
  `CREATE TRIGGER authz_relations_partition_biu BEFORE INSERT OR UPDATE ON authz_relations ` +
@@ -283,7 +293,7 @@ function relationPartitionTriggerStatements(dialect) {
283
293
  `CREATE TRIGGER authz_relations_partition_b${event === 'INSERT' ? 'i' : 'u'} ` +
284
294
  `BEFORE ${event} ON authz_relations FOR EACH ROW ` +
285
295
  `BEGIN ` +
286
- `IF NEW.subject_relation IS NOT NULL AND NOT (NEW.subject_partition <=> NEW.partition_key) THEN ` +
296
+ `IF NEW.subject_relation <> '' AND NOT (NEW.subject_partition <=> NEW.partition_key) THEN ` +
287
297
  `SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '${msg}'; ` +
288
298
  `END IF; END`
289
299
  return [body('INSERT'), body('UPDATE')]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jantstack/adonis-authz",
3
- "version": "2.4.0-alpha.1",
3
+ "version": "2.4.0-alpha.3",
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",