@jantstack/adonis-authz 2.0.0-alpha.1 → 2.4.0-alpha.2
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.
- package/README.md +693 -36
- package/build/commands/authz_catalog_diff.js +1 -1
- package/build/commands/authz_catalog_diff.js.map +1 -1
- package/build/commands/authz_catalog_prune_orphans.d.ts +78 -0
- package/build/commands/authz_catalog_prune_orphans.d.ts.map +1 -0
- package/build/commands/authz_catalog_prune_orphans.js +136 -0
- package/build/commands/authz_catalog_prune_orphans.js.map +1 -0
- package/build/commands/authz_catalog_sync.d.ts +17 -0
- package/build/commands/authz_catalog_sync.d.ts.map +1 -1
- package/build/commands/authz_catalog_sync.js +27 -4
- package/build/commands/authz_catalog_sync.js.map +1 -1
- package/build/commands/authz_freeze.d.ts +44 -0
- package/build/commands/authz_freeze.d.ts.map +1 -0
- package/build/commands/authz_freeze.js +95 -0
- package/build/commands/authz_freeze.js.map +1 -0
- package/build/commands/authz_reconcile.d.ts +102 -0
- package/build/commands/authz_reconcile.d.ts.map +1 -0
- package/build/commands/authz_reconcile.js +294 -0
- package/build/commands/authz_reconcile.js.map +1 -0
- package/build/commands/authz_relations_reconcile.d.ts +97 -0
- package/build/commands/authz_relations_reconcile.d.ts.map +1 -0
- package/build/commands/authz_relations_reconcile.js +313 -0
- package/build/commands/authz_relations_reconcile.js.map +1 -0
- package/build/commands/authz_scopes_relay.d.ts +47 -0
- package/build/commands/authz_scopes_relay.d.ts.map +1 -0
- package/build/commands/authz_scopes_relay.js +141 -0
- package/build/commands/authz_scopes_relay.js.map +1 -0
- package/build/commands/authz_unfreeze.d.ts +37 -0
- package/build/commands/authz_unfreeze.d.ts.map +1 -0
- package/build/commands/authz_unfreeze.js +92 -0
- package/build/commands/authz_unfreeze.js.map +1 -0
- package/build/commands/main.d.ts +6 -1
- package/build/commands/main.d.ts.map +1 -1
- package/build/commands/main.js +6 -1
- package/build/commands/main.js.map +1 -1
- package/build/commands/openfga_provision.d.ts +46 -4
- package/build/commands/openfga_provision.d.ts.map +1 -1
- package/build/commands/openfga_provision.js +90 -7
- package/build/commands/openfga_provision.js.map +1 -1
- package/build/configure.d.ts +11 -0
- package/build/configure.d.ts.map +1 -1
- package/build/configure.js +37 -1
- package/build/configure.js.map +1 -1
- package/build/index.d.ts +50 -10
- package/build/index.d.ts.map +1 -1
- package/build/index.js +45 -6
- package/build/index.js.map +1 -1
- package/build/providers/authz_provider.d.ts +26 -2
- package/build/providers/authz_provider.d.ts.map +1 -1
- package/build/providers/authz_provider.js +55 -2
- package/build/providers/authz_provider.js.map +1 -1
- package/build/services/relations.d.ts +14 -0
- package/build/services/relations.d.ts.map +1 -0
- package/build/services/relations.js +17 -0
- package/build/services/relations.js.map +1 -0
- package/build/src/{catalog.d.ts → catalog/catalog.d.ts} +41 -2
- package/build/src/catalog/catalog.d.ts.map +1 -0
- package/build/src/{catalog.js → catalog/catalog.js} +120 -14
- package/build/src/catalog/catalog.js.map +1 -0
- package/build/src/{catalog_cache.d.ts → catalog/catalog_cache.d.ts} +46 -22
- package/build/src/catalog/catalog_cache.d.ts.map +1 -0
- package/build/src/{catalog_cache.js → catalog/catalog_cache.js} +53 -43
- package/build/src/catalog/catalog_cache.js.map +1 -0
- package/build/src/define_config.d.ts +137 -3
- package/build/src/define_config.d.ts.map +1 -1
- package/build/src/define_config.js.map +1 -1
- package/build/src/drivers/database_driver.d.ts +128 -7
- package/build/src/drivers/database_driver.d.ts.map +1 -1
- package/build/src/drivers/database_driver.js +510 -24
- package/build/src/drivers/database_driver.js.map +1 -1
- package/build/src/drivers/database_relations_driver.d.ts +113 -0
- package/build/src/drivers/database_relations_driver.d.ts.map +1 -0
- package/build/src/drivers/database_relations_driver.js +679 -0
- package/build/src/drivers/database_relations_driver.js.map +1 -0
- package/build/src/drivers/openfga_driver.d.ts +729 -122
- package/build/src/drivers/openfga_driver.d.ts.map +1 -1
- package/build/src/drivers/openfga_driver.js +2083 -476
- package/build/src/drivers/openfga_driver.js.map +1 -1
- package/build/src/drivers/openfga_facts.d.ts +384 -0
- package/build/src/drivers/openfga_facts.d.ts.map +1 -0
- package/build/src/drivers/openfga_facts.js +836 -0
- package/build/src/drivers/openfga_facts.js.map +1 -0
- package/build/src/drivers/openfga_relations_driver.d.ts +141 -0
- package/build/src/drivers/openfga_relations_driver.d.ts.map +1 -0
- package/build/src/drivers/openfga_relations_driver.js +590 -0
- package/build/src/drivers/openfga_relations_driver.js.map +1 -0
- package/build/src/errors.d.ts +290 -5
- package/build/src/errors.d.ts.map +1 -1
- package/build/src/errors.js +296 -7
- package/build/src/errors.js.map +1 -1
- package/build/src/freeze.d.ts +141 -0
- package/build/src/freeze.d.ts.map +1 -0
- package/build/src/freeze.js +217 -0
- package/build/src/freeze.js.map +1 -0
- package/build/src/http/app_access_middleware.d.ts.map +1 -0
- package/build/src/http/app_access_middleware.js.map +1 -0
- package/build/src/http/resource_access_middleware.d.ts +105 -0
- package/build/src/http/resource_access_middleware.d.ts.map +1 -0
- package/build/src/http/resource_access_middleware.js +81 -0
- package/build/src/http/resource_access_middleware.js.map +1 -0
- package/build/src/identity.d.ts +74 -1
- package/build/src/identity.d.ts.map +1 -1
- package/build/src/identity.js +100 -2
- package/build/src/identity.js.map +1 -1
- package/build/src/manager.d.ts +330 -4
- package/build/src/manager.d.ts.map +1 -1
- package/build/src/manager.js +1285 -188
- package/build/src/manager.js.map +1 -1
- package/build/src/models/authz_assignment.d.ts +7 -7
- package/build/src/models/authz_assignment.d.ts.map +1 -1
- package/build/src/models/authz_deny.d.ts +7 -7
- package/build/src/models/authz_deny.d.ts.map +1 -1
- package/build/src/models/authz_permission.d.ts +7 -7
- package/build/src/models/authz_permission.d.ts.map +1 -1
- package/build/src/models/authz_role.d.ts +7 -7
- package/build/src/models/authz_role.d.ts.map +1 -1
- package/build/src/models/authz_role_permission.d.ts +7 -7
- package/build/src/models/authz_role_permission.d.ts.map +1 -1
- package/build/src/openfga.d.ts +18 -2
- package/build/src/openfga.d.ts.map +1 -1
- package/build/src/openfga.js +15 -1
- package/build/src/openfga.js.map +1 -1
- package/build/src/reconcile.d.ts +37 -0
- package/build/src/reconcile.d.ts.map +1 -0
- package/build/src/reconcile.js +69 -0
- package/build/src/reconcile.js.map +1 -0
- package/build/src/relation_partition_trigger.d.ts +8 -0
- package/build/src/relation_partition_trigger.d.ts.map +1 -0
- package/build/src/relation_partition_trigger.js +93 -0
- package/build/src/relation_partition_trigger.js.map +1 -0
- package/build/src/relations/define_relations_config.d.ts +75 -0
- package/build/src/relations/define_relations_config.d.ts.map +1 -0
- package/build/src/relations/define_relations_config.js +173 -0
- package/build/src/relations/define_relations_config.js.map +1 -0
- package/build/src/relations/manager.d.ts +60 -0
- package/build/src/relations/manager.d.ts.map +1 -0
- package/build/src/relations/manager.js +213 -0
- package/build/src/relations/manager.js.map +1 -0
- package/build/src/relations/reconcile.d.ts +111 -0
- package/build/src/relations/reconcile.d.ts.map +1 -0
- package/build/src/relations/reconcile.js +200 -0
- package/build/src/relations/reconcile.js.map +1 -0
- package/build/src/relations_config_store.d.ts +22 -0
- package/build/src/relations_config_store.d.ts.map +1 -0
- package/build/src/relations_config_store.js +74 -0
- package/build/src/relations_config_store.js.map +1 -0
- package/build/src/scope_outbox.d.ts +69 -0
- package/build/src/scope_outbox.d.ts.map +1 -0
- package/build/src/scope_outbox.js +298 -0
- package/build/src/scope_outbox.js.map +1 -0
- package/build/src/{drivers → shared}/backend_guard.d.ts +42 -0
- package/build/src/shared/backend_guard.d.ts.map +1 -0
- package/build/src/{drivers → shared}/backend_guard.js +78 -1
- package/build/src/shared/backend_guard.js.map +1 -0
- package/build/src/shared/sql_expiry.d.ts.map +1 -0
- package/build/src/shared/sql_expiry.js.map +1 -0
- package/build/src/shared/transaction_guard.d.ts +50 -0
- package/build/src/shared/transaction_guard.d.ts.map +1 -0
- package/build/src/shared/transaction_guard.js +60 -0
- package/build/src/shared/transaction_guard.js.map +1 -0
- package/build/src/sql_descendants.d.ts +47 -1
- package/build/src/sql_descendants.d.ts.map +1 -1
- package/build/src/sql_descendants.js +75 -1
- package/build/src/sql_descendants.js.map +1 -1
- package/build/src/testing/contract.d.ts +129 -4
- package/build/src/testing/contract.d.ts.map +1 -1
- package/build/src/testing/contract.js +912 -177
- package/build/src/testing/contract.js.map +1 -1
- package/build/src/testing/main.d.ts +8 -2
- package/build/src/testing/main.d.ts.map +1 -1
- package/build/src/testing/main.js +4 -1
- package/build/src/testing/main.js.map +1 -1
- package/build/src/testing/migration_contract.d.ts +284 -0
- package/build/src/testing/migration_contract.d.ts.map +1 -0
- package/build/src/testing/migration_contract.js +586 -0
- package/build/src/testing/migration_contract.js.map +1 -0
- package/build/src/testing/relations_contract.d.ts +73 -0
- package/build/src/testing/relations_contract.d.ts.map +1 -0
- package/build/src/testing/relations_contract.js +1069 -0
- package/build/src/testing/relations_contract.js.map +1 -0
- package/build/src/testing/relations_reconcile_contract.d.ts +24 -0
- package/build/src/testing/relations_reconcile_contract.d.ts.map +1 -0
- package/build/src/testing/relations_reconcile_contract.js +220 -0
- package/build/src/testing/relations_reconcile_contract.js.map +1 -0
- package/build/src/traits/authz_scopes.js +1 -1
- package/build/src/traits/authz_scopes.js.map +1 -1
- package/build/src/traits/has_uuid.d.ts +8 -8
- package/build/src/traits/has_uuid.d.ts.map +1 -1
- package/build/src/types.d.ts +1053 -83
- package/build/src/types.d.ts.map +1 -1
- package/build/src/types.js +10 -0
- package/build/src/types.js.map +1 -1
- package/build/stubs/config/authorization.stub +104 -4
- package/build/stubs/migration.stub +136 -0
- package/build/stubs/scopes_outbox_migration.stub +57 -0
- package/package.json +4 -2
- package/build/commands/openfga_import.d.ts +0 -34
- package/build/commands/openfga_import.d.ts.map +0 -1
- package/build/commands/openfga_import.js +0 -97
- package/build/commands/openfga_import.js.map +0 -1
- package/build/src/catalog.d.ts.map +0 -1
- package/build/src/catalog.js.map +0 -1
- package/build/src/catalog_cache.d.ts.map +0 -1
- package/build/src/catalog_cache.js.map +0 -1
- package/build/src/drivers/backend_guard.d.ts.map +0 -1
- package/build/src/drivers/backend_guard.js.map +0 -1
- package/build/src/drivers/sql_expiry.d.ts.map +0 -1
- package/build/src/drivers/sql_expiry.js.map +0 -1
- package/build/src/middleware/app_access_middleware.d.ts.map +0 -1
- package/build/src/middleware/app_access_middleware.js.map +0 -1
- /package/build/src/{middleware → http}/app_access_middleware.d.ts +0 -0
- /package/build/src/{middleware → http}/app_access_middleware.js +0 -0
- /package/build/src/{drivers → shared}/sql_expiry.d.ts +0 -0
- /package/build/src/{drivers → shared}/sql_expiry.js +0 -0
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Driver `openfga` del puerto `RelationsDriver` — ReBAC sobre el MISMO store y
|
|
3
|
+
* `modelId` que el driver `facts` de roles (Fase 4, lote 4-4).
|
|
4
|
+
*
|
|
5
|
+
* Store COMPARTIDO (decisión del dueño / juez §2): las relaciones ReBAC viven
|
|
6
|
+
* en el modelo FUSIONADO (`facts` + `group` + los tipos de objeto del
|
|
7
|
+
* consumidor), así que `relate`/`unrelate` son `Write`/delete de tuplas del
|
|
8
|
+
* mismo store, `check` es UN solo `Check` (el modelo resuelve includes +
|
|
9
|
+
* usersets del lado del servidor) y `list*` van por `ListObjects`/`Read`.
|
|
10
|
+
*
|
|
11
|
+
* **La partición va en el ID del objeto y del userset**, no en el modelo:
|
|
12
|
+
* `document:<scopeKey(partition)>|<uuid>`, `group:<scopeKey(partition)>|<uuid>#member`.
|
|
13
|
+
* El cruce de particiones se corta por comparación de string —aquí NO hay
|
|
14
|
+
* trigger como en `database`, así que F-05 (en el manager) + la comparación de
|
|
15
|
+
* string son la ÚNICA defensa de tenant—. El id lleva DOS `|` cuando la
|
|
16
|
+
* partición es un scope no-raíz (`document:unit|<partUuid>|<objUuid>`), así que
|
|
17
|
+
* el parseo es CANÓNICO desde la DERECHA (⚪5, el mismo cuidado que
|
|
18
|
+
* `parseBindingId`): partición = todo menos el ÚLTIMO segmento; el id del
|
|
19
|
+
* objeto es el último y no contiene `|`.
|
|
20
|
+
*
|
|
21
|
+
* **La frontera del 🔴 (F-05) NO vive aquí**: la cierra el `RelationsManager`
|
|
22
|
+
* ANTES de tocar el driver (un `object.type` no declarado ⇒ 422) junto con ⚪4
|
|
23
|
+
* (un tipo reservado no se puede declarar). Si a este driver se le llama en
|
|
24
|
+
* DIRECTO con `object.type: 'role_binding'` (la salida `manager.driver()`, de
|
|
25
|
+
* plataforma), compondría el id de un binding real y escalaría — por eso el
|
|
26
|
+
* caso-exploit del auditor se cierra en el manager, y el runner publica el
|
|
27
|
+
* caso para que un driver de terceros que no lo cumpla NO pase el contrato.
|
|
28
|
+
*
|
|
29
|
+
* Pureza: vive detrás del subpath `@jantstack/adonis-authz/openfga`
|
|
30
|
+
* (`src/openfga.ts`), como el driver `facts` de roles: es lo único que puede
|
|
31
|
+
* tocar `@openfga/sdk` (peer opcional). La abstracción no filtra: ningún error
|
|
32
|
+
* del SDK escapa (todo va por `#guard`, que clasifica a 503/timeout).
|
|
33
|
+
*/
|
|
34
|
+
import { OpenFgaClient, ConsistencyPreference, ClientWriteRequestOnMissingDeletes, ClientWriteRequestOnDuplicateWrites, } from '@openfga/sdk';
|
|
35
|
+
import { AuthorizationBackendError, AuthorizationBackendTimeoutError, AuthorizationConfigError, InvalidIdentityError, PurgeIncompleteError, UnsupportedOperationError, } from '../errors.js';
|
|
36
|
+
import { assertScope, assertSubject, assertRelationId, assertExpiresAt, scopeKey, scopeSpellings } from '../identity.js';
|
|
37
|
+
import { isClock, systemClock } from '../clock.js';
|
|
38
|
+
import { resolveGrantExpiry, sameInstant, toExpiryDate } from '../expiry.js';
|
|
39
|
+
import { isRelUserset } from '../types.js';
|
|
40
|
+
import { assertFgaObjectId, FACTS_EXPIRY_CONDITION, FACTS_GROUP_TYPE, } from './openfga_facts.js';
|
|
41
|
+
import { assertRelationDeclared } from '../relations/define_relations_config.js';
|
|
42
|
+
export const DEFAULT_TIMEOUT_MS = 5_000;
|
|
43
|
+
/** Tope de operaciones por `Write` en FGA (`exceeded_entity_limit` a partir de 100). */
|
|
44
|
+
const WRITE_BATCH_SIZE = 100;
|
|
45
|
+
/** Tamaño de página de `Read` (máximo del servidor). */
|
|
46
|
+
const READ_PAGE_SIZE = 100;
|
|
47
|
+
/**
|
|
48
|
+
* Cota de páginas de una enumeración por `Read`. Un `continuation_token` que
|
|
49
|
+
* no avanza (servidor roto, proxy, caché) sería un bucle que ningún deadline
|
|
50
|
+
* corta (el deadline es por llamada). Mismo patrón que el driver de roles.
|
|
51
|
+
*/
|
|
52
|
+
const MAX_READ_PAGES = 10_000;
|
|
53
|
+
/**
|
|
54
|
+
* El tope de `ListObjects` del servidor por defecto (`OPENFGA_LIST_OBJECTS_MAX_RESULTS`).
|
|
55
|
+
* `ListObjects` NO devuelve señal de truncado ni `continuation_token`: corta al
|
|
56
|
+
* tope y calla. La única forma honesta de detectar el corte es comparar el
|
|
57
|
+
* número de resultados con el tope CONFIGURADO del servidor —de ahí la opción
|
|
58
|
+
* `listObjectsMaxResults`—: si vuelven `>= tope`, no se puede prometer que la
|
|
59
|
+
* lista esté completa ⇒ `truncated: true` (lección S16/L0.7: nunca una lista
|
|
60
|
+
* parcial MUDA). Ponerlo por debajo del tope real del servidor daría un
|
|
61
|
+
* truncado FALSO (el servidor devolvió todo); por encima, se perdería el corte.
|
|
62
|
+
*/
|
|
63
|
+
const DEFAULT_LIST_OBJECTS_MAX = 1_000;
|
|
64
|
+
/**
|
|
65
|
+
* Ignora duplicados en el `Write` de una tupla que NO estaba (idempotencia de
|
|
66
|
+
* dos `relate` concurrentes idénticos). Cambiar la caducidad de una que SÍ
|
|
67
|
+
* está es delete+write (R-15): FGA no admite reescribir la condición de una
|
|
68
|
+
* tupla existente y un `Ignore` la dejaría con la caducidad vieja en silencio.
|
|
69
|
+
*/
|
|
70
|
+
const IGNORE_DUPLICATE_WRITES = {
|
|
71
|
+
conflict: { onDuplicateWrites: ClientWriteRequestOnDuplicateWrites.Ignore },
|
|
72
|
+
};
|
|
73
|
+
/** Ignora deletes de tuplas ya ausentes (re-unrelate no-op seguro, invariante 6). */
|
|
74
|
+
const IGNORE_MISSING_DELETES = {
|
|
75
|
+
conflict: { onMissingDeletes: ClientWriteRequestOnMissingDeletes.Ignore },
|
|
76
|
+
};
|
|
77
|
+
export class OpenFgaRelationsDriver {
|
|
78
|
+
/**
|
|
79
|
+
* Lo que declara (cada valor tiene su cara en `runRelationsDriverContract`):
|
|
80
|
+
* - `singleCheckRelations: true`: `check` es UN solo `Check` (el modelo
|
|
81
|
+
* resuelve includes + usersets del lado del servidor).
|
|
82
|
+
* - `listObjectsInherited: false`: `list*` no abren el subárbol de
|
|
83
|
+
* particiones (invariante 7); `ListObjects` sí resuelve la derivación del
|
|
84
|
+
* modelo (includes/usersets), que es «directos + derivados», no herencia
|
|
85
|
+
* de scopes.
|
|
86
|
+
* - `usersetSubjects: true`: `listSubjects` (`Read`) trae los usersets
|
|
87
|
+
* directos (`group#member`) junto con los holders.
|
|
88
|
+
* - `membersOfNative: false`: la membresía TRANSITIVA sería `ListUsers`, que
|
|
89
|
+
* trunca al tope del servidor; se declara 500 `E_AUTHZ_UNSUPPORTED`, jamás
|
|
90
|
+
* un userset enumerado a medias (par de `purgeRole` de la 3b).
|
|
91
|
+
* - `enumerateRelations: true` (4-5): es ORIGEN de `authz:reconcile` de
|
|
92
|
+
* relaciones — lee el store ENTERO (`Read({})`, la única forma de ver todo
|
|
93
|
+
* sin filtro por user u objeto: un `Read` por tipo-solo sin user es un 400)
|
|
94
|
+
* y devuelve SOLO las tuplas de relación de la partición (tipo declarado o
|
|
95
|
+
* `group`), descartando las de `facts` (scope/role/role_binding).
|
|
96
|
+
* - `listObjectsTruncation: true`: `ListObjects` trunca al tope del servidor
|
|
97
|
+
* y este driver lo SEÑALA (medido contra el `:8103` de tope 3).
|
|
98
|
+
* - `injectableClock: true` (R-15): `withClock(now)` fija el reloj que viaja
|
|
99
|
+
* como `current_time` y filtra `listSubjects`, como el driver de roles.
|
|
100
|
+
*/
|
|
101
|
+
capabilities = Object.freeze({
|
|
102
|
+
singleCheckRelations: true,
|
|
103
|
+
listObjectsInherited: false,
|
|
104
|
+
usersetSubjects: true,
|
|
105
|
+
membersOfNative: false,
|
|
106
|
+
enumerateRelations: true,
|
|
107
|
+
listObjectsTruncation: true,
|
|
108
|
+
injectableClock: true,
|
|
109
|
+
// L-2/L-5 (panel `{trx}`, (C)): `false` EXPLÍCITO, y no puede ser otra
|
|
110
|
+
// cosa — una tupla no entra en una transacción SQL: el store es otro
|
|
111
|
+
// servicio y no hay 2PC, así que «los dos o ninguno» con la transacción
|
|
112
|
+
// del consumidor es una promesa que este driver no puede hacer, y no la
|
|
113
|
+
// hace. `{ transaction }` es 500 `E_AUTHZ_UNSUPPORTED` por llamada, en el
|
|
114
|
+
// `RelationsManager` (puerta 1) Y en el driver (`#rejectTransaction`,
|
|
115
|
+
// para quien entre por `manager.driver()` o por `reconcileRelations`),
|
|
116
|
+
// con CERO llamadas al store (L-5 lo fija contra el `:8101` con espía
|
|
117
|
+
// sobre el cliente). La alternativa que SÍ existe es para el ÁRBOL de
|
|
118
|
+
// scopes: `scopes.outbox` encola en tu transacción y `authz:scopes:relay`
|
|
119
|
+
// aplica. Para las RELACIONES no hay outbox (el panel la descartó:
|
|
120
|
+
// fail-open medido durante el lag); el consumidor lee esta capacidad y
|
|
121
|
+
// ajusta (usa `database` para lo que tenga que ir en su transacción, o
|
|
122
|
+
// `requireTransactionalWrites` para fallar al arrancar).
|
|
123
|
+
transactionalWrites: false,
|
|
124
|
+
});
|
|
125
|
+
client;
|
|
126
|
+
#config;
|
|
127
|
+
#options;
|
|
128
|
+
#now;
|
|
129
|
+
#holderTypes;
|
|
130
|
+
#fgaToMorph;
|
|
131
|
+
#timeoutMs;
|
|
132
|
+
#consistency;
|
|
133
|
+
#listObjectsMax;
|
|
134
|
+
#logger;
|
|
135
|
+
diagnostics = { unparseableTuples: 0 };
|
|
136
|
+
constructor(config, options) {
|
|
137
|
+
this.#config = config;
|
|
138
|
+
this.#options = options;
|
|
139
|
+
this.#now = options.now ?? systemClock;
|
|
140
|
+
this.#holderTypes = options.holderTypes;
|
|
141
|
+
this.#fgaToMorph = Object.fromEntries(Object.entries(options.holderTypes).map(([morph, fga]) => [fga, morph]));
|
|
142
|
+
this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
143
|
+
this.#consistency =
|
|
144
|
+
options.consistency === 'minimize_latency'
|
|
145
|
+
? ConsistencyPreference.MinimizeLatency
|
|
146
|
+
: ConsistencyPreference.HigherConsistency;
|
|
147
|
+
this.#listObjectsMax = options.listObjectsMaxResults ?? DEFAULT_LIST_OBJECTS_MAX;
|
|
148
|
+
this.#logger = options.logger ?? console;
|
|
149
|
+
this.client = new OpenFgaClient({
|
|
150
|
+
apiUrl: options.apiUrl,
|
|
151
|
+
storeId: options.storeId,
|
|
152
|
+
authorizationModelId: options.modelId,
|
|
153
|
+
baseOptions: { timeout: this.#timeoutMs },
|
|
154
|
+
retryParams: { maxRetry: 0 },
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Vista de este driver con OTRO reloj de pared (R-15, paridad con
|
|
159
|
+
* `AuthorizationDriver.withClock`): MISMO cliente (store, modelo, deadline),
|
|
160
|
+
* solo cambia el `now` que decide la caducidad. Se construye una instancia
|
|
161
|
+
* nueva y se le presta el cliente (el espía de «1 Check» envuelve
|
|
162
|
+
* `client.check` de la instancia base y tiene que seguir viéndolo).
|
|
163
|
+
*/
|
|
164
|
+
withClock(now) {
|
|
165
|
+
if (!isClock(now)) {
|
|
166
|
+
throw new AuthorizationConfigError(`withClock: now debe ser una función () => Date (llegó ${typeof now})`);
|
|
167
|
+
}
|
|
168
|
+
const view = new OpenFgaRelationsDriver(this.#config, { ...this.#options, now });
|
|
169
|
+
view.client = this.client;
|
|
170
|
+
return view;
|
|
171
|
+
}
|
|
172
|
+
/** El `context` de todo `Check`/`ListObjects`: la condición `not_expired` se evalúa contra este instante (un `now` por operación). */
|
|
173
|
+
#checkContext(at) {
|
|
174
|
+
return { current_time: at.toISOString() };
|
|
175
|
+
}
|
|
176
|
+
/* ── Clasificación de fallos (la abstracción no filtra) ─────────────────── */
|
|
177
|
+
async #guard(operation, fn) {
|
|
178
|
+
try {
|
|
179
|
+
return await fn();
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
// Un error del paquete (422/500 ya clasificado) se propaga tal cual.
|
|
183
|
+
if (error instanceof InvalidIdentityError || error instanceof PurgeIncompleteError)
|
|
184
|
+
throw error;
|
|
185
|
+
if (isTimeoutLike(error)) {
|
|
186
|
+
throw new AuthorizationBackendTimeoutError('openfga-relations', operation, this.#timeoutMs, error);
|
|
187
|
+
}
|
|
188
|
+
throw new AuthorizationBackendError('openfga-relations', operation, error);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/* ── Composición y parseo de ids (⚪5: partición por string en el id) ────── */
|
|
192
|
+
/** `<type>:<scopeKey(partition)>|<objectUuid>`. */
|
|
193
|
+
#objectId(objectType, partitionKey, objectUuid) {
|
|
194
|
+
// R-16 (defensa en profundidad, paridad con `database`): el `object.id` con
|
|
195
|
+
// la gramática estricta ANTES de componer. Un `|`/`#`/`:`/espacio dentro se
|
|
196
|
+
// escribía (solo se medía la longitud) y luego se perdía en el parseo por
|
|
197
|
+
// el último `|` —invisible en enumeración y `reconcile`—. `manager.driver()`
|
|
198
|
+
// salta el manager, así que el driver aplica la MISMA regla que él.
|
|
199
|
+
assertRelationId(`el id del objeto '${objectType}'`, objectUuid);
|
|
200
|
+
const id = `${objectType}:${partitionKey}|${objectUuid}`;
|
|
201
|
+
assertFgaObjectId(objectType, id);
|
|
202
|
+
return id;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* El `user` FGA de un sujeto: holder ⇒ `<fgaType>:<uuid>`; userset ⇒
|
|
206
|
+
* `<type>:<scopeKey(partition)>|<objUuid>#<relation>`. El userset comparte la
|
|
207
|
+
* partición de la tupla (el manager lo exige).
|
|
208
|
+
*/
|
|
209
|
+
#subjectUser(subject, partitionKey) {
|
|
210
|
+
if (isRelUserset(subject)) {
|
|
211
|
+
const object = this.#objectId(subject.object.type, partitionKey, subject.object.id);
|
|
212
|
+
return `${object}#${subject.relation}`;
|
|
213
|
+
}
|
|
214
|
+
const fgaType = this.#holderTypes[subject.type];
|
|
215
|
+
if (!fgaType) {
|
|
216
|
+
throw new InvalidIdentityError(`Holder type '${subject.type}' no está en holderTypes del driver openfga de relaciones ` +
|
|
217
|
+
`(declarados: ${Object.keys(this.#holderTypes).join(', ') || 'ninguno'}).`);
|
|
218
|
+
}
|
|
219
|
+
return `${fgaType}:${subject.uuid}`;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* **Parseo CANÓNICO del id de un objeto de relación** (⚪5): a partir de
|
|
223
|
+
* `<type>:<scopeKey>|<objUuid>`, separa `type` (antes del primer `:`),
|
|
224
|
+
* `partitionKey` (todo menos el ÚLTIMO segmento tras el `:`) y `objectUuid`
|
|
225
|
+
* (el último segmento, que no contiene `|`). Es el MISMO parseo-desde-la-
|
|
226
|
+
* derecha que `parseBindingId`: `lastIndexOf('|')` corta el objeto de la
|
|
227
|
+
* clave de partición (que tiene 1 parte `app` o 2 `<tipo>|<uuid>`). `null` si
|
|
228
|
+
* el id no tiene la forma que este driver escribe.
|
|
229
|
+
*/
|
|
230
|
+
#parseObjectId(fullId) {
|
|
231
|
+
const colon = fullId.indexOf(':');
|
|
232
|
+
if (colon <= 0)
|
|
233
|
+
return null;
|
|
234
|
+
const type = fullId.slice(0, colon);
|
|
235
|
+
const rest = fullId.slice(colon + 1);
|
|
236
|
+
const cut = rest.lastIndexOf('|');
|
|
237
|
+
if (cut < 0)
|
|
238
|
+
return null;
|
|
239
|
+
const partitionKey = rest.slice(0, cut);
|
|
240
|
+
const objectUuid = rest.slice(cut + 1);
|
|
241
|
+
if (!partitionKey || !objectUuid)
|
|
242
|
+
return null;
|
|
243
|
+
return { type, partitionKey, objectUuid };
|
|
244
|
+
}
|
|
245
|
+
/** Un `user` del store de vuelta a `RelSubject`: userset (`…#rel`) o holder (`<fga>:<uuid>`). */
|
|
246
|
+
#userToSubject(user) {
|
|
247
|
+
const hash = user.indexOf('#');
|
|
248
|
+
if (hash >= 0) {
|
|
249
|
+
const parsed = this.#parseObjectId(user.slice(0, hash));
|
|
250
|
+
const relation = user.slice(hash + 1);
|
|
251
|
+
if (!parsed || !relation)
|
|
252
|
+
return null;
|
|
253
|
+
return { object: { type: parsed.type, id: parsed.objectUuid }, relation };
|
|
254
|
+
}
|
|
255
|
+
const colon = user.indexOf(':');
|
|
256
|
+
if (colon <= 0)
|
|
257
|
+
return null;
|
|
258
|
+
const morph = this.#fgaToMorph[user.slice(0, colon)];
|
|
259
|
+
const uuid = user.slice(colon + 1);
|
|
260
|
+
if (!morph || !uuid || uuid === '*' || uuid.includes('|'))
|
|
261
|
+
return null;
|
|
262
|
+
return { type: morph, uuid };
|
|
263
|
+
}
|
|
264
|
+
/* ── Escrituras ─────────────────────────────────────────────────────────── */
|
|
265
|
+
/**
|
|
266
|
+
* `relate` con los tres estados de `expiresAt` (R-15, invariante 10). Se LEE
|
|
267
|
+
* primero la tupla exacta (un `Read` por clave, como `grant` de roles):
|
|
268
|
+
* - ausente ⇒ un `Write` (con la condición `not_expired` si caduca);
|
|
269
|
+
* - presente con la caducidad que toca ⇒ no-op (idempotente, invariante 6);
|
|
270
|
+
* - presente con OTRA ⇒ delete + write en DOS llamadas (FGA no admite
|
|
271
|
+
* delete+write de la misma clave en un `Write`, ni reescribir su
|
|
272
|
+
* condición): entre ambas hay un instante en el que `check` responde
|
|
273
|
+
* `false` —fail-closed, la misma ventana que el `grant` de roles—.
|
|
274
|
+
*/
|
|
275
|
+
/**
|
|
276
|
+
* **`{ transaction }` se rechaza también AQUÍ** (L-5, defensa en
|
|
277
|
+
* profundidad como F-05 en L-0): la puerta 1 vive en el `RelationsManager`,
|
|
278
|
+
* pero `manager.driver()` y `reconcileRelations` entran por el driver; sin
|
|
279
|
+
* esta guarda un `{ transaction }` por ahí escribiría la tupla en el store
|
|
280
|
+
* IGNORANDO la transacción — una escritura que finge ir en tu transacción y
|
|
281
|
+
* que tu rollback no deshace, que es exactamente el fail-open que
|
|
282
|
+
* `transactionalWrites: false` declara no poder evitar. Primera línea de
|
|
283
|
+
* las cuatro escrituras, antes de F-05, del `Read` y del `Write`: CERO
|
|
284
|
+
* llamadas al cliente.
|
|
285
|
+
*/
|
|
286
|
+
#rejectTransaction(operation, options) {
|
|
287
|
+
if (options?.transaction === undefined || options.transaction === null)
|
|
288
|
+
return;
|
|
289
|
+
throw UnsupportedOperationError.transactionalDriver(`relations.${operation}`, 'openfga', 'relations');
|
|
290
|
+
}
|
|
291
|
+
async relate(subject, relation, object, partition, options) {
|
|
292
|
+
this.#rejectTransaction('relate', options);
|
|
293
|
+
// L-0 · F-05 en el DRIVER (defensa en profundidad, la MISMA función que el
|
|
294
|
+
// manager): `manager.driver()` y `reconcileRelations` entran por aquí, y en
|
|
295
|
+
// el store COMPARTIDO un tipo no declarado (`role_binding`) compondría el
|
|
296
|
+
// id exacto de un binding de roles. Corta ANTES del `Read` y del `Write`.
|
|
297
|
+
assertRelationDeclared(this.#config, object, relation);
|
|
298
|
+
assertScope(partition);
|
|
299
|
+
assertExpiresAt(options?.expiresAt);
|
|
300
|
+
const partitionKey = scopeKey(partition);
|
|
301
|
+
const objectId = this.#objectId(object.type, partitionKey, object.id);
|
|
302
|
+
const user = this.#subjectUser(subject, partitionKey);
|
|
303
|
+
const key = { user, relation, object: objectId };
|
|
304
|
+
const now = this.#now();
|
|
305
|
+
const current = await this.#readTuple('relate', key);
|
|
306
|
+
const previous = current ? current.validUntil : null;
|
|
307
|
+
const expiresAt = resolveGrantExpiry(previous, options?.expiresAt, now);
|
|
308
|
+
if (current && sameInstant(previous, expiresAt))
|
|
309
|
+
return;
|
|
310
|
+
const tuple = expiresAt
|
|
311
|
+
? { ...key, condition: { name: FACTS_EXPIRY_CONDITION, context: { valid_until: expiresAt.toISOString() } } }
|
|
312
|
+
: key;
|
|
313
|
+
if (current) {
|
|
314
|
+
await this.#guard('relate', () => this.client.write({ deletes: [key] }, IGNORE_MISSING_DELETES));
|
|
315
|
+
}
|
|
316
|
+
await this.#guard('relate', () => this.client.write({ writes: [tuple] }, IGNORE_DUPLICATE_WRITES));
|
|
317
|
+
}
|
|
318
|
+
/** La tupla EXACTA de una clave (un `Read` por clave completa), con su caducidad si la lleva. */
|
|
319
|
+
async #readTuple(operation, key) {
|
|
320
|
+
const response = await this.#guard(operation, () => this.client.read(key, { consistency: this.#consistency }));
|
|
321
|
+
const found = (response.tuples ?? []).map((t) => t.key).find((k) => k?.user === key.user && k?.relation === key.relation && k?.object === key.object);
|
|
322
|
+
if (!found)
|
|
323
|
+
return null;
|
|
324
|
+
return { ...key, validUntil: toExpiryDate(found.condition?.context?.valid_until) };
|
|
325
|
+
}
|
|
326
|
+
async unrelate(subject, relation, object, partition, options) {
|
|
327
|
+
this.#rejectTransaction('unrelate', options);
|
|
328
|
+
// L-0 · F-05 también al RETIRAR: un `unrelate(alice, assignee, role_binding)`
|
|
329
|
+
// sería un revoke de roles por la puerta de relaciones.
|
|
330
|
+
assertRelationDeclared(this.#config, object, relation);
|
|
331
|
+
assertScope(partition);
|
|
332
|
+
const partitionKey = scopeKey(partition);
|
|
333
|
+
const objectId = this.#objectId(object.type, partitionKey, object.id);
|
|
334
|
+
const user = this.#subjectUser(subject, partitionKey);
|
|
335
|
+
await this.#guard('unrelate', () =>
|
|
336
|
+
// Re-unrelate no-op seguro: borrar una tupla ausente se ignora.
|
|
337
|
+
this.client.write({ deletes: [{ user, relation, object: objectId }] }, IGNORE_MISSING_DELETES));
|
|
338
|
+
}
|
|
339
|
+
/* ── Lecturas ───────────────────────────────────────────────────────────── */
|
|
340
|
+
/** `check` = UN solo `Check` (el modelo resuelve includes + usersets). */
|
|
341
|
+
async check(subject, relation, object, partition) {
|
|
342
|
+
assertScope(partition);
|
|
343
|
+
const partitionKey = scopeKey(partition);
|
|
344
|
+
const objectId = this.#objectId(object.type, partitionKey, object.id);
|
|
345
|
+
const user = this.#subjectUser(subject, partitionKey);
|
|
346
|
+
const context = this.#checkContext(this.#now());
|
|
347
|
+
const response = await this.#guard('check', () => this.client.check({ user, relation, object: objectId, context }, { consistency: this.#consistency }));
|
|
348
|
+
return response.allowed === true;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* `listObjects` = `ListObjects` (resuelve includes + usersets del lado del
|
|
352
|
+
* servidor) + FILTRO por partición + señal de TRUNCADO. `ListObjects` corta
|
|
353
|
+
* al tope del servidor sin `continuation_token` ni bandera, así que el
|
|
354
|
+
* truncado se detecta comparando el número de resultados CRUDOS con el tope
|
|
355
|
+
* configurado (`listObjectsMaxResults`): `>= tope` ⇒ no se puede prometer la
|
|
356
|
+
* lista completa ⇒ `truncated: true`. El filtro por partición se aplica
|
|
357
|
+
* DESPUÉS (los ids llevan la partición), así que en un store con varias
|
|
358
|
+
* particiones el tope se consume entre todas — otra razón para señalar el
|
|
359
|
+
* truncado y no callar (documentado en el README).
|
|
360
|
+
*/
|
|
361
|
+
async listObjects(subject, relation, objectType, partition, _page) {
|
|
362
|
+
assertScope(partition);
|
|
363
|
+
const partitionKey = scopeKey(partition);
|
|
364
|
+
const user = this.#subjectUser(subject, partitionKey);
|
|
365
|
+
const context = this.#checkContext(this.#now());
|
|
366
|
+
const response = await this.#guard('listObjects', () => this.client.listObjects({ user, relation, type: objectType, context }, { consistency: this.#consistency }));
|
|
367
|
+
const raw = response.objects ?? [];
|
|
368
|
+
const truncated = raw.length >= this.#listObjectsMax;
|
|
369
|
+
const objects = [];
|
|
370
|
+
for (const full of raw) {
|
|
371
|
+
const parsed = this.#parseObjectId(full);
|
|
372
|
+
if (!parsed || parsed.type !== objectType) {
|
|
373
|
+
this.#warnUnparseable('listObjects', full);
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (parsed.partitionKey !== partitionKey)
|
|
377
|
+
continue; // otra partición: no cruza
|
|
378
|
+
objects.push({ type: objectType, id: parsed.objectUuid });
|
|
379
|
+
}
|
|
380
|
+
objects.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
381
|
+
return truncated ? { objects, truncated: true } : { objects };
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* `listSubjects` = `Read` por `(object, relation)` — hechos DIRECTOS del
|
|
385
|
+
* relation EXACTO (invariante 7): ni transitivo (eso es `membersOf`) ni
|
|
386
|
+
* derivado por includes. `usersetSubjects: true` ⇒ los usersets
|
|
387
|
+
* (`group#member`) salen junto con los holders.
|
|
388
|
+
*/
|
|
389
|
+
async listSubjects(relation, object, partition, _page) {
|
|
390
|
+
assertScope(partition);
|
|
391
|
+
const partitionKey = scopeKey(partition);
|
|
392
|
+
const objectId = this.#objectId(object.type, partitionKey, object.id);
|
|
393
|
+
// `Read` devuelve tuplas ESCRITAS, no evaluadas: la caducidad se filtra en
|
|
394
|
+
// cliente con el MISMO reloj del driver (R-15, estricta: la que vence en
|
|
395
|
+
// `now` ya no cuenta), como las enumeraciones del driver de roles.
|
|
396
|
+
const now = this.#now();
|
|
397
|
+
const tuples = await this.#readAll('listSubjects', { object: objectId, relation });
|
|
398
|
+
const subjects = [];
|
|
399
|
+
for (const t of tuples) {
|
|
400
|
+
if (t.validUntil && t.validUntil.getTime() <= now.getTime())
|
|
401
|
+
continue;
|
|
402
|
+
const subject = this.#userToSubject(t.user);
|
|
403
|
+
if (subject)
|
|
404
|
+
subjects.push(subject);
|
|
405
|
+
else
|
|
406
|
+
this.#warnUnparseable('listSubjects', t.user);
|
|
407
|
+
}
|
|
408
|
+
return { subjects };
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* **`membersOf` ⇒ 500 `E_AUTHZ_UNSUPPORTED`** (cara `whenFalse`): la
|
|
412
|
+
* membresía TRANSITIVA de un grupo sería `ListUsers`, que trunca al tope del
|
|
413
|
+
* servidor sin señal fiable. No hay userset transitivo enumerable aquí; se
|
|
414
|
+
* dice con 500 nombrándolo, jamás un userset a medias. Es el análogo del par
|
|
415
|
+
* `purgeRole` de la 3b. (El método existe en el prototipo para que el manager
|
|
416
|
+
* lo despache, pero la capacidad `membersOfNative: false` lo corta antes.)
|
|
417
|
+
*/
|
|
418
|
+
async membersOf() {
|
|
419
|
+
throw new UnsupportedOperationError('membersOf', 'la membresía transitiva de un grupo', 'openfga', `En 'openfga' la transitiva sería 'ListUsers', que trunca al tope del servidor sin señal fiable. ` +
|
|
420
|
+
`Usa el driver 'database' (CTE recursiva) o 'listSubjects' para los hechos directos.`);
|
|
421
|
+
}
|
|
422
|
+
/* ── ORIGEN de reconcile (4-5): las tuplas de relación de la partición ──── */
|
|
423
|
+
/**
|
|
424
|
+
* **`enumerateRelations` — el ORIGEN de `authz:reconcile` de relaciones**
|
|
425
|
+
* (4-5). Lee el store ENTERO (`Read({})`) —la única forma de ver todo sin un
|
|
426
|
+
* filtro por `user` u `object`, que un `Read` por tipo-solo sin user
|
|
427
|
+
* rechazaría con 400— y devuelve SOLO las tuplas de relación de la partición
|
|
428
|
+
* pedida: tipo `group` o un tipo declarado en la config, DESCARTANDO las de
|
|
429
|
+
* `facts` (`scope`/`role`/`role_binding`, que no llevan la partición en el id
|
|
430
|
+
* con la forma `<key>|<uuid>` de relaciones —o cuyo tipo no está declarado—).
|
|
431
|
+
*
|
|
432
|
+
* `truncated`/paginación: `enumerateRelations` agota el `Read` (exhaustivo,
|
|
433
|
+
* como `listSubjects`/`purge*`), así que devuelve TODO en una página sin
|
|
434
|
+
* cursor —el reconcile itera hasta que no hay cursor—. Es el ORIGEN de una
|
|
435
|
+
* migración: una tupla que no llegara aquí desaparecería sin rastro — por eso
|
|
436
|
+
* la CADUCADA también llega, con su `expiresAt` (R-15), para que el destino
|
|
437
|
+
* la cuente en `skipped`.
|
|
438
|
+
*/
|
|
439
|
+
async enumerateRelations(partition, _page) {
|
|
440
|
+
assertScope(partition);
|
|
441
|
+
const partitionKey = scopeKey(partition);
|
|
442
|
+
const relationTypes = new Set([FACTS_GROUP_TYPE, ...this.#config.objectTypes.map((t) => t.type)]);
|
|
443
|
+
const all = await this.#readAll('enumerateRelations', {});
|
|
444
|
+
const tuples = [];
|
|
445
|
+
for (const key of all) {
|
|
446
|
+
const parsed = this.#parseObjectId(key.object);
|
|
447
|
+
// No es una tupla de relación (facts: scope/role/role_binding, o un tipo
|
|
448
|
+
// no declarado): no la migra este puerto.
|
|
449
|
+
if (!parsed || !relationTypes.has(parsed.type))
|
|
450
|
+
continue;
|
|
451
|
+
if (parsed.partitionKey !== partitionKey)
|
|
452
|
+
continue; // otra partición: no cruza
|
|
453
|
+
const subject = this.#userToSubject(key.user);
|
|
454
|
+
if (!subject) {
|
|
455
|
+
this.#warnUnparseable('enumerateRelations', key.user);
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
tuples.push({
|
|
459
|
+
subject,
|
|
460
|
+
relation: key.relation,
|
|
461
|
+
object: { type: parsed.type, id: parsed.objectUuid },
|
|
462
|
+
partition,
|
|
463
|
+
expiresAt: key.validUntil,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
return { tuples };
|
|
467
|
+
}
|
|
468
|
+
/* ── Purga (invariante 11): borra y DEMUESTRA cero, o lanza ─────────────── */
|
|
469
|
+
/**
|
|
470
|
+
* Borra TODAS las tuplas del objeto (cualquier relación) y demuestra cero.
|
|
471
|
+
* Barre las DOS ortografías del uuid de partición (🟡2, paridad con
|
|
472
|
+
* `database`): un motor que funda `aaa…` con `aaa-…` dejaría la fila del
|
|
473
|
+
* alias concediendo tras una purga «exitosa». Si tras borrar quedan tuplas
|
|
474
|
+
* ⇒ 500 `E_AUTHZ_PURGE_INCOMPLETE`.
|
|
475
|
+
*/
|
|
476
|
+
async purgeObject(object, partition, options) {
|
|
477
|
+
this.#rejectTransaction('purgeObject', options);
|
|
478
|
+
assertScope(partition);
|
|
479
|
+
const objectIds = this.#partitionSpellings(partition).map((key) => this.#objectId(object.type, key, object.id));
|
|
480
|
+
for (const objectId of objectIds) {
|
|
481
|
+
await this.#purgeByFilter('purgeObject', { object: objectId }, `objeto ${objectId}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Borra TODAS las tuplas cuyo SUJETO es este subject en la partición, en
|
|
486
|
+
* cualquier tipo de objeto declarado, y demuestra cero. `Read` exige un
|
|
487
|
+
* filtro de objeto (un `Read` solo por `user` es un 400 del servidor), así
|
|
488
|
+
* que se recorre cada tipo declarado (`group` + los del consumidor) con
|
|
489
|
+
* `Read({ user, object: '<type>:' })`.
|
|
490
|
+
*/
|
|
491
|
+
async purgeSubject(subject, partition, options) {
|
|
492
|
+
this.#rejectTransaction('purgeSubject', options);
|
|
493
|
+
assertScope(partition);
|
|
494
|
+
if (!isRelUserset(subject))
|
|
495
|
+
assertSubject(subject);
|
|
496
|
+
const partitionKeys = this.#partitionSpellings(partition);
|
|
497
|
+
const types = [FACTS_GROUP_TYPE, ...this.#config.objectTypes.map((t) => t.type)];
|
|
498
|
+
for (const partitionKey of partitionKeys) {
|
|
499
|
+
const user = this.#subjectUser(subject, partitionKey);
|
|
500
|
+
for (const type of types) {
|
|
501
|
+
await this.#purgeByFilter('purgeSubject', { user, object: `${type}:` }, `sujeto ${user} en '${type}'`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
/** Lee por filtro, borra en lotes, y RE-LEE para demostrar cero (o lanza). */
|
|
506
|
+
async #purgeByFilter(operation, filter, what) {
|
|
507
|
+
const keys = await this.#readAll(operation, filter);
|
|
508
|
+
for (let i = 0; i < keys.length; i += WRITE_BATCH_SIZE) {
|
|
509
|
+
await this.#guard(operation, () => this.client.deleteTuples(keys.slice(i, i + WRITE_BATCH_SIZE).map((k) => ({ user: k.user, relation: k.relation, object: k.object })), IGNORE_MISSING_DELETES));
|
|
510
|
+
}
|
|
511
|
+
if (keys.length === 0)
|
|
512
|
+
return;
|
|
513
|
+
// Demostración de cero: re-lee tras borrar (invariante 11).
|
|
514
|
+
const residue = await this.#readAll(operation, filter);
|
|
515
|
+
if (residue.length > 0) {
|
|
516
|
+
throw new PurgeIncompleteError(`${operation}: quedan ${residue.length} tuplas de ${what} tras el borrado ` +
|
|
517
|
+
`(${residue.slice(0, 3).map((k) => `${k.user}#${k.relation}@${k.object}`).join('; ')}). ` +
|
|
518
|
+
`No se confirma la purga.`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
/* ── Helpers ────────────────────────────────────────────────────────────── */
|
|
522
|
+
/** Las dos ortografías del uuid de partición, como claves de scope (🟡2). */
|
|
523
|
+
#partitionSpellings(partition) {
|
|
524
|
+
return [...new Set(scopeSpellings(partition).map((s) => scopeKey(s)))];
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* TODAS las tuplas que casan con el filtro, paginando `Read` hasta agotar el
|
|
528
|
+
* `continuation_token`. `Read` no tiene tope de resultados (a diferencia de
|
|
529
|
+
* `ListObjects`), así que es exhaustivo — por eso las purgas y `listSubjects`
|
|
530
|
+
* van por aquí, no por `ListObjects`.
|
|
531
|
+
*/
|
|
532
|
+
async #readAll(operation, filter) {
|
|
533
|
+
const keys = [];
|
|
534
|
+
let continuationToken;
|
|
535
|
+
const seenTokens = new Set();
|
|
536
|
+
let pages = 0;
|
|
537
|
+
do {
|
|
538
|
+
const response = await this.#guard(operation, () => this.client.read(filter, {
|
|
539
|
+
pageSize: READ_PAGE_SIZE,
|
|
540
|
+
continuationToken,
|
|
541
|
+
consistency: this.#consistency,
|
|
542
|
+
}));
|
|
543
|
+
pages += 1;
|
|
544
|
+
for (const tuple of response.tuples ?? []) {
|
|
545
|
+
const key = tuple?.key;
|
|
546
|
+
if (!key?.user || !key?.relation || !key?.object) {
|
|
547
|
+
this.#warnUnparseable(operation, JSON.stringify(key ?? null));
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
// Con su caducidad (R-15): quien la lee decide si filtra (`listSubjects`),
|
|
551
|
+
// la emite (`enumerateRelations`) o la ignora (`purge*` borra TODO).
|
|
552
|
+
keys.push({
|
|
553
|
+
user: key.user,
|
|
554
|
+
relation: key.relation,
|
|
555
|
+
object: key.object,
|
|
556
|
+
validUntil: toExpiryDate(key.condition?.context?.valid_until),
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
continuationToken = response.continuation_token || undefined;
|
|
560
|
+
if (continuationToken) {
|
|
561
|
+
if (seenTokens.has(continuationToken)) {
|
|
562
|
+
throw new AuthorizationBackendError('openfga-relations', operation, new Error(`el continuation_token se repite (página ${pages}); el servidor no avanza`));
|
|
563
|
+
}
|
|
564
|
+
if (pages >= MAX_READ_PAGES) {
|
|
565
|
+
throw new AuthorizationBackendError('openfga-relations', operation, new Error(`más de ${MAX_READ_PAGES} páginas sin agotar el Read`));
|
|
566
|
+
}
|
|
567
|
+
seenTokens.add(continuationToken);
|
|
568
|
+
}
|
|
569
|
+
} while (continuationToken);
|
|
570
|
+
return keys;
|
|
571
|
+
}
|
|
572
|
+
#warnUnparseable(operation, detail) {
|
|
573
|
+
this.diagnostics.unparseableTuples += 1;
|
|
574
|
+
this.#logger.warn(`authz(openfga-relations): tupla no parseable en ${operation} (${detail}); ` +
|
|
575
|
+
`se ignora en la enumeración (total: ${this.diagnostics.unparseableTuples})`);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
/** ¿El error huele a timeout de red/axios? (mismo criterio que el driver de roles). */
|
|
579
|
+
function isTimeoutLike(error) {
|
|
580
|
+
let current = error;
|
|
581
|
+
for (let depth = 0; current && depth < 6; depth++) {
|
|
582
|
+
const code = String(current.code ?? '');
|
|
583
|
+
if (code === 'ECONNABORTED' || code === 'ETIMEDOUT' || /timeout/i.test(String(current.message ?? ''))) {
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
current = current.cause;
|
|
587
|
+
}
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
590
|
+
//# sourceMappingURL=openfga_relations_driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openfga_relations_driver.js","sourceRoot":"","sources":["../../../src/drivers/openfga_relations_driver.ts"],"names":[],"mappings":"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"}
|