@jantstack/adonis-authz 1.0.2 → 2.0.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.
- package/LICENSE +1 -1
- package/README.md +417 -46
- package/build/commands/authz_catalog_diff.d.ts +28 -0
- package/build/commands/authz_catalog_diff.d.ts.map +1 -0
- package/build/commands/authz_catalog_diff.js +67 -0
- package/build/commands/authz_catalog_diff.js.map +1 -0
- package/build/commands/authz_catalog_sync.d.ts +20 -0
- package/build/commands/authz_catalog_sync.d.ts.map +1 -0
- package/build/commands/authz_catalog_sync.js +58 -0
- package/build/commands/authz_catalog_sync.js.map +1 -0
- package/build/commands/main.d.ts +2 -0
- package/build/commands/main.d.ts.map +1 -1
- package/build/commands/main.js +2 -0
- package/build/commands/main.js.map +1 -1
- package/build/commands/openfga_import.d.ts +8 -2
- package/build/commands/openfga_import.d.ts.map +1 -1
- package/build/commands/openfga_import.js +29 -6
- package/build/commands/openfga_import.js.map +1 -1
- package/build/commands/openfga_provision.d.ts.map +1 -1
- package/build/commands/openfga_provision.js +2 -1
- package/build/commands/openfga_provision.js.map +1 -1
- package/build/index.d.ts +51 -5
- package/build/index.d.ts.map +1 -1
- package/build/index.js +43 -3
- package/build/index.js.map +1 -1
- package/build/src/catalog.d.ts +249 -2
- package/build/src/catalog.d.ts.map +1 -1
- package/build/src/catalog.js +709 -59
- package/build/src/catalog.js.map +1 -1
- package/build/src/catalog_cache.d.ts +300 -0
- package/build/src/catalog_cache.d.ts.map +1 -0
- package/build/src/catalog_cache.js +656 -0
- package/build/src/catalog_cache.js.map +1 -0
- package/build/src/clock.d.ts +24 -0
- package/build/src/clock.d.ts.map +1 -0
- package/build/src/clock.js +7 -0
- package/build/src/clock.js.map +1 -0
- package/build/src/define_config.d.ts +102 -6
- package/build/src/define_config.d.ts.map +1 -1
- package/build/src/define_config.js.map +1 -1
- package/build/src/drivers/backend_guard.d.ts +92 -0
- package/build/src/drivers/backend_guard.d.ts.map +1 -0
- package/build/src/drivers/backend_guard.js +221 -0
- package/build/src/drivers/backend_guard.js.map +1 -0
- package/build/src/drivers/database_driver.d.ts +242 -15
- package/build/src/drivers/database_driver.d.ts.map +1 -1
- package/build/src/drivers/database_driver.js +692 -127
- package/build/src/drivers/database_driver.js.map +1 -1
- package/build/src/drivers/openfga_driver.d.ts +341 -27
- package/build/src/drivers/openfga_driver.d.ts.map +1 -1
- package/build/src/drivers/openfga_driver.js +1052 -264
- package/build/src/drivers/openfga_driver.js.map +1 -1
- package/build/src/drivers/sql_expiry.d.ts +53 -0
- package/build/src/drivers/sql_expiry.d.ts.map +1 -0
- package/build/src/drivers/sql_expiry.js +66 -0
- package/build/src/drivers/sql_expiry.js.map +1 -0
- package/build/src/errors.d.ts +366 -0
- package/build/src/errors.d.ts.map +1 -0
- package/build/src/errors.js +387 -0
- package/build/src/errors.js.map +1 -0
- package/build/src/expiry.d.ts +27 -0
- package/build/src/expiry.d.ts.map +1 -0
- package/build/src/expiry.js +50 -0
- package/build/src/expiry.js.map +1 -0
- package/build/src/hierarchical_resolver.d.ts +56 -0
- package/build/src/hierarchical_resolver.d.ts.map +1 -0
- package/build/src/hierarchical_resolver.js +87 -0
- package/build/src/hierarchical_resolver.js.map +1 -0
- package/build/src/identity.d.ts +155 -0
- package/build/src/identity.d.ts.map +1 -0
- package/build/src/identity.js +359 -0
- package/build/src/identity.js.map +1 -0
- package/build/src/manager.d.ts +225 -7
- package/build/src/manager.d.ts.map +1 -1
- package/build/src/manager.js +1670 -23
- package/build/src/manager.js.map +1 -1
- package/build/src/memoize_ancestors.d.ts +23 -0
- package/build/src/memoize_ancestors.d.ts.map +1 -0
- package/build/src/memoize_ancestors.js +42 -0
- package/build/src/memoize_ancestors.js.map +1 -0
- package/build/src/middleware/app_access_middleware.d.ts +8 -6
- package/build/src/middleware/app_access_middleware.d.ts.map +1 -1
- package/build/src/middleware/app_access_middleware.js +10 -26
- package/build/src/middleware/app_access_middleware.js.map +1 -1
- package/build/src/models/authz_assignment.d.ts +5 -5
- package/build/src/models/authz_assignment.d.ts.map +1 -1
- package/build/src/models/authz_deny.d.ts +5 -5
- package/build/src/models/authz_deny.d.ts.map +1 -1
- package/build/src/models/authz_permission.d.ts +11 -5
- package/build/src/models/authz_permission.d.ts.map +1 -1
- package/build/src/models/authz_permission.js +4 -0
- package/build/src/models/authz_permission.js.map +1 -1
- package/build/src/models/authz_role.d.ts +13 -6
- package/build/src/models/authz_role.d.ts.map +1 -1
- package/build/src/models/authz_role.js +6 -1
- package/build/src/models/authz_role.js.map +1 -1
- package/build/src/models/authz_role_permission.d.ts +5 -5
- package/build/src/models/authz_role_permission.d.ts.map +1 -1
- package/build/src/openfga.d.ts +13 -0
- package/build/src/openfga.d.ts.map +1 -0
- package/build/src/openfga.js +12 -0
- package/build/src/openfga.js.map +1 -0
- package/build/src/sql_descendants.d.ts +51 -0
- package/build/src/sql_descendants.d.ts.map +1 -0
- package/build/src/sql_descendants.js +129 -0
- package/build/src/sql_descendants.js.map +1 -0
- package/build/src/testing/contract.d.ts +138 -7
- package/build/src/testing/contract.d.ts.map +1 -1
- package/build/src/testing/contract.js +2946 -24
- package/build/src/testing/contract.js.map +1 -1
- package/build/src/testing/main.d.ts +4 -2
- package/build/src/testing/main.d.ts.map +1 -1
- package/build/src/testing/main.js +2 -1
- package/build/src/testing/main.js.map +1 -1
- package/build/src/testing/scope_tree.d.ts +65 -0
- package/build/src/testing/scope_tree.d.ts.map +1 -0
- package/build/src/testing/scope_tree.js +145 -0
- package/build/src/testing/scope_tree.js.map +1 -0
- package/build/src/traits/authz_scopes.d.ts +30 -6
- package/build/src/traits/authz_scopes.d.ts.map +1 -1
- package/build/src/traits/authz_scopes.js +30 -18
- package/build/src/traits/authz_scopes.js.map +1 -1
- package/build/src/traits/has_uuid.d.ts +5 -5
- package/build/src/traits/has_uuid.d.ts.map +1 -1
- package/build/src/types.d.ts +530 -28
- package/build/src/types.d.ts.map +1 -1
- package/build/src/types.js +10 -4
- package/build/src/types.js.map +1 -1
- package/build/stubs/config/app_acl.stub +4 -2
- package/build/stubs/config/authorization.stub +66 -12
- package/build/stubs/migration.stub +57 -13
- package/package.json +11 -6
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { Exception } from '@adonisjs/core/exceptions';
|
|
2
|
+
/**
|
|
3
|
+
* El backend de autorización no respondió.
|
|
4
|
+
*
|
|
5
|
+
* Es el tercer estado de una pregunta de permisos, y merece un tipo propio:
|
|
6
|
+
*
|
|
7
|
+
* no tienes permiso → `false`
|
|
8
|
+
* la pregunta no es válida → Exception 422 (permiso/rol inexistente)
|
|
9
|
+
* no se pudo preguntar → esto, 503
|
|
10
|
+
*
|
|
11
|
+
* Dos razones para envolver el error del driver en vez de dejarlo pasar:
|
|
12
|
+
*
|
|
13
|
+
* 1. **La abstracción no debe tener fugas.** Sin esto escapaba un `FgaError`
|
|
14
|
+
* del SDK de OpenFGA, así que distinguir "backend caído" obligaba a
|
|
15
|
+
* importar el SDK — acoplando el call-site al backend que este paquete
|
|
16
|
+
* existe para abstraer. Cambiar de driver habría obligado a cambiar los
|
|
17
|
+
* `catch`.
|
|
18
|
+
* 2. **Nadie debería escribir `try/catch` para estar a salvo.** Con `status`
|
|
19
|
+
* 503, el manejador de excepciones responde solo, y con el código correcto:
|
|
20
|
+
* no es que la aplicación esté rota (500), es que una dependencia no está
|
|
21
|
+
* disponible.
|
|
22
|
+
*
|
|
23
|
+
* Lo que NO se hace es capturar y devolver `false`. Denegar en silencio
|
|
24
|
+
* durante una caída deja a todos sin permisos sin decir por qué, y manda a
|
|
25
|
+
* buscar un rol mal configurado que no existe. Se deniega igual; lo que
|
|
26
|
+
* cambia es el diagnóstico.
|
|
27
|
+
*/
|
|
28
|
+
export class AuthorizationBackendError extends Exception {
|
|
29
|
+
static status = 503;
|
|
30
|
+
static code = 'E_AUTHZ_BACKEND_UNAVAILABLE';
|
|
31
|
+
constructor(driver, operation, cause) {
|
|
32
|
+
super(`El backend de autorización '${driver}' no respondió (${operation})`, { cause });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* El backend respondió tarde: venció `timeoutMs`. Es un `AuthorizationBackendError`
|
|
37
|
+
* (503, se maneja igual) con código propio, porque para operaciones "no responde"
|
|
38
|
+
* y "responde lento" son incidentes distintos: el segundo suele ser saturación, no
|
|
39
|
+
* caída, y el remedio (subir el deadline, escalar el backend) es otro.
|
|
40
|
+
*/
|
|
41
|
+
export class AuthorizationBackendTimeoutError extends AuthorizationBackendError {
|
|
42
|
+
static code = 'E_AUTHZ_BACKEND_TIMEOUT';
|
|
43
|
+
constructor(driver, operation, timeoutMs, cause) {
|
|
44
|
+
super(driver, operation, cause);
|
|
45
|
+
this.message = `El backend de autorización '${driver}' no respondió en ${timeoutMs} ms (${operation})`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* El resolutor de ancestros del consumidor lanzó. Es una dependencia más de
|
|
50
|
+
* cada pregunta (el árbol de scopes), así que su caída se clasifica como la
|
|
51
|
+
* del backend: 503, nunca `false`, nunca el error crudo del consumidor.
|
|
52
|
+
*/
|
|
53
|
+
export class ScopeResolverError extends Exception {
|
|
54
|
+
static status = 503;
|
|
55
|
+
static code = 'E_AUTHZ_RESOLVER_FAILED';
|
|
56
|
+
constructor(operation, cause) {
|
|
57
|
+
super(`El resolutor de ancestros de scopes falló (${operation})`, { cause });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Un componente de identidad (holder, scope, rol, permiso) no cumple el formato
|
|
62
|
+
* del motor. Es una pregunta inválida (422), no un "sin permiso": aceptarla
|
|
63
|
+
* fundiría identidades distintas en una (`user:undefined`, `{app, uuid}` que
|
|
64
|
+
* colapsa a la raíz) o produciría ids que otro backend no puede representar.
|
|
65
|
+
*/
|
|
66
|
+
export class InvalidIdentityError extends Exception {
|
|
67
|
+
static status = 422;
|
|
68
|
+
static code = 'E_AUTHZ_INVALID_IDENTITY';
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Un slug de rol/permiso no cumple la gramática del catálogo: formato,
|
|
72
|
+
* longitud, nombre reservado o familia de prefijos reservada. 422 porque es la
|
|
73
|
+
* pregunta la que está mal formada; el catálogo no se consulta.
|
|
74
|
+
*/
|
|
75
|
+
export class InvalidSlugError extends Exception {
|
|
76
|
+
static status = 422;
|
|
77
|
+
static code = 'E_AUTHZ_INVALID_SLUG';
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* La configuración del consumidor es contradictoria (p. ej. `holderTypes` no
|
|
81
|
+
* inyectivo). 500: la aplicación está mal construida, no hay pregunta que
|
|
82
|
+
* responder hasta que se arregle.
|
|
83
|
+
*/
|
|
84
|
+
export class AuthorizationConfigError extends Exception {
|
|
85
|
+
static status = 500;
|
|
86
|
+
static code = 'E_AUTHZ_CONFIG';
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Invariante interno del motor violado (una escritura sin scopes, un batchCheck
|
|
90
|
+
* incompleto). 500: es un bug del paquete o de quien lo extiende, no del
|
|
91
|
+
* llamante ni del backend.
|
|
92
|
+
*/
|
|
93
|
+
export class AuthorizationInternalError extends Exception {
|
|
94
|
+
static status = 500;
|
|
95
|
+
static code = 'E_AUTHZ_INTERNAL';
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* El scope no existe para el consumidor (`resolveChain` devolvió `null`).
|
|
99
|
+
* En lectura el motor responde `false` sin más; en escritura es 422: conceder
|
|
100
|
+
* o denegar en un scope que nadie reconoce dejaría un hecho huérfano que
|
|
101
|
+
* ningún árbol volverá a alcanzar — o, peor, que resucitaría si el uuid se
|
|
102
|
+
* reutiliza (L0.3, N5).
|
|
103
|
+
*/
|
|
104
|
+
export class UnknownScopeError extends Exception {
|
|
105
|
+
static status = 422;
|
|
106
|
+
static code = 'E_AUTHZ_UNKNOWN_SCOPE';
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Se preguntó por un scope que no es la raíz y el driver no tiene resolutor
|
|
110
|
+
* de ancestros. Antes el paquete suplía un default plano ("todo cuelga de
|
|
111
|
+
* app") que convertía cualquier scope inventado en descendiente de la raíz.
|
|
112
|
+
* 422 y no 500: la raíz sigue funcionando; lo que no existe en este
|
|
113
|
+
* despliegue son los niveles intermedios.
|
|
114
|
+
*/
|
|
115
|
+
export class NoScopeResolverError extends Exception {
|
|
116
|
+
static status = 422;
|
|
117
|
+
static code = 'E_AUTHZ_NO_SCOPE_RESOLVER';
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Rol fuera del catálogo para ese nivel (pregunta inválida, 422). Con
|
|
121
|
+
* `scopeType` se buscó por `(slug, scopeType)`; sin él, por uuid (3B ·
|
|
122
|
+
* `purgeRole`, `updateScopedRole`, `deleteScopedRole`).
|
|
123
|
+
*/
|
|
124
|
+
export class UnknownRoleError extends Exception {
|
|
125
|
+
static status = 422;
|
|
126
|
+
static code = 'E_AUTHZ_UNKNOWN_ROLE';
|
|
127
|
+
constructor(slugOrUuid, scopeType) {
|
|
128
|
+
super(scopeType === undefined
|
|
129
|
+
? `Rol con uuid '${slugOrUuid}' no existe en el catálogo`
|
|
130
|
+
: `Rol '${slugOrUuid}' no existe en el catálogo para el nivel '${scopeType}'`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* El rol existe pero es LOCAL a un scope que no está en la cadena del scope
|
|
135
|
+
* de la escritura (3B · B2): un rol de la organization A no existe en B ni
|
|
136
|
+
* en la raíz. 422 y nada escrito — es la barrera que impide asignar el rol
|
|
137
|
+
* de un tenant fuera de su contenedor. En lectura no hay error: el rol
|
|
138
|
+
* simplemente no concede ni es membresía fuera de su owner.
|
|
139
|
+
*/
|
|
140
|
+
export class RoleNotVisibleError extends Exception {
|
|
141
|
+
static status = 422;
|
|
142
|
+
static code = 'E_AUTHZ_ROLE_NOT_VISIBLE';
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* En la cadena del scope de la pregunta hay MÁS DE UN rol visible con ese
|
|
146
|
+
* `(slug, nivel)` (3D · M1): dos locales de owners distintos que un
|
|
147
|
+
* `scopes.moved` juntó, o un local que tapa a un global. El slug ya no
|
|
148
|
+
* identifica un rol y elegir uno («el owner más cercano gana») convertía un
|
|
149
|
+
* homónimo en una escalada de privilegios (auditor V1/V2/V3): la ambigüedad
|
|
150
|
+
* es un ERROR, no una regla de resolución. Toda ruta que direccione por slug
|
|
151
|
+
* —`grant`, `revoke` por uuid, `hasRole`, `listSubjects`— falla CERRADA con
|
|
152
|
+
* 422 nombrando los uuids y sus owners; la forma sin ambigüedad posible es
|
|
153
|
+
* `{ uuid }` (`RoleQuery`). `authorize` no pasa por aquí: no direcciona por
|
|
154
|
+
* slug (lo asignado concede lo que su rol vincula, invariante 1).
|
|
155
|
+
*/
|
|
156
|
+
export class AmbiguousRoleError extends Exception {
|
|
157
|
+
static status = 422;
|
|
158
|
+
static code = 'E_AUTHZ_AMBIGUOUS_ROLE';
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* El `scopeType` de un rol local no es el nivel de su owner ni uno que
|
|
162
|
+
* cuelgue de él (3E · P1, auditor A1). Un rol de nivel SUPERIOR al owner no
|
|
163
|
+
* es visible en ninguna parte —no concede, no es membresía, nadie lo puede
|
|
164
|
+
* asignar— pero OCUPA ese `(slug, nivel)`: bloqueaba el `defineScopedRole`
|
|
165
|
+
* del dueño del árbol y, hasta 3E, el `syncAuthzCatalog` entero de la
|
|
166
|
+
* plataforma. Es squatting con forma de spec, como `permissions: []` (3D ·
|
|
167
|
+
* N3): 422. Los niveles que cuelgan del owner los declara el consumidor con
|
|
168
|
+
* `scopes.descendantsOf`; sin él solo se admite el nivel del owner.
|
|
169
|
+
*/
|
|
170
|
+
export class RoleLevelAboveOwnerError extends Exception {
|
|
171
|
+
static status = 422;
|
|
172
|
+
static code = 'E_AUTHZ_ROLE_LEVEL_ABOVE_OWNER';
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* `updateScopedRole`/`deleteScopedRole` sobre un rol GLOBAL (3B · B3). Los
|
|
176
|
+
* roles del catálogo del config se cambian en el config y se sincronizan
|
|
177
|
+
* (`syncAuthzCatalog`); por la API de delegación son inmutables, o un
|
|
178
|
+
* administrador de tenant podría editar el catálogo de la plataforma.
|
|
179
|
+
*/
|
|
180
|
+
export class RoleImmutableError extends Exception {
|
|
181
|
+
static status = 422;
|
|
182
|
+
static code = 'E_AUTHZ_ROLE_IMMUTABLE';
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Un rol de nivel L no puede llevar (o ser asignado llevando) un permiso
|
|
186
|
+
* cuyo `assignableAt` no incluye L (3B · B5). Es un control de COMPOSICIÓN
|
|
187
|
+
* —`syncAuthzCatalog`, `defineScopedRole`, `updateScopedRole` y, por defensa
|
|
188
|
+
* en profundidad, `grant`—, nunca de evaluación: lo ya asignado sigue
|
|
189
|
+
* concediendo (invariante 1).
|
|
190
|
+
*/
|
|
191
|
+
export class RoleNotAssignableAtError extends Exception {
|
|
192
|
+
static status = 422;
|
|
193
|
+
static code = 'E_AUTHZ_ROLE_NOT_ASSIGNABLE_AT';
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* `defineScopedRole`/`updateScopedRole` con un permiso que el actor no puede
|
|
197
|
+
* delegar (3B · B3): no está en `config.delegablePermissions` (lista blanca;
|
|
198
|
+
* vacía por defecto: nadie delega nada hasta declararla), no existe en el
|
|
199
|
+
* catálogo, o el actor no lo tiene EFECTIVO en el owner (no lo concede
|
|
200
|
+
* ningún rol suyo en la cadena, o lo tiene denegado — auditor C2: un deny
|
|
201
|
+
* no se lava componiendo un rol para un títere).
|
|
202
|
+
*/
|
|
203
|
+
export class PermissionNotDelegableError extends Exception {
|
|
204
|
+
static status = 422;
|
|
205
|
+
static code = 'E_AUTHZ_PERMISSION_NOT_DELEGABLE';
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* El `rank` de un rol local no cumple `0 < rank < min(rank del actor, rank
|
|
209
|
+
* máximo global)` (3B · B3), o el actor intenta tocar un rol de rango ≥ al
|
|
210
|
+
* suyo. Es policy de ESCRITURA (composición y delegación); el motor sigue sin
|
|
211
|
+
* evaluar `rank` en `authorize` (invariante 8).
|
|
212
|
+
*/
|
|
213
|
+
export class RankExceededError extends Exception {
|
|
214
|
+
static status = 422;
|
|
215
|
+
static code = 'E_AUTHZ_RANK_EXCEEDED';
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Dos catálogos de `config.catalogs` declaran el mismo rol `(slug, scopeType)`
|
|
219
|
+
* o el mismo permiso. La identidad de un rol es ese par: compartirla es
|
|
220
|
+
* compartir el rol, y el prune del segundo sync borraría los vínculos del
|
|
221
|
+
* primero en silencio (D3). Un rol pertenece a exactamente un catálogo; se
|
|
222
|
+
* rechaza antes de tocar la base, en el sync y en el diff.
|
|
223
|
+
*/
|
|
224
|
+
export class CatalogConflictError extends Exception {
|
|
225
|
+
static status = 422;
|
|
226
|
+
static code = 'E_AUTHZ_CATALOG_CONFLICT';
|
|
227
|
+
}
|
|
228
|
+
/** Permiso fuera del catálogo (pregunta inválida en `deny`/catálogo, 422). */
|
|
229
|
+
export class UnknownPermissionError extends Exception {
|
|
230
|
+
static status = 422;
|
|
231
|
+
static code = 'E_AUTHZ_UNKNOWN_PERMISSION';
|
|
232
|
+
constructor(slug) {
|
|
233
|
+
super(`Permiso '${slug}' no existe en el catálogo`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* `appAccess({ role })` se retiró (L0.6). `hasRole` es una consulta de
|
|
238
|
+
* MEMBRESÍA y el deny no la gobierna: un middleware sobre ella era un punto de
|
|
239
|
+
* decisión indenegable (un holder con todos sus permisos denegados seguía
|
|
240
|
+
* pasando). 500: es la ruta la que está mal declarada, y debe saltar en el
|
|
241
|
+
* primer request de desarrollo, no en producción.
|
|
242
|
+
*/
|
|
243
|
+
export class RoleIsNotAccessError extends Exception {
|
|
244
|
+
static status = 500;
|
|
245
|
+
static code = 'E_AUTHZ_ROLE_IS_NOT_ACCESS';
|
|
246
|
+
constructor(role) {
|
|
247
|
+
super(`appAccess({ role: ${JSON.stringify(role)} }) ya no existe: un rol es membresía, no acceso, ` +
|
|
248
|
+
`y el deny no lo gobierna. Receta: crea un permiso que represente ese acceso ` +
|
|
249
|
+
`(p. ej. 'admin:access'), vincúlalo al rol en el catálogo y protege la ruta con ` +
|
|
250
|
+
`appAccess({ permission: 'admin:access' }).`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Colgar `child` de `parent` cerraría un ciclo (el padre desciende del hijo,
|
|
255
|
+
* o son el mismo). FGA no detecta ciclos de `parent`: los evalúa, y un grant
|
|
256
|
+
* en cualquier nodo del ciclo concede en todos, raíz incluida (S2). La única
|
|
257
|
+
* barrera es el paquete, antes de escribir nada.
|
|
258
|
+
*/
|
|
259
|
+
export class ScopeCycleError extends Exception {
|
|
260
|
+
static status = 422;
|
|
261
|
+
static code = 'E_AUTHZ_SCOPE_CYCLE';
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* `purgeScope` terminó y el backend todavía muestra hechos del scope. Un
|
|
265
|
+
* número parcial que no lanza deja grants huérfanos e indenegables (S6, B2);
|
|
266
|
+
* se lanza para que el consumidor NO confirme el borrado de su entidad.
|
|
267
|
+
*/
|
|
268
|
+
export class PurgeIncompleteError extends Exception {
|
|
269
|
+
static status = 500;
|
|
270
|
+
static code = 'E_AUTHZ_PURGE_INCOMPLETE';
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* `openfga:import` sobre un store con tuplas y sin `--reconcile`. En FGA la
|
|
274
|
+
* condición no es parte de la clave: escribir "ignorando duplicados" sobre
|
|
275
|
+
* una tupla existente dejaba la caducidad vieja (o ninguna) y reportaba
|
|
276
|
+
* éxito (S7). 409: el estado del destino no es el que el comando espera.
|
|
277
|
+
*/
|
|
278
|
+
export class StoreNotEmptyError extends Exception {
|
|
279
|
+
static status = 409;
|
|
280
|
+
static code = 'E_AUTHZ_STORE_NOT_EMPTY';
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* `config.requireActor: true` y una escritura llegó sin `actor` (2.1, B7).
|
|
284
|
+
* 422 antes de tocar el driver: una auditoría que exige saber quién ordenó
|
|
285
|
+
* cada cambio no puede tener huecos, y el hueco no se descubre después.
|
|
286
|
+
*/
|
|
287
|
+
export class ActorRequiredError extends Exception {
|
|
288
|
+
static status = 422;
|
|
289
|
+
static code = 'E_AUTHZ_ACTOR_REQUIRED';
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* `within` no está en la cadena del scope de la escritura (2.1, B1): el
|
|
293
|
+
* call-site declaró "dentro de MI tenant" y el scope pertenece a otro. 422 y
|
|
294
|
+
* nada escrito: es la barrera contra el administrador de A que concede en
|
|
295
|
+
* una unit de B pasando un uuid ajeno. Vale para las SEIS escrituras del
|
|
296
|
+
* manager (2D · F2): quitar un deny ajeno es conceder, revocar un rol ajeno
|
|
297
|
+
* es sabotear, y purgar o recolgar un scope ajeno es lo mismo.
|
|
298
|
+
*/
|
|
299
|
+
export class NotWithinError extends Exception {
|
|
300
|
+
static status = 422;
|
|
301
|
+
static code = 'E_AUTHZ_NOT_WITHIN';
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* `config.requireWithin` (`true` o `'non-root'`) y una escritura llegó sin
|
|
305
|
+
* `within` (2.1, B1; las seis desde 2D · F2). 422 antes del driver: la
|
|
306
|
+
* contención opt-in (auditor E2) deja de serlo cuando el consumidor lo declara.
|
|
307
|
+
*/
|
|
308
|
+
export class WithinRequiredError extends Exception {
|
|
309
|
+
static status = 422;
|
|
310
|
+
static code = 'E_AUTHZ_WITHIN_REQUIRED';
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* `config.requireWithin: 'non-root'` y una escritura declaró `within: APP_SCOPE`
|
|
314
|
+
* (2.1, 2D · F2). La raíz contiene todo, así que como `within` no acota nada:
|
|
315
|
+
* era el comodín con el que un call-site de tenant satisfacía `requireWithin`
|
|
316
|
+
* sin decir de qué tenant es (auditor 9). 422 y nada escrito; la plataforma,
|
|
317
|
+
* que sí escribe en la raíz, usa `manager.driver()` o una config sin el flag.
|
|
318
|
+
*/
|
|
319
|
+
export class WithinRootForbiddenError extends Exception {
|
|
320
|
+
static status = 422;
|
|
321
|
+
static code = 'E_AUTHZ_WITHIN_ROOT_FORBIDDEN';
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Una enumeración de scopes superó su cota (2.1, B2/B3): `authorizedScopes`
|
|
325
|
+
* por encima de `maxScopes`, o un `descendantsOf` que devolvió más de
|
|
326
|
+
* `maxNodes`. 422 y NUNCA una lista parcial: un listado truncado en silencio
|
|
327
|
+
* es la forma más discreta de fail-open/fail-closed. El llamante acota la
|
|
328
|
+
* pregunta o sube la cota a sabiendas.
|
|
329
|
+
*/
|
|
330
|
+
export class TooManyScopesError extends Exception {
|
|
331
|
+
static status = 422;
|
|
332
|
+
static code = 'E_AUTHZ_TOO_MANY_SCOPES';
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* `sqlDescendantsOf` sobre un dialecto sin observación (2.1, B2): hoy solo PG
|
|
336
|
+
* y SQLite corren la CTE en la suite; MySQL entra en 2.5 con harness propio.
|
|
337
|
+
* 500: config del consumidor, salta en la primera llamada.
|
|
338
|
+
*/
|
|
339
|
+
export class UnsupportedDialectError extends Exception {
|
|
340
|
+
static status = 500;
|
|
341
|
+
static code = 'E_AUTHZ_UNSUPPORTED_DIALECT';
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* `hierarchicalScopeResolver` superó `maxDepth` (2.1, B4). Truncar la cadena
|
|
345
|
+
* perdería la raíz —y con ella cualquier deny en `app`—: fail-open. 500 y no
|
|
346
|
+
* 503: el árbol del consumidor es más profundo de lo declarado, no hay
|
|
347
|
+
* reintento que lo arregle.
|
|
348
|
+
*/
|
|
349
|
+
export class ScopeTooDeepError extends Exception {
|
|
350
|
+
static status = 500;
|
|
351
|
+
static code = 'E_AUTHZ_SCOPE_TOO_DEEP';
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Una primitiva de 2.1 necesita un método OPCIONAL del puerto que el driver
|
|
355
|
+
* activo no implementa (2.1, B5): se nombra el método y se lanza. 500 porque
|
|
356
|
+
* es el despliegue el que está incompleto; nunca se simula la respuesta —un
|
|
357
|
+
* `[]` en `listDenies` sería "sin denies", fail-open.
|
|
358
|
+
*/
|
|
359
|
+
export class UnsupportedOperationError extends Exception {
|
|
360
|
+
static status = 500;
|
|
361
|
+
static code = 'E_AUTHZ_UNSUPPORTED';
|
|
362
|
+
constructor(method, primitive, driver, hint) {
|
|
363
|
+
super(`${primitive} necesita '${method}' y el driver '${driver}' no lo implementa: es un método opcional del ` +
|
|
364
|
+
`puerto (2.1) que este driver tiene que añadir para usar esta primitiva.` + (hint ? ` ${hint}` : ''));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Una vista de `forRequest()` se usó para LEER después de `maxAgeMs` (2D ·
|
|
369
|
+
* F9, auditor 5). El memo de ancestros de la vista es correcto durante un
|
|
370
|
+
* request y peligroso guardado en un módulo: tras un `scopes.moved` serviría
|
|
371
|
+
* la cadena vieja para siempre (cruce de tenant). 500 y ruidoso: es un bug
|
|
372
|
+
* del consumidor (la vista sobrevivió a su request), no un "sin permiso".
|
|
373
|
+
*/
|
|
374
|
+
export class ViewExpiredError extends Exception {
|
|
375
|
+
static status = 500;
|
|
376
|
+
static code = 'E_AUTHZ_VIEW_EXPIRED';
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* `authorizedScopes` sin `scopes.descendantsOf` en el config (2.1, B3). 500
|
|
380
|
+
* y nunca `none`: un "ningún scope" sin haber mirado el árbol sería un
|
|
381
|
+
* fail-closed mentiroso que el consumidor tomaría por respuesta.
|
|
382
|
+
*/
|
|
383
|
+
export class NoDescendantsResolverError extends Exception {
|
|
384
|
+
static status = 500;
|
|
385
|
+
static code = 'E_AUTHZ_NO_DESCENDANTS_RESOLVER';
|
|
386
|
+
}
|
|
387
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { GrantOutcome } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Semántica de `expiresAt` en un re-grant (L0.4), compartida por los drivers
|
|
4
|
+
* para que ambos respondan igual:
|
|
5
|
+
*
|
|
6
|
+
* omitido (`undefined`) → no tocar una caducidad VIGENTE; si la asignación
|
|
7
|
+
* ya expiró, revive sin caducidad (es un grant nuevo)
|
|
8
|
+
* `null` → quitar la caducidad, explícitamente
|
|
9
|
+
* `Date` → fijarla (también a un instante pasado: expira)
|
|
10
|
+
*
|
|
11
|
+
* El defecto que cierra: "asegúrate de que tiene el rol" (grant sin opciones,
|
|
12
|
+
* idempotente por naturaleza) convertía un acceso temporal en permanente.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveGrantExpiry(previous: Date | null, requested: Date | null | undefined, now?: Date): Date | null;
|
|
15
|
+
/** Vigente: sin caducidad o con caducidad futura. */
|
|
16
|
+
export declare function isActiveExpiry(expiresAt: Date | null, now?: Date): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* ¿Mismo instante? Compara el tiempo, no la referencia ni la cadena:
|
|
19
|
+
* `2026-01-01T00:00:00Z` y `…00.000Z` son el mismo momento y no justifican
|
|
20
|
+
* una reescritura (que en openfga es delete+write con ventana de denegación).
|
|
21
|
+
*/
|
|
22
|
+
export declare function sameInstant(a: Date | null | undefined, b: Date | null | undefined): boolean;
|
|
23
|
+
/** Lo que devuelve el backend como caducidad (número, ISO, Date) → Date | null. */
|
|
24
|
+
export declare function toExpiryDate(value: unknown): Date | null;
|
|
25
|
+
/** ¿El outcome de un grant cambió la caducidad de una asignación que ya existía? */
|
|
26
|
+
export declare function expiryChanged(outcome: GrantOutcome): boolean;
|
|
27
|
+
//# sourceMappingURL=expiry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expiry.d.ts","sourceRoot":"","sources":["../../src/expiry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAG9C;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,IAAI,GAAG,IAAI,EACrB,SAAS,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,EAClC,GAAG,GAAE,IAAoB,GACxB,IAAI,GAAG,IAAI,CAGb;AAED,qDAAqD;AACrD,wBAAgB,cAAc,CAAC,SAAS,EAAE,IAAI,GAAG,IAAI,EAAE,GAAG,GAAE,IAAoB,GAAG,OAAO,CAEzF;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,EAAE,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAI3F;AAED,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI,CAKxD;AAED,oFAAoF;AACpF,wBAAgB,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAM5D"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { systemClock } from './clock.js';
|
|
2
|
+
/**
|
|
3
|
+
* Semántica de `expiresAt` en un re-grant (L0.4), compartida por los drivers
|
|
4
|
+
* para que ambos respondan igual:
|
|
5
|
+
*
|
|
6
|
+
* omitido (`undefined`) → no tocar una caducidad VIGENTE; si la asignación
|
|
7
|
+
* ya expiró, revive sin caducidad (es un grant nuevo)
|
|
8
|
+
* `null` → quitar la caducidad, explícitamente
|
|
9
|
+
* `Date` → fijarla (también a un instante pasado: expira)
|
|
10
|
+
*
|
|
11
|
+
* El defecto que cierra: "asegúrate de que tiene el rol" (grant sin opciones,
|
|
12
|
+
* idempotente por naturaleza) convertía un acceso temporal en permanente.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveGrantExpiry(previous, requested, now = systemClock()) {
|
|
15
|
+
if (requested !== undefined)
|
|
16
|
+
return requested;
|
|
17
|
+
return isActiveExpiry(previous, now) ? previous : null;
|
|
18
|
+
}
|
|
19
|
+
/** Vigente: sin caducidad o con caducidad futura. */
|
|
20
|
+
export function isActiveExpiry(expiresAt, now = systemClock()) {
|
|
21
|
+
return expiresAt === null || expiresAt.getTime() > now.getTime();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* ¿Mismo instante? Compara el tiempo, no la referencia ni la cadena:
|
|
25
|
+
* `2026-01-01T00:00:00Z` y `…00.000Z` son el mismo momento y no justifican
|
|
26
|
+
* una reescritura (que en openfga es delete+write con ventana de denegación).
|
|
27
|
+
*/
|
|
28
|
+
export function sameInstant(a, b) {
|
|
29
|
+
if (!a && !b)
|
|
30
|
+
return true;
|
|
31
|
+
if (!a || !b)
|
|
32
|
+
return false;
|
|
33
|
+
return a.getTime() === b.getTime();
|
|
34
|
+
}
|
|
35
|
+
/** Lo que devuelve el backend como caducidad (número, ISO, Date) → Date | null. */
|
|
36
|
+
export function toExpiryDate(value) {
|
|
37
|
+
if (value === null || value === undefined || value === '')
|
|
38
|
+
return null;
|
|
39
|
+
if (value instanceof Date)
|
|
40
|
+
return value;
|
|
41
|
+
const date = new Date(value);
|
|
42
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
43
|
+
}
|
|
44
|
+
/** ¿El outcome de un grant cambió la caducidad de una asignación que ya existía? */
|
|
45
|
+
export function expiryChanged(outcome) {
|
|
46
|
+
return (outcome.existed &&
|
|
47
|
+
outcome.previousExpiresAt !== undefined &&
|
|
48
|
+
!sameInstant(outcome.previousExpiresAt, outcome.expiresAt));
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=expiry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expiry.js","sourceRoot":"","sources":["../../src/expiry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAExC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAqB,EACrB,SAAkC,EAClC,MAAY,WAAW,EAAE;IAEzB,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAC7C,OAAO,cAAc,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAA;AACxD,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,cAAc,CAAC,SAAsB,EAAE,MAAY,WAAW,EAAE;IAC9E,OAAO,SAAS,KAAK,IAAI,IAAI,SAAS,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,CAAA;AAClE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,CAA0B,EAAE,CAA0B;IAChF,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IACzB,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAA;IAC1B,OAAO,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,OAAO,EAAE,CAAA;AACpC,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,IAAI,CAAA;IACtE,IAAI,KAAK,YAAY,IAAI;QAAE,OAAO,KAAK,CAAA;IACvC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,KAAY,CAAC,CAAA;IACnC,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;AACnD,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,aAAa,CAAC,OAAqB;IACjD,OAAO,CACL,OAAO,CAAC,OAAO;QACf,OAAO,CAAC,iBAAiB,KAAK,SAAS;QACvC,CAAC,WAAW,CAAC,OAAO,CAAC,iBAAiB,EAAE,OAAO,CAAC,SAAS,CAAC,CAC3D,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ScopeChainResolver, ScopeRef } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Un nodo del árbol del consumidor, tal como está en SU tabla (2.5-B · K1):
|
|
4
|
+
* - `self`: el propio scope CANÓNICO (la fila leída: su `type` y su `uuid`
|
|
5
|
+
* tal como están guardados, no tal como llegó la pregunta);
|
|
6
|
+
* - `parent`: el padre directo (`ScopeRef`; si es `app`, la cadena termina
|
|
7
|
+
* ahí), o `null` si el nodo es de primer nivel ⇒ cuelga de `app`.
|
|
8
|
+
*/
|
|
9
|
+
export interface ScopeNode {
|
|
10
|
+
self: ScopeRef;
|
|
11
|
+
parent: ScopeRef | null;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* `nodeOf` del consumidor: la fila de un scope.
|
|
15
|
+
* - `ScopeNode`: el nodo existe; `self` es su identidad canónica y `parent`
|
|
16
|
+
* su padre (`null` = primer nivel);
|
|
17
|
+
* - `undefined`: el scope NO existe ⇒ el resolutor devuelve `null` y el
|
|
18
|
+
* motor deniega/rechaza escribir (L0.3). Vale también a mitad de cadena:
|
|
19
|
+
* un hijo cuyo padre ya no existe es un árbol roto, no un nodo de `app`.
|
|
20
|
+
* Si lanza, el error se propaga tal cual y el driver lo clasifica como 503
|
|
21
|
+
* `E_AUTHZ_RESOLVER_FAILED` (nunca `[]`, nunca `false`). Un `self` o un
|
|
22
|
+
* padre mal formado (`{app, uuid}`, tipo en mayúsculas…) es 503
|
|
23
|
+
* `E_AUTHZ_RESOLVER_FAILED` del propio resolutor (F6): nunca se normaliza en
|
|
24
|
+
* silencio. Un `self` que no sea el scope preguntado (otro tipo, o un uuid
|
|
25
|
+
* que no es el mismo id salvo mayúsculas/guiones) también es 503: la fila
|
|
26
|
+
* que devuelves tiene que ser la del scope que se te pidió.
|
|
27
|
+
*
|
|
28
|
+
* Por qué devuelve el nodo entero y no solo el padre (`parentOf` de 2.1): con
|
|
29
|
+
* el tipo `uuid` de PostgreSQL o una collation `*_ci`, `SELECT … WHERE uuid =
|
|
30
|
+
* ?` encuentra la fila para un ALIAS del id (mayúsculas, sin guiones); si el
|
|
31
|
+
* resolutor solo devolvía el padre, la cadena se construía con el alias del
|
|
32
|
+
* llamante y los hechos —guardados canónicos— no casaban (el deny se evadía).
|
|
33
|
+
*/
|
|
34
|
+
export type NodeOf = (scope: ScopeRef) => Promise<ScopeNode | undefined>;
|
|
35
|
+
export interface HierarchicalResolverOptions {
|
|
36
|
+
nodeOf: NodeOf;
|
|
37
|
+
/**
|
|
38
|
+
* Número máximo de ANCESTROS (`app` incluida; el propio scope no cuenta).
|
|
39
|
+
* Default 64. Superado ⇒ lanza 500 `E_AUTHZ_SCOPE_TOO_DEEP`: truncar
|
|
40
|
+
* devolvería una cadena sin la raíz y el deny de `app` dejaría de verse
|
|
41
|
+
* (fail-open).
|
|
42
|
+
*/
|
|
43
|
+
maxDepth?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Construye un `ScopeChainResolver` a partir de un `nodeOf` (2.1, B4; 2.5-B ·
|
|
47
|
+
* K1): lee el nodo (canónico), sube padre a padre hasta `app`, con conjunto
|
|
48
|
+
* de visitados (un ciclo lanza 422 `E_AUTHZ_SCOPE_CYCLE`: FGA evaluaría el
|
|
49
|
+
* ciclo y un grant en cualquier nodo concedería en la raíz, S2) y cota de
|
|
50
|
+
* profundidad que lanza. Cuesta una llamada a `nodeOf` por nivel: envuélvelo
|
|
51
|
+
* con `memoizeAncestors` (o usa `forRequest()`) para que una request no lo
|
|
52
|
+
* pague por cada pregunta. La cadena que devuelve es `[self canónico,
|
|
53
|
+
* ...padres canónicos, APP_SCOPE]`: cada nivel es la fila leída.
|
|
54
|
+
*/
|
|
55
|
+
export declare function hierarchicalScopeResolver(options: HierarchicalResolverOptions): ScopeChainResolver;
|
|
56
|
+
//# sourceMappingURL=hierarchical_resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hierarchical_resolver.d.ts","sourceRoot":"","sources":["../../src/hierarchical_resolver.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAE9D;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAA;CACxB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAA;AAExE,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,2BAA2B,GAAG,kBAAkB,CA8ElG"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { ScopeCycleError, ScopeResolverError, ScopeTooDeepError } from './errors.js';
|
|
2
|
+
import { assertScope } from './identity.js';
|
|
3
|
+
import { APP_SCOPE, APP_SCOPE_TYPE } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Construye un `ScopeChainResolver` a partir de un `nodeOf` (2.1, B4; 2.5-B ·
|
|
6
|
+
* K1): lee el nodo (canónico), sube padre a padre hasta `app`, con conjunto
|
|
7
|
+
* de visitados (un ciclo lanza 422 `E_AUTHZ_SCOPE_CYCLE`: FGA evaluaría el
|
|
8
|
+
* ciclo y un grant en cualquier nodo concedería en la raíz, S2) y cota de
|
|
9
|
+
* profundidad que lanza. Cuesta una llamada a `nodeOf` por nivel: envuélvelo
|
|
10
|
+
* con `memoizeAncestors` (o usa `forRequest()`) para que una request no lo
|
|
11
|
+
* pague por cada pregunta. La cadena que devuelve es `[self canónico,
|
|
12
|
+
* ...padres canónicos, APP_SCOPE]`: cada nivel es la fila leída.
|
|
13
|
+
*/
|
|
14
|
+
export function hierarchicalScopeResolver(options) {
|
|
15
|
+
const { nodeOf } = options;
|
|
16
|
+
const maxDepth = options.maxDepth ?? 64;
|
|
17
|
+
if (typeof nodeOf !== 'function') {
|
|
18
|
+
throw new TypeError('hierarchicalScopeResolver: nodeOf debe ser una función');
|
|
19
|
+
}
|
|
20
|
+
if (!Number.isInteger(maxDepth) || maxDepth < 1) {
|
|
21
|
+
throw new TypeError(`hierarchicalScopeResolver: maxDepth debe ser un entero >= 1 (llegó ${String(maxDepth)})`);
|
|
22
|
+
}
|
|
23
|
+
// Separador no imprimible, escrito como escape (nunca como carácter
|
|
24
|
+
// literal): sin él `org`+`a-1` y `o`+`rga-1` colisionaban y un árbol
|
|
25
|
+
// legítimo era un 422 falso de ciclo (F6, CR4).
|
|
26
|
+
const key = (s) => `${s.type}\u001f${s.uuid ?? ''}`;
|
|
27
|
+
const fold = (uuid) => (uuid ?? '').toLowerCase().replaceAll('-', '');
|
|
28
|
+
return async (scope) => {
|
|
29
|
+
if (scope.type === APP_SCOPE_TYPE)
|
|
30
|
+
return [APP_SCOPE];
|
|
31
|
+
const label = `hierarchicalScopeResolver(${scope.type}:${scope.uuid ?? ''})`;
|
|
32
|
+
const chain = [];
|
|
33
|
+
const visited = new Set();
|
|
34
|
+
let asked = scope;
|
|
35
|
+
for (;;) {
|
|
36
|
+
const node = await nodeOf(asked);
|
|
37
|
+
if (node === undefined)
|
|
38
|
+
return null;
|
|
39
|
+
// La RESPUESTA del árbol se valida (D13, F6, K1): un `self` o un padre
|
|
40
|
+
// mal formado no es un nodo, es un fallo de la dependencia ⇒ 503 con la
|
|
41
|
+
// causa; y `self` tiene que ser la fila del scope que se pidió.
|
|
42
|
+
if (!node || typeof node !== 'object' || !('self' in node) || !('parent' in node)) {
|
|
43
|
+
throw new ScopeResolverError(label, new TypeError(`nodeOf devolvió ${typeof node} en vez de { self, parent } | undefined`));
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
assertScope(node.self);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
throw new ScopeResolverError(label, error);
|
|
50
|
+
}
|
|
51
|
+
if (node.self.type !== asked.type || fold(node.self.uuid) !== fold(asked.uuid)) {
|
|
52
|
+
throw new ScopeResolverError(label, new TypeError(`nodeOf(${asked.type}:${asked.uuid ?? ''}) devolvió self = ${node.self.type}:${node.self.uuid ?? ''}: no es la fila del scope pedido`));
|
|
53
|
+
}
|
|
54
|
+
const self = node.self;
|
|
55
|
+
const k = key(self);
|
|
56
|
+
if (visited.has(k)) {
|
|
57
|
+
throw new ScopeCycleError(`hierarchicalScopeResolver: ciclo al resolver ${scope.type}:${scope.uuid} — ` +
|
|
58
|
+
`${self.type}:${self.uuid} ya está en la cadena (${chain.map((s) => `${s.type}:${s.uuid}`).join(' → ') || 'vacía'})`);
|
|
59
|
+
}
|
|
60
|
+
visited.add(k);
|
|
61
|
+
chain.push(self);
|
|
62
|
+
// Los ancestros serán (nodos leídos − 1) + `app` = nodos leídos: si ya
|
|
63
|
+
// superan la cota, se lanza ahora — nunca se devuelve una cadena corta.
|
|
64
|
+
if (chain.length > maxDepth) {
|
|
65
|
+
throw new ScopeTooDeepError(`hierarchicalScopeResolver: la cadena de ${scope.type}:${scope.uuid} supera maxDepth=${maxDepth} ` +
|
|
66
|
+
`(truncarla dejaría fuera la raíz y sus denies)`);
|
|
67
|
+
}
|
|
68
|
+
const parent = node.parent;
|
|
69
|
+
if (parent === null) {
|
|
70
|
+
chain.push(APP_SCOPE);
|
|
71
|
+
return chain;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
assertScope(parent);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
throw new ScopeResolverError(label, error);
|
|
78
|
+
}
|
|
79
|
+
if (parent.type === APP_SCOPE_TYPE) {
|
|
80
|
+
chain.push(APP_SCOPE);
|
|
81
|
+
return chain;
|
|
82
|
+
}
|
|
83
|
+
asked = parent;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=hierarchical_resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hierarchical_resolver.js","sourceRoot":"","sources":["../../src/hierarchical_resolver.ts"],"names":[],"mappings":"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"}
|