@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
package/build/src/types.d.ts
CHANGED
|
@@ -12,10 +12,12 @@
|
|
|
12
12
|
* suite corre contra cada driver — es el juez del contrato):
|
|
13
13
|
*
|
|
14
14
|
* 1. **Scopes jerárquicos, herencia SOLO hacia abajo.** La cadena de un scope
|
|
15
|
-
* es `[scope, ...ancestros]` (`app` es la raíz: su cadena es él
|
|
15
|
+
* es `[scope canónico, ...ancestros]` (`app` es la raíz: su cadena es él
|
|
16
|
+
* mismo); la identidad de un scope es la que devuelve el resolutor, nunca
|
|
17
|
+
* la forma con la que lo escribió el llamante (2.5-B · K1).
|
|
16
18
|
* Un grant en un scope autoriza en ese scope y en TODOS sus descendientes;
|
|
17
19
|
* nunca en hermanos ni ancestros. En T1 solo opera `app` (los ancestros de
|
|
18
|
-
* `organization`/`unit` se completan en T2/T3 vía `
|
|
20
|
+
* `organization`/`unit` se completan en T2/T3 vía `resolveChain`).
|
|
19
21
|
* 2. **Deny explícito gana.** Un deny de un permiso en cualquier scope de la
|
|
20
22
|
* cadena bloquea `authorize`, aunque un rol lo conceda (el deny también
|
|
21
23
|
* hereda hacia abajo). Quitar el deny restaura el permiso.
|
|
@@ -28,8 +30,12 @@
|
|
|
28
30
|
* holder sin asignación vigente → `false`, nunca throw en `authorize`.
|
|
29
31
|
* En cambio `grant`/`deny` con rol/permiso fuera del catálogo → throw
|
|
30
32
|
* (error de programación, no de autorización).
|
|
31
|
-
* 6. **Idempotencia de escritura.** Re-`grant` no duplica (
|
|
32
|
-
* `
|
|
33
|
+
* 6. **Idempotencia de escritura.** Re-`grant` no duplica (`expiresAt` en
|
|
34
|
+
* tres estados: omitido preserva, `null` quita, `Date` fija);
|
|
35
|
+
* re-`revoke`/re-`deny`/re-`removeDeny` son no-ops seguros.
|
|
36
|
+
* 7. **El árbol es un hecho del contrato.** Un scope que el resolutor no
|
|
37
|
+
* conoce (`null`) deniega, no lista y no admite escrituras; `purgeScope`
|
|
38
|
+
* borra los hechos del scope exacto (los del catálogo) y demuestra cero.
|
|
33
39
|
*/
|
|
34
40
|
/** Referencia polimórfica al holder: morph name + uuid. */
|
|
35
41
|
export interface SubjectRef {
|
|
@@ -40,7 +46,7 @@ export interface SubjectRef {
|
|
|
40
46
|
* Nivel de scope. El motor solo conoce la raíz (`app`, uuid null); los demás
|
|
41
47
|
* niveles los define el CONSUMIDOR — `organization`/`unit` en este chasis,
|
|
42
48
|
* pero podrían ser `project`, `site`, `case`… El árbol lo declara el
|
|
43
|
-
* `
|
|
49
|
+
* `ScopeChainResolver` que se inyecta a los drivers, así que el motor no
|
|
44
50
|
* necesita conocer la taxonomía.
|
|
45
51
|
*
|
|
46
52
|
* Un consumidor que quiera seguridad de tipos define su propia unión:
|
|
@@ -56,10 +62,106 @@ export interface ScopeRef {
|
|
|
56
62
|
}
|
|
57
63
|
/** El scope raíz de aplicación (nivel plataforma). */
|
|
58
64
|
export declare const APP_SCOPE: ScopeRef;
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Opciones comunes a TODA escritura del manager (`grant`, `revoke`, `deny`,
|
|
67
|
+
* `removeDeny`, `scopes.*`) — 2.1, B7.
|
|
68
|
+
*/
|
|
69
|
+
export interface WriteOptions {
|
|
70
|
+
/**
|
|
71
|
+
* Quién ordena la escritura. Se valida como identidad (422 si está mal
|
|
72
|
+
* formado) y viaja en `AuthzWriteEvent.actor` para que la auditoría del
|
|
73
|
+
* consumidor no dependa de un `AsyncLocalStorage` que el paquete no tiene.
|
|
74
|
+
* Con `requireActor: true` en el config, omitirlo es 422
|
|
75
|
+
* `E_AUTHZ_ACTOR_REQUIRED` antes de tocar el driver. El motor NO lo evalúa:
|
|
76
|
+
* quién puede conceder qué es policy del consumidor (invariante 8).
|
|
77
|
+
*/
|
|
78
|
+
actor?: SubjectRef;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Opciones de las NUEVE escrituras del manager (`grant`, `revoke`, `deny`,
|
|
82
|
+
* `removeDeny`, `scopes.attached/moved/detached` y, desde 3D · M3, la API de
|
|
83
|
+
* delegación `defineScopedRole`/`updateScopedRole`/`deleteScopedRole`) —
|
|
84
|
+
* 2.1, B1; 2D · F2.
|
|
85
|
+
*/
|
|
86
|
+
export interface ScopedWriteOptions extends WriteOptions {
|
|
87
|
+
/**
|
|
88
|
+
* Contención: el scope de la escritura tiene que estar DENTRO de `within`
|
|
89
|
+
* (`within ∈ chain(scope)`, inclusive; `APP_SCOPE` contiene todo). Si no,
|
|
90
|
+
* 422 `E_AUTHZ_NOT_WITHIN` y nada se escribe. Es lo que impide que el
|
|
91
|
+
* administrador de la organización A conceda en una unit de B pasando un
|
|
92
|
+
* uuid ajeno: el call-site declara "dentro de MI tenant" y el motor lo
|
|
93
|
+
* comprueba contra el árbol, en fresco (nunca con el memo por request).
|
|
94
|
+
* Qué scope se contrasta: el de `grant`/`revoke`/`deny`/`removeDeny`; el
|
|
95
|
+
* PADRE (nuevo) Y la cadena ACTUAL del hijo en `scopes.moved` (origen y
|
|
96
|
+
* destino, 2E · H1: notifica ANTES de recolgar tu fila), lo mismo en
|
|
97
|
+
* `scopes.attached` cuando el hijo ya existe (es un move; un nodo nuevo
|
|
98
|
+
* solo contrasta el padre); el propio hijo en `scopes.detached`; el OWNER
|
|
99
|
+
* del rol en `defineScopedRole`/`updateScopedRole`/`deleteScopedRole`. Con
|
|
100
|
+
* `requireWithin: true` en el config, omitirlo es 422
|
|
101
|
+
* `E_AUTHZ_WITHIN_REQUIRED`; con `'non-root'`, además `APP_SCOPE` como
|
|
102
|
+
* `within` es 422 `E_AUTHZ_WITHIN_ROOT_FORBIDDEN` (no acota nada).
|
|
103
|
+
* `within` viene de la SESIÓN (el tenant autenticado), nunca del cuerpo
|
|
104
|
+
* de la petición: `within = scope` satisface siempre por definición.
|
|
105
|
+
*/
|
|
106
|
+
within?: ScopeRef;
|
|
107
|
+
}
|
|
108
|
+
export interface GrantOptions extends ScopedWriteOptions {
|
|
109
|
+
/**
|
|
110
|
+
* Caducidad de la asignación, en TRES estados (L0.4):
|
|
111
|
+
* - omitido: no tocar una caducidad vigente (si la asignación ya había
|
|
112
|
+
* expirado, revive sin caducidad);
|
|
113
|
+
* - `null`: quitar la caducidad;
|
|
114
|
+
* - `Date`: fijarla.
|
|
115
|
+
* Antes "omitido" borraba la caducidad: un "asegúrate de que tiene el rol"
|
|
116
|
+
* convertía un acceso temporal en permanente.
|
|
117
|
+
*/
|
|
61
118
|
expiresAt?: Date | null;
|
|
62
119
|
}
|
|
120
|
+
/** Opciones de `deny` (2.1): contención y actor; sin caducidad (un deny que caduca es fail-open por reloj). */
|
|
121
|
+
export type DenyOptions = ScopedWriteOptions;
|
|
122
|
+
/** Lo que un `grant` hizo, para que el manager audite y el juez lo observe. */
|
|
123
|
+
export interface GrantOutcome {
|
|
124
|
+
/** Ya había una asignación de ese rol en ese scope exacto. */
|
|
125
|
+
existed: boolean;
|
|
126
|
+
/** Caducidad que tenía antes (solo si `existed` y se pudo leer). */
|
|
127
|
+
previousExpiresAt?: Date | null;
|
|
128
|
+
/** Caducidad con la que queda tras la escritura. */
|
|
129
|
+
expiresAt: Date | null;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Cómo se direcciona un rol en el puerto (`grant`, `revoke`, `hasRole`,
|
|
133
|
+
* `listSubjects`).
|
|
134
|
+
*
|
|
135
|
+
* - **string** (slug): en cada nivel de la cadena solo cuenta el rol de ESE
|
|
136
|
+
* nivel (el `owner` de app casa en app y hereda hacia abajo; un `owner` de
|
|
137
|
+
* organization jamás casa en app).
|
|
138
|
+
* - **`{ slug, scopeType }`**: el rol de un nivel concreto; solo los scopes
|
|
139
|
+
* de la cadena de ese tipo cuentan (L0.6).
|
|
140
|
+
* - **`{ uuid }`** (3D · M1): la forma EXACTA. Desde que un rol puede ser
|
|
141
|
+
* local a un scope (3B), el slug NO identifica un rol —dos tenants definen
|
|
142
|
+
* `lead@unit`— y un `scopes.moved` legítimo puede juntar dos homónimos en
|
|
143
|
+
* la misma cadena: entonces las dos formas por slug fallan cerradas con
|
|
144
|
+
* 422 `E_AUTHZ_AMBIGUOUS_ROLE` y esta es la única que responde. El uuid
|
|
145
|
+
* tiene que estar en el catálogo (422 `E_AUTHZ_UNKNOWN_ROLE`) y ser
|
|
146
|
+
* visible en el scope de la operación —declarado para su nivel y global o
|
|
147
|
+
* con el owner en la cadena— (422 `E_AUTHZ_ROLE_NOT_VISIBLE`).
|
|
148
|
+
*/
|
|
149
|
+
export type RoleQuery = string | {
|
|
150
|
+
slug: string;
|
|
151
|
+
scopeType: ScopeType;
|
|
152
|
+
} | {
|
|
153
|
+
uuid: string;
|
|
154
|
+
};
|
|
155
|
+
/** `RoleQuery` ya validado (`normalizeRoleQuery`): o nombre, o identidad; nunca las dos. */
|
|
156
|
+
export type NormalizedRoleQuery = {
|
|
157
|
+
slug: string;
|
|
158
|
+
scopeType?: ScopeType;
|
|
159
|
+
uuid?: undefined;
|
|
160
|
+
} | {
|
|
161
|
+
uuid: string;
|
|
162
|
+
slug?: undefined;
|
|
163
|
+
scopeType?: undefined;
|
|
164
|
+
};
|
|
63
165
|
export interface AuthorizationDriver {
|
|
64
166
|
/**
|
|
65
167
|
* ¿El holder tiene el permiso en el scope? Evalúa la cadena completa:
|
|
@@ -69,26 +171,43 @@ export interface AuthorizationDriver {
|
|
|
69
171
|
authorize(subject: SubjectRef, permission: string, scope: ScopeRef): Promise<boolean>;
|
|
70
172
|
/**
|
|
71
173
|
* Asigna un rol al holder en un scope. El rol debe existir en el catálogo
|
|
72
|
-
* para `scope.type` (
|
|
174
|
+
* para `scope.type` (422 si no) y el scope para el resolutor (422 si no).
|
|
175
|
+
* Idempotente: re-grant no duplica; `expiresAt` sigue los tres estados de
|
|
176
|
+
* `GrantOptions`. Devuelve qué hizo (`GrantOutcome`).
|
|
177
|
+
*/
|
|
178
|
+
grant(subject: SubjectRef, role: RoleQuery, scope: ScopeRef, options?: GrantOptions): Promise<GrantOutcome>;
|
|
179
|
+
/**
|
|
180
|
+
* Quita la asignación del rol en ese scope exacto. El rol debe existir en
|
|
181
|
+
* el catálogo para `scope.type` (422 si no, como `grant`); la asignación
|
|
182
|
+
* puede no existir (no-op).
|
|
73
183
|
*/
|
|
74
|
-
|
|
75
|
-
/** Quita la asignación del rol en ese scope exacto. No-op si no existía. */
|
|
76
|
-
revoke(subject: SubjectRef, role: string, scope: ScopeRef): Promise<void>;
|
|
184
|
+
revoke(subject: SubjectRef, role: RoleQuery, scope: ScopeRef): Promise<void>;
|
|
77
185
|
/**
|
|
78
186
|
* ¿El holder tiene el rol (vigente) en el scope o en un ancestro?
|
|
79
|
-
* Misma regla de herencia hacia abajo que `authorize`.
|
|
187
|
+
* Misma regla de herencia hacia abajo que `authorize`. Es MEMBRESÍA: el
|
|
188
|
+
* deny no la gobierna, así que nunca decide acceso (para eso, `authorize`).
|
|
80
189
|
*/
|
|
81
|
-
hasRole(subject: SubjectRef, role:
|
|
190
|
+
hasRole(subject: SubjectRef, role: RoleQuery, scope: ScopeRef): Promise<boolean>;
|
|
82
191
|
/**
|
|
83
192
|
* Deny explícito de UN permiso al holder en un scope (y sus descendientes).
|
|
84
193
|
* El permiso debe existir en el catálogo (throw si no). Idempotente.
|
|
85
194
|
*/
|
|
86
195
|
deny(subject: SubjectRef, permission: string, scope: ScopeRef): Promise<void>;
|
|
87
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* Levanta el deny en ese scope exacto. El permiso debe existir en el
|
|
198
|
+
* catálogo (422 si no, como `deny`); el deny puede no existir (no-op).
|
|
199
|
+
*/
|
|
88
200
|
removeDeny(subject: SubjectRef, permission: string, scope: ScopeRef): Promise<void>;
|
|
89
201
|
/** Holders con asignación VIGENTE del rol en ese scope exacto (sin herencia). */
|
|
90
|
-
listSubjects(role:
|
|
91
|
-
/**
|
|
202
|
+
listSubjects(role: RoleQuery, scope: ScopeRef): Promise<SubjectRef[]>;
|
|
203
|
+
/**
|
|
204
|
+
* Roles (slugs) con asignación DIRECTA vigente del holder en ese scope
|
|
205
|
+
* exacto. Es API de MEMBRESÍA y habla en slugs: desde 3B dos roles pueden
|
|
206
|
+
* compartir `(slug, nivel)` con owners distintos, así que un slug de esta
|
|
207
|
+
* lista puede no bastar para volver a direccionar el rol (`grant`/`hasRole`
|
|
208
|
+
* responderían 422 `E_AUTHZ_AMBIGUOUS_ROLE`). La forma sin ambigüedad es
|
|
209
|
+
* `{ uuid }`, y los uuids de la cadena los da `rolesInChain`.
|
|
210
|
+
*/
|
|
92
211
|
listRoles(subject: SubjectRef, scope: ScopeRef): Promise<string[]>;
|
|
93
212
|
/**
|
|
94
213
|
* Scopes del tipo dado donde el holder tiene alguna asignación DIRECTA
|
|
@@ -101,29 +220,360 @@ export interface AuthorizationDriver {
|
|
|
101
220
|
* abierto): el caller consulta `authorize` sobre un scope concreto.
|
|
102
221
|
*/
|
|
103
222
|
listScopes(subject: SubjectRef, permission: string): Promise<ScopeRef[]>;
|
|
223
|
+
/**
|
|
224
|
+
* Borra TODAS las asignaciones y denies del scope EXACTO cuyo rol/permiso
|
|
225
|
+
* está en el catálogo (no de sus descendientes: hasta que exista
|
|
226
|
+
* `descendantsOf`, Fase 2, el consumidor purga cada nodo del subárbol que
|
|
227
|
+
* borra). No consulta el árbol: el scope puede ya no existir para el
|
|
228
|
+
* resolutor. Debe demostrar que ESE conjunto quedó a cero o lanzar (500
|
|
229
|
+
* `E_AUTHZ_PURGE_INCOMPLETE`): un borrado parcial silencioso deja hechos
|
|
230
|
+
* huérfanos e indenegables. Los hechos de roles/permisos retirados del
|
|
231
|
+
* catálogo no son membresía ni conceden nada (las lecturas filtran por el
|
|
232
|
+
* catálogo, D5) y los recoge `authz:reconcile` (3b). La raíz no se purga
|
|
233
|
+
* (422).
|
|
234
|
+
*/
|
|
235
|
+
purgeScope(scope: ScopeRef): Promise<void>;
|
|
236
|
+
/**
|
|
237
|
+
* Notificaciones del árbol del consumidor (`manager.scopes.*`), ya
|
|
238
|
+
* validadas por el paquete (raíz, existencia del padre, ciclos). Un driver
|
|
239
|
+
* que materializa el árbol como hechos propios (modo facts) las necesita;
|
|
240
|
+
* `database` no (lee el árbol vía `resolveChain`). Opcionales.
|
|
241
|
+
*/
|
|
242
|
+
onScopeAttached?(child: ScopeRef, parent: ScopeRef): Promise<void>;
|
|
243
|
+
onScopeMoved?(child: ScopeRef, newParent: ScopeRef): Promise<void>;
|
|
244
|
+
/** Se llama DESPUÉS de `purgeScope` (hechos primero, arista al final: S6). */
|
|
245
|
+
onScopeDetached?(child: ScopeRef): Promise<void>;
|
|
246
|
+
/**
|
|
247
|
+
* Vista del driver que resuelve la CADENA con OTRO resolutor y comparte
|
|
248
|
+
* todo lo demás (conexión, memo del catálogo, deadline). Opcional (2.1):
|
|
249
|
+
* `AuthorizationManager.forRequest()` la usa para leer con un resolutor
|
|
250
|
+
* memoizado por request; un driver que no la implemente sigue funcionando
|
|
251
|
+
* (la vista lee con el driver tal cual, sin memo). Solo el camino de
|
|
252
|
+
* lectura pasa por aquí: las escrituras del manager van al driver original.
|
|
253
|
+
*/
|
|
254
|
+
withChainResolver?(resolveChain: ScopeChainResolver): AuthorizationDriver;
|
|
255
|
+
/**
|
|
256
|
+
* Vista del driver que evalúa el TIEMPO con otro reloj (2.5 · J1) y
|
|
257
|
+
* comparte todo lo demás. `now()` es el instante de pared con el que se
|
|
258
|
+
* decide la caducidad —`expires_at > now` en SQL, `current_time` de cada
|
|
259
|
+
* check de FGA, el filtro de caducidad de las enumeraciones y los tres
|
|
260
|
+
* estados de `resolveGrantExpiry`—. Los sellos de auditoría (`created_at`)
|
|
261
|
+
* NO lo usan (2.5-B · K5): no son decisiones.
|
|
262
|
+
* Opcional: el manager lo aplica si el config trae `clock` (500
|
|
263
|
+
* `E_AUTHZ_CONFIG` si el driver no lo implementa: un reloj que no llega al
|
|
264
|
+
* driver mentiría). El juez lo usa con `injectableClock: true` para fijar
|
|
265
|
+
* la caducidad exacta sin dormir.
|
|
266
|
+
*/
|
|
267
|
+
withClock?(now: () => Date): AuthorizationDriver;
|
|
268
|
+
/**
|
|
269
|
+
* Denies DIRECTOS vigentes del holder (2.1, B5): con `scope`, los de ese
|
|
270
|
+
* scope exacto (sin herencia, invariante 7; scope desconocido ⇒ `[]`); sin
|
|
271
|
+
* él, todos los del holder con su scope (los de scopes que el árbol ya no
|
|
272
|
+
* conoce no se listan, D8). Solo permisos del catálogo (D5). Opcional:
|
|
273
|
+
* ambos drivers del paquete lo implementan; sin él, `effectivePermissions`
|
|
274
|
+
* y `authorizedScopes` lanzan 500 `E_AUTHZ_UNSUPPORTED` (nunca un `[]`
|
|
275
|
+
* que significaría "sin denies": fail-open).
|
|
276
|
+
*/
|
|
277
|
+
listDenies?(subject: SubjectRef, scope?: ScopeRef): Promise<DenyRef[]>;
|
|
278
|
+
/**
|
|
279
|
+
* `authorize` sobre varios scopes, un booleano por posición (2.1, B6).
|
|
280
|
+
* Opcional: el manager compone `Promise.all` de `authorize` sobre una
|
|
281
|
+
* vista memoizada si el driver no lo trae; `openfga` lo implementa con UN
|
|
282
|
+
* batchCheck para todos los scopes, correlacionado por id (L0.14). Misma
|
|
283
|
+
* respuesta que N `authorize`; si una posición no se puede responder
|
|
284
|
+
* (503), no se responde ninguna. Lista vacía ⇒ `[]` sin tocar el backend.
|
|
285
|
+
*/
|
|
286
|
+
authorizeMany?(subject: SubjectRef, permission: string, scopes: ScopeRef[]): Promise<boolean[]>;
|
|
287
|
+
/**
|
|
288
|
+
* Purga un ROL del catálogo con sus hechos (3B · B4): revoca TODAS sus
|
|
289
|
+
* asignaciones en TODOS los scopes, borra sus vínculos rol→permiso y la
|
|
290
|
+
* fila del rol, atómicamente, y sube la versión compartida del catálogo
|
|
291
|
+
* (`withAuthzCatalogWrite`). Es lo que `deleteScopedRole` necesita: un rol
|
|
292
|
+
* borrado sin sus asignaciones dejaría hechos huérfanos que resucitarían
|
|
293
|
+
* al recrear el slug. `uuid` mal formado ⇒ 422 `E_AUTHZ_INVALID_IDENTITY`;
|
|
294
|
+
* desconocido ⇒ 422 `E_AUTHZ_UNKNOWN_ROLE`. No distingue global de local
|
|
295
|
+
* (esa barrera es del manager). Un driver que no pueda purgar (openfga
|
|
296
|
+
* hasta 3b: sus bindings no se enumeran por rol) lo DICE con 500
|
|
297
|
+
* `E_AUTHZ_UNSUPPORTED` y no toca nada — capacidad `purgeRole: false`.
|
|
298
|
+
*
|
|
299
|
+
* OPCIONAL en el puerto (3E · Q4): el manager ya lo trata como opcional
|
|
300
|
+
* (`#optional` ⇒ 500 `E_AUTHZ_UNSUPPORTED` nombrándolo) y declararlo
|
|
301
|
+
* obligatorio rompía al COMPILAR a todo driver de terceros escrito para
|
|
302
|
+
* 2.0/2.1. Un driver que no lo trae no puede tener roles locales:
|
|
303
|
+
* `defineScopedRole` lo dice antes de escribir nada (3E · P4).
|
|
304
|
+
*/
|
|
305
|
+
purgeRole?(roleUuid: string): Promise<void>;
|
|
306
|
+
/**
|
|
307
|
+
* Roles DIRECTOS vigentes del holder en cada scope de `chain` (2D · G5),
|
|
308
|
+
* como pares `{ scope, role }`; solo roles que EXISTEN en ese scope (D5 +
|
|
309
|
+
* 3B · B2: declarados para su nivel y visibles por owner desde ese nivel).
|
|
310
|
+
* Opcional: es lo que `effectivePermissions` usa para leer los roles de
|
|
311
|
+
* toda la cadena en UNA lectura; sin él, el manager compone N `listRoles`.
|
|
312
|
+
* La cadena llega ya resuelta y validada.
|
|
313
|
+
*
|
|
314
|
+
* Devuelve `CatalogRoleRef` (uuid + slug + nivel + owner), no un slug (3D ·
|
|
315
|
+
* M1): el manager usa el uuid tal cual y NUNCA vuelve del slug al catálogo
|
|
316
|
+
* en el camino de policy. Volver del slug hacía que `effectivePermissions`
|
|
317
|
+
* y `defineScopedRole` atribuyeran al holder los permisos de un homónimo
|
|
318
|
+
* (auditor V1: escalada reproducida).
|
|
319
|
+
*/
|
|
320
|
+
rolesInChain?(subject: SubjectRef, chain: ScopeRef[]): Promise<Array<{
|
|
321
|
+
scope: ScopeRef;
|
|
322
|
+
role: CatalogRoleRef;
|
|
323
|
+
}>>;
|
|
104
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* Un subárbol excluido de un `all` (2.1, B3; tipo nominal desde 2D · F10):
|
|
327
|
+
* el scope con el deny vivo Y todos sus descendientes. No es una lista de
|
|
328
|
+
* scopes: un `NOT IN (uuids)` con solo `scope` seguiría listando las units
|
|
329
|
+
* de una organización denegada. Expándelo con
|
|
330
|
+
* `authorization.expandExcludedSubtrees(excluded)` (usa tu `descendantsOf`)
|
|
331
|
+
* o resta el subárbol en tu propia consulta (CTE recursiva, `path LIKE`…).
|
|
332
|
+
*/
|
|
333
|
+
export interface ExcludedSubtree {
|
|
334
|
+
scope: ScopeRef;
|
|
335
|
+
/** Siempre `true`: recuerda que lo excluido es el subárbol entero. */
|
|
336
|
+
includesDescendants: true;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Respuesta de `authorizedScopes(subject, permission, scopeType)` (2.1, B3):
|
|
340
|
+
* - `none`: ningún scope de ese tipo;
|
|
341
|
+
* - `some`: exactamente estos (directos del tipo + descendientes vía
|
|
342
|
+
* `descendantsOf`, menos los que tienen un deny en su cadena), nunca más
|
|
343
|
+
* de `maxScopes`. Coherente con `authorize` scope a scope cuando
|
|
344
|
+
* `descendantsOf` y `resolveChain` describen el mismo árbol; si
|
|
345
|
+
* discrepan, lanza 503 `E_AUTHZ_RESOLVER_FAILED` (2D · F3);
|
|
346
|
+
* - `all`: hay un grant vigente en la raíz `app` (ancestro común de todo el
|
|
347
|
+
* tipo) — MENOS `excludedSubtrees`: los scopes con deny vivo del permiso,
|
|
348
|
+
* cada uno con su subárbol entero. Nunca `all` a secas con denies vivos
|
|
349
|
+
* (juez cruce 5, auditor E1): quien liste "todo" tiene que restar esto.
|
|
350
|
+
*/
|
|
351
|
+
export type AuthorizedScopes = {
|
|
352
|
+
kind: 'none';
|
|
353
|
+
} | {
|
|
354
|
+
kind: 'some';
|
|
355
|
+
scopes: ScopeRef[];
|
|
356
|
+
} | {
|
|
357
|
+
kind: 'all';
|
|
358
|
+
excludedSubtrees: ExcludedSubtree[];
|
|
359
|
+
};
|
|
360
|
+
/** Un deny directo, tal como lo enumera `listDenies` (2.1). */
|
|
361
|
+
export interface DenyRef {
|
|
362
|
+
permission: string;
|
|
363
|
+
scope: ScopeRef;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Mapa morph name → tipo del modelo FGA (`users` → `user`). Lo consume el
|
|
367
|
+
* driver `openfga` (subpath `@jantstack/adonis-authz/openfga`); vive en el
|
|
368
|
+
* puerto para que `defineConfig` lo tipe sin importar el driver (D9).
|
|
369
|
+
*/
|
|
370
|
+
export type HolderTypeMap = Record<string, string>;
|
|
105
371
|
/** Factory registrable en `config/authorization.ts`. */
|
|
106
372
|
export type AuthorizationDriverFactory = () => AuthorizationDriver | Promise<AuthorizationDriver>;
|
|
107
373
|
/**
|
|
108
|
-
* Resolutor de
|
|
374
|
+
* Resolutor de la CADENA de un scope (2.5-B · K1): `[scope canónico,
|
|
375
|
+
* ...ancestros]`, del más cercano a la raíz, con `app` al final. El paquete
|
|
109
376
|
* NO conoce el dominio del consumidor: el chasis inyecta el suyo (que sabe de
|
|
110
|
-
* organizations/organization_units) al construir cada driver
|
|
111
|
-
*
|
|
377
|
+
* organizations/organization_units) al construir cada driver y el manager.
|
|
378
|
+
*
|
|
379
|
+
* El elemento 0 es el PROPIO scope tal como está en la tabla del consumidor
|
|
380
|
+
* (la fila leída), no tal como lo escribió el llamante: es la identidad con
|
|
381
|
+
* la que el paquete lee y escribe todos los hechos. Un motor que canoniza
|
|
382
|
+
* ids (el tipo `uuid` de PostgreSQL, una collation `*_ci`) puede encontrar
|
|
383
|
+
* la fila para un alias (mayúsculas, guiones quitados); devolverla canónica es
|
|
384
|
+
* lo que hace que el deny escrito con la forma real siga casando. Devolver
|
|
385
|
+
* otro scope como elemento 0 es 503 `E_AUTHZ_RESOLVER_FAILED`.
|
|
386
|
+
*
|
|
387
|
+
* `null` significa "este scope no existe": el motor deniega (`authorize`/
|
|
388
|
+
* `hasRole` → false) y rechaza escribir sobre él (`grant`/`deny` → 422
|
|
389
|
+
* `E_AUTHZ_UNKNOWN_SCOPE`). Ya no hay default plano: un driver sin resolutor
|
|
390
|
+
* solo conoce la raíz `app`, y cualquier otro tipo es 422
|
|
391
|
+
* `E_AUTHZ_NO_SCOPE_RESOLVER` (L0.3). Un resolutor que devuelva `[scope,
|
|
392
|
+
* APP_SCOPE]` para lo que no conoce vuelve a abrir el defecto: es su
|
|
393
|
+
* responsabilidad no hacerlo, y el vocabulario para no hacerlo es `null`. La
|
|
394
|
+
* raíz nunca se pregunta: su cadena es `[APP_SCOPE]` por definición.
|
|
112
395
|
*/
|
|
113
|
-
export type
|
|
396
|
+
export type ScopeChainResolver = (scope: ScopeRef) => Promise<ScopeRef[] | null>;
|
|
397
|
+
/**
|
|
398
|
+
* Resolutor de DESCENDIENTES de un scope (2.1, B2): todos los nodos del
|
|
399
|
+
* subárbol (cualquier tipo, cualquier profundidad), sin el propio scope y
|
|
400
|
+
* sin orden exigido. Lo implementa el consumidor (o `sqlDescendantsOf`, el
|
|
401
|
+
* helper opt-in del paquete): el paquete NO lo suple con N+1 llamadas a
|
|
402
|
+
* `resolveChain`. `null` = scope desconocido. Más de `maxNodes` nodos ⇒
|
|
403
|
+
* el consumidor lanza; si devuelve de más, lanza el manager (422
|
|
404
|
+
* `E_AUTHZ_TOO_MANY_SCOPES`). Nunca se llama desde `authorize`/`hasRole`/
|
|
405
|
+
* `list*` (test de arquitectura): solo desde `authorizedScopes`.
|
|
406
|
+
*/
|
|
407
|
+
/**
|
|
408
|
+
* El árbol del consumidor hacia ABAJO (2.1): todos los descendientes de
|
|
409
|
+
* `scope`, en cualquier orden y sin incluirlo. `null` = «este árbol no conoce
|
|
410
|
+
* ese scope».
|
|
411
|
+
*
|
|
412
|
+
* Contrato con un scope que `resolveChain` YA NO conoce (3G · W2, auditor
|
|
413
|
+
* pregunta 2): **el consumidor es la autoridad sobre su tabla y puede
|
|
414
|
+
* devolver los hijos** (una ruta materializada, o un `where parent_id = X`,
|
|
415
|
+
* no necesitan la fila del padre) **o `null`**; el paquete no asume ninguna
|
|
416
|
+
* de las dos. La consecuencia está en `scopes.detached`: si el scope no
|
|
417
|
+
* resuelve y por debajo no llega nada, la purga NO se puede declarar
|
|
418
|
+
* completa (`ScopeDetachOutcome.truncated: true`). Y lo que se devuelva se
|
|
419
|
+
* trata como el subárbol real: los roles de esos owners se purgan con la
|
|
420
|
+
* policy de rango medida en la cadena de CADA owner (3G · W1), nunca en la
|
|
421
|
+
* del scope notificado.
|
|
422
|
+
*
|
|
423
|
+
* Más de `maxNodes` nodos ⇒ el resolutor puede devolver la lista larga (el
|
|
424
|
+
* paquete la caza con 422 `E_AUTHZ_TOO_MANY_SCOPES`) o lanzar; en
|
|
425
|
+
* `authorizedScopes` eso es un 422 y en `scopes.detached`/`defineScopedRole`
|
|
426
|
+
* DEGRADA (3F · S2, y ver el aviso de `#assertLevelUnderOwner`).
|
|
427
|
+
*/
|
|
428
|
+
export type ScopeDescendantsResolver = (scope: ScopeRef, options: {
|
|
429
|
+
maxNodes: number;
|
|
430
|
+
}) => Promise<ScopeRef[] | null>;
|
|
114
431
|
/**
|
|
115
432
|
* Escritura del motor, notificada al hook `onWrite` del config. El chasis lo
|
|
116
433
|
* usa para auditar/emitir SSE; un consumidor puede loguear, notificar, etc.
|
|
117
434
|
*/
|
|
118
435
|
export interface AuthzWriteEvent {
|
|
119
|
-
|
|
120
|
-
|
|
436
|
+
/**
|
|
437
|
+
* `extended`: un re-grant cambió la caducidad de una asignación que ya
|
|
438
|
+
* existía (alargada, acortada o quitada) — lleva `previousExpiresAt`. Un
|
|
439
|
+
* re-grant que no cambia nada sigue siendo `granted` (idempotente).
|
|
440
|
+
* `scope_purged`: `scopes.detached` borró todos los hechos del scope; no
|
|
441
|
+
* lleva `subject` (afecta a todos los holders del scope).
|
|
442
|
+
*/
|
|
443
|
+
action: 'granted' | 'extended' | 'revoked' | 'denied' | 'deny_removed' | 'scope_purged';
|
|
444
|
+
/** Ausente solo en `scope_purged`. */
|
|
445
|
+
subject?: SubjectRef;
|
|
121
446
|
scope: ScopeRef;
|
|
122
|
-
/**
|
|
123
|
-
|
|
447
|
+
/**
|
|
448
|
+
* Quién ordenó la escritura (2.1, B7): lo que el llamante pasó en
|
|
449
|
+
* `WriteOptions.actor`, ya validado. Ausente si no lo pasó.
|
|
450
|
+
*/
|
|
451
|
+
actor?: SubjectRef;
|
|
452
|
+
/**
|
|
453
|
+
* Presente en granted/extended/revoked: el/los rol(es) RESUELTOS (3E · Q7,
|
|
454
|
+
* auditor A8), con `uuid`, `slug`, nivel y owner — no la pregunta cruda.
|
|
455
|
+
*
|
|
456
|
+
* En 1.x era `role: string` (el slug) y en 3D pasó a `RoleQuery`: un sink
|
|
457
|
+
* de auditoría que filtraba por slug dejó de casar EN SILENCIO, que es una
|
|
458
|
+
* pérdida de auditoría, no solo de tipos. Con la forma resuelta el sink
|
|
459
|
+
* vuelve a tener el slug —`event.roles.some((r) => r.slug === 'admin')`— y
|
|
460
|
+
* además el uuid, que es lo único que identifica un rol desde 3A.
|
|
461
|
+
*
|
|
462
|
+
* Es una LISTA porque un `revoke` por slug quita los hechos de TODOS los
|
|
463
|
+
* homónimos visibles en el scope (3B); un `grant` resuelve exactamente uno
|
|
464
|
+
* (con dos sería 422 `E_AUTHZ_AMBIGUOUS_ROLE`). Ausente si el rol no se
|
|
465
|
+
* pudo resolver (scope que el árbol no conoce, rol fuera del catálogo): el
|
|
466
|
+
* driver decidirá el resultado, y el evento no inventa.
|
|
467
|
+
*/
|
|
468
|
+
roles?: CatalogRoleRef[];
|
|
124
469
|
/** Presente en denied/deny_removed. */
|
|
125
470
|
permission?: string;
|
|
471
|
+
/** Caducidad con la que queda la asignación (granted/extended). */
|
|
126
472
|
expiresAt?: Date | null;
|
|
473
|
+
/** Caducidad que tenía antes (solo extended). */
|
|
474
|
+
previousExpiresAt?: Date | null;
|
|
475
|
+
/**
|
|
476
|
+
* `true` cuando la escritura venció el deadline (503 `E_AUTHZ_BACKEND_TIMEOUT`)
|
|
477
|
+
* y el paquete NO sabe si el backend la aplicó: la petición puede aterrizar
|
|
478
|
+
* después de que el llamante recibiera el error. Se notifica ANTES de
|
|
479
|
+
* propagar el 503 para que la auditoría registre un resultado desconocido
|
|
480
|
+
* en vez de un silencio (que se lee como "no pasó nada"). Un 503 que no es
|
|
481
|
+
* timeout (conexión rechazada) no lo lleva: esa escritura no ocurrió.
|
|
482
|
+
*/
|
|
483
|
+
indeterminate?: boolean;
|
|
484
|
+
/**
|
|
485
|
+
* Solo en `scope_purged`: el árbol ya NO conoce el scope notificado —el
|
|
486
|
+
* consumidor borró su fila y avisa después, que es el orden que el paquete
|
|
487
|
+
* admite— o alguno de los roles purgados tenía un owner que tampoco
|
|
488
|
+
* resuelve (3F · S1; 3G · W1/W2). `'owner-detached-unknown'` significa dos
|
|
489
|
+
* cosas a la vez, y las dos importan a quien audita: (a) la purga procede
|
|
490
|
+
* igual —bloquearla dejaba vivos el rol, sus asignaciones y los denies de
|
|
491
|
+
* un scope borrado (auditor N2), sin ninguna salida con `requireActor:
|
|
492
|
+
* true`— y (b) para ESOS roles —los que no tienen dónde medir el rango— la
|
|
493
|
+
* policy de 3E · P3 no se pudo evaluar. Para los demás sí se evalúa: el
|
|
494
|
+
* rango se mide en la cadena del OWNER de cada rol (3G · W1), así que un
|
|
495
|
+
* `detached` de un ancestro desconocido ya NO destruye los roles de
|
|
496
|
+
* descendientes vivos. Sale también con `purgedRoles: 0`.
|
|
497
|
+
*/
|
|
498
|
+
reason?: 'owner-detached-unknown';
|
|
499
|
+
/**
|
|
500
|
+
* Solo en `scope_purged`: la purga de roles se acotó al scope EXACTO
|
|
501
|
+
* porque el subárbol no se pudo enumerar (3F · S2). Ver
|
|
502
|
+
* `ScopeDetachOutcome.truncated`.
|
|
503
|
+
*/
|
|
504
|
+
truncated?: true;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Lo que devuelve `scopes.detached` (3F · S1/S2). Hasta 3E era `void` y no
|
|
508
|
+
* había forma de saber si la purga alcanzó a todo el subárbol ni si la
|
|
509
|
+
* policy de rango se llegó a evaluar.
|
|
510
|
+
*/
|
|
511
|
+
export interface ScopeDetachOutcome {
|
|
512
|
+
/** Roles LOCALES purgados (los del scope y, con `descendantsOf`, los del subárbol). */
|
|
513
|
+
purgedRoles: number;
|
|
514
|
+
/**
|
|
515
|
+
* `true` cuando el subárbol NO se pudo enumerar (más de `maxDescendants`,
|
|
516
|
+
* o un `descendantsOf` que falló) y la purga se acotó al scope EXACTO
|
|
517
|
+
* (3F · S2). Degradar en vez de tumbar la operación es la regla: declarar
|
|
518
|
+
* `scopes.descendantsOf` nunca puede dejarte peor que no declararlo, y
|
|
519
|
+
* hasta 3E un subárbol grande dejaba el `detached` en 503 sin purgar ni
|
|
520
|
+
* los roles ni los hechos (auditor N3). Los roles que quedan abajo no son
|
|
521
|
+
* visibles en ninguna parte —su owner ya no cuelga del árbol—, pero siguen
|
|
522
|
+
* ocupando su `(slug, nivel)`: hay que volver a notificar nodo a nodo o
|
|
523
|
+
* subir la cota.
|
|
524
|
+
*
|
|
525
|
+
* También es `true` cuando el árbol ya NO conoce el scope y `descendantsOf`
|
|
526
|
+
* no devolvió nada debajo (3G · W2, auditor P2): el puerto no le exige
|
|
527
|
+
* responder por un scope que `resolveChain` desconoce —puede devolver sus
|
|
528
|
+
* hijos o `null`, ver `ScopeDescendantsResolver`—, así que un vacío ahí no
|
|
529
|
+
* demuestra que debajo no quedara nada. Decir `truncated: false` era
|
|
530
|
+
* afirmar «purga completa» con el rol de la unit hija vivo y concediendo.
|
|
531
|
+
*/
|
|
532
|
+
truncated: boolean;
|
|
533
|
+
/**
|
|
534
|
+
* Igual que en `AuthzWriteEvent`: el scope notificado (o el owner de algún
|
|
535
|
+
* rol purgado) ya no está en el árbol, así que para esos roles la policy
|
|
536
|
+
* de rango no se pudo evaluar. Presente aunque `purgedRoles` sea 0.
|
|
537
|
+
*/
|
|
538
|
+
reason?: 'owner-detached-unknown';
|
|
539
|
+
}
|
|
540
|
+
/** Un rol del catálogo tal como lo ve el motor (3A · A2/A3, 3B · B2). */
|
|
541
|
+
export interface CatalogRole {
|
|
542
|
+
/** Identidad interna: lo que llevan `authz_assignments.role_uuid` y los ids de binding de FGA. */
|
|
543
|
+
uuid: string;
|
|
544
|
+
slug: string;
|
|
545
|
+
scopeType: ScopeType;
|
|
546
|
+
/** `'global'` o `scopeKey(owner)` (`<tipo>|<uuid>`): el contenedor fuera del cual el rol no existe. */
|
|
547
|
+
owner: string;
|
|
548
|
+
/** Metadata de policy (invariante 8): el motor no lo evalúa en `authorize`. */
|
|
549
|
+
rank: number;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* La IDENTIDAD pública de un rol sin su metadata de policy (3D · M1): lo que
|
|
553
|
+
* devuelve `rolesInChain` y lo que el memo usa para filtrar por owner. El
|
|
554
|
+
* uuid manda; el slug viaja como etiqueta legible.
|
|
555
|
+
*/
|
|
556
|
+
export interface CatalogRoleRef {
|
|
557
|
+
slug: string;
|
|
558
|
+
uuid: string;
|
|
559
|
+
scopeType: ScopeType;
|
|
560
|
+
/** `'global'` o `scopeKey(owner)`. */
|
|
561
|
+
owner: string;
|
|
562
|
+
}
|
|
563
|
+
export interface CatalogPermissionSpec {
|
|
564
|
+
/** Formato `recurso:accion`. */
|
|
565
|
+
slug: string;
|
|
566
|
+
description?: string | null;
|
|
567
|
+
/**
|
|
568
|
+
* Niveles (scope types) cuyos roles PUEDEN llevar este permiso (3B · B5):
|
|
569
|
+
* omitido = cualquiera. Es un control de COMPOSICIÓN: `syncAuthzCatalog`,
|
|
570
|
+
* `defineScopedRole`/`updateScopedRole` y `grant` rechazan (422
|
|
571
|
+
* `E_AUTHZ_ROLE_NOT_ASSIGNABLE_AT`) un rol de otro nivel que lo lleve;
|
|
572
|
+
* `authorize` NO lo mira (invariante 1: lo ya asignado sigue concediendo).
|
|
573
|
+
* Es lo que cubre «un rol de unit no puede llevar org:settings» sin romper
|
|
574
|
+
* la herencia hacia abajo (panel 2026-08-28, H).
|
|
575
|
+
*/
|
|
576
|
+
assignableAt?: ScopeType[];
|
|
127
577
|
}
|
|
128
578
|
export interface CatalogRoleSpec {
|
|
129
579
|
/** UUID fijo opcional (mismo patrón que organization_acl: estable entre entornos). */
|
|
@@ -144,10 +594,62 @@ export interface CatalogRoleSpec {
|
|
|
144
594
|
}
|
|
145
595
|
export interface CatalogSpec {
|
|
146
596
|
/** Todos los permisos del catálogo (formato `recurso:accion`). */
|
|
147
|
-
permissions:
|
|
148
|
-
|
|
149
|
-
description?: string | null;
|
|
150
|
-
}>;
|
|
597
|
+
permissions: CatalogPermissionSpec[];
|
|
598
|
+
/** Roles GLOBALES (owner `global`): un spec nunca declara roles locales. */
|
|
151
599
|
roles: CatalogRoleSpec[];
|
|
152
600
|
}
|
|
601
|
+
/**
|
|
602
|
+
* Un rol LOCAL a un scope, tal como lo define `defineScopedRole(actor,
|
|
603
|
+
* ownerScope, spec)` (3B · B3). Sin `uuid` (lo genera el motor) y con `rank`
|
|
604
|
+
* OBLIGATORIO: `0 < rank < min(rank del actor, rank máximo global)`.
|
|
605
|
+
*/
|
|
606
|
+
export interface ScopedRoleSpec {
|
|
607
|
+
slug: string;
|
|
608
|
+
/** Nivel al que el rol es asignable (dentro del owner). Nunca `app`. */
|
|
609
|
+
scopeType: ScopeType;
|
|
610
|
+
name?: string;
|
|
611
|
+
description?: string | null;
|
|
612
|
+
rank: number;
|
|
613
|
+
/** Slugs de permisos: ⊆ `config.delegablePermissions` ∩ efectivos del actor en el owner. */
|
|
614
|
+
permissions: string[];
|
|
615
|
+
}
|
|
616
|
+
/** Lo que `updateScopedRole` puede cambiar de un rol local: nunca su slug, nivel ni owner. */
|
|
617
|
+
export interface ScopedRoleChanges {
|
|
618
|
+
name?: string;
|
|
619
|
+
description?: string | null;
|
|
620
|
+
rank?: number;
|
|
621
|
+
permissions?: string[];
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Escritura del CATÁLOGO por la API de delegación (3B · B3), notificada al
|
|
625
|
+
* hook `onCatalogWrite` del config. Siempre lleva `actor` (la API lo exige)
|
|
626
|
+
* y el rol tal como queda (`role_purged`: tal como estaba).
|
|
627
|
+
*/
|
|
628
|
+
export interface AuthzCatalogWriteEvent {
|
|
629
|
+
action: 'role_defined' | 'role_updated' | 'role_purged';
|
|
630
|
+
/**
|
|
631
|
+
* Quién lo ordenó. La API de delegación lo exige siempre; ausente solo en
|
|
632
|
+
* los `role_purged` que arrastra `scopes.detached` (3D · M4), donde el
|
|
633
|
+
* actor es el `WriteOptions.actor` de esa notificación del árbol y puede
|
|
634
|
+
* no venir.
|
|
635
|
+
*/
|
|
636
|
+
actor?: SubjectRef;
|
|
637
|
+
role: CatalogRole;
|
|
638
|
+
/** El scope owner del rol (`scopeFromKey(role.owner)`). */
|
|
639
|
+
owner: ScopeRef;
|
|
640
|
+
/** Permisos con los que queda el rol (o tenía, si se purga). */
|
|
641
|
+
permissions: string[];
|
|
642
|
+
/**
|
|
643
|
+
* Solo en `role_defined` (3F · S3): los roles LOCALES de un DESCENDIENTE
|
|
644
|
+
* del owner con ese mismo `(slug, nivel)` que el nuevo acaba de
|
|
645
|
+
* ENSOMBRECER. La autoridad manda —global > local de un ancestro > local
|
|
646
|
+
* de un descendiente—, así que el dueño del árbol siempre puede definir su
|
|
647
|
+
* rol aunque alguien de abajo le haya ocupado el nombre; dentro del
|
|
648
|
+
* subárbol de esos owners toda ruta por slug pasa a 422
|
|
649
|
+
* `E_AUTHZ_AMBIGUOUS_ROLE` (se opera por `{ uuid }`) hasta que se purgue
|
|
650
|
+
* uno. Es el mismo trato que `shadowedByGlobal` en el sync, y como allí:
|
|
651
|
+
* se REPORTA, nunca en silencio.
|
|
652
|
+
*/
|
|
653
|
+
shadowedByAncestor?: CatalogRoleRef[];
|
|
654
|
+
}
|
|
153
655
|
//# sourceMappingURL=types.d.ts.map
|
package/build/src/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,2DAA2D;AAC3D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAA;AAE9B,uCAAuC;AACvC,eAAO,MAAM,cAAc,QAAQ,CAAA;AAEnC,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,SAAS,CAAA;IACf,yDAAyD;IACzD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;AAED,sDAAsD;AACtD,eAAO,MAAM,SAAS,EAAE,QAAqD,CAAA;AAE7E;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,UAAU,CAAA;CACnB;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAA;CAClB;AAED,MAAM,WAAW,YAAa,SAAQ,kBAAkB;IACtD;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAA;CACxB;AAED,+GAA+G;AAC/G,MAAM,MAAM,WAAW,GAAG,kBAAkB,CAAA;AAE5C,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC3B,8DAA8D;IAC9D,OAAO,EAAE,OAAO,CAAA;IAChB,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,IAAI,GAAG,IAAI,CAAA;IAC/B,oDAAoD;IACpD,SAAS,EAAE,IAAI,GAAG,IAAI,CAAA;CACvB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1F,4FAA4F;AAC5F,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,CAAA;CAAE,CAAA;AAE7D,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,SAAS,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAErF;;;;;OAKG;IACH,KAAK,CACH,OAAO,EAAE,UAAU,EACnB,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,QAAQ,EACf,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,YAAY,CAAC,CAAA;IAExB;;;;OAIG;IACH,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE5E;;;;OAIG;IACH,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEhF;;;OAGG;IACH,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE7E;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEnF,iFAAiF;IACjF,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAA;IAErE;;;;;;;OAOG;IACH,SAAS,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IAElE;;;OAGG;IACH,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAA;IAE9E;;;;OAIG;IACH,UAAU,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAA;IAExE;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE1C;;;;;OAKG;IACH,eAAe,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClE,YAAY,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClE,8EAA8E;IAC9E,eAAe,CAAC,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEhD;;;;;;;OAOG;IACH,iBAAiB,CAAC,CAAC,YAAY,EAAE,kBAAkB,GAAG,mBAAmB,CAAA;IAEzE;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,CAAC,GAAG,EAAE,MAAM,IAAI,GAAG,mBAAmB,CAAA;IAEhD;;;;;;;;OAQG;IACH,UAAU,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;IAEtE;;;;;;;OAOG;IACH,aAAa,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;IAE/F;;;;;;;;;;;;;;;;;OAiBG;IACH,SAAS,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE3C;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,KAAK,EAAE,QAAQ,CAAC;QAAC,IAAI,EAAE,cAAc,CAAA;KAAE,CAAC,CAAC,CAAA;CACjH;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,QAAQ,CAAA;IACf,sEAAsE;IACtE,mBAAmB,EAAE,IAAI,CAAA;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,QAAQ,EAAE,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,gBAAgB,EAAE,eAAe,EAAE,CAAA;CAAE,CAAA;AAExD,+DAA+D;AAC/D,MAAM,WAAW,OAAO;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,QAAQ,CAAA;CAChB;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAElD,wDAAwD;AACxD,MAAM,MAAM,0BAA0B,GAAG,MAAM,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAA;AAEjG;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,OAAO,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,CAAA;AAEhF;;;;;;;;;GASG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,wBAAwB,GAAG,CACrC,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,KAC1B,OAAO,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,CAAA;AAE/B;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,cAAc,GAAG,cAAc,CAAA;IACvF,sCAAsC;IACtC,OAAO,CAAC,EAAE,UAAU,CAAA;IACpB,KAAK,EAAE,QAAQ,CAAA;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,EAAE,cAAc,EAAE,CAAA;IACxB,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,mEAAmE;IACnE,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAA;IACvB,iDAAiD;IACjD,iBAAiB,CAAC,EAAE,IAAI,GAAG,IAAI,CAAA;IAC/B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,wBAAwB,CAAA;IACjC;;;;OAIG;IACH,SAAS,CAAC,EAAE,IAAI,CAAA;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,uFAAuF;IACvF,WAAW,EAAE,MAAM,CAAA;IACnB;;;;;;;;;;;;;;;;;OAiBG;IACH,SAAS,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,wBAAwB,CAAA;CAClC;AAgBD,yEAAyE;AACzE,MAAM,WAAW,WAAW;IAC1B,kGAAkG;IAClG,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,SAAS,CAAA;IACpB,uGAAuG;IACvG,KAAK,EAAE,MAAM,CAAA;IACb,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,SAAS,CAAA;IACpB,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,SAAS,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,0CAA0C;IAC1C,SAAS,EAAE,SAAS,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,+DAA+D;IAC/D,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,WAAW,EAAE,qBAAqB,EAAE,CAAA;IACpC,4EAA4E;IAC5E,KAAK,EAAE,eAAe,EAAE,CAAA;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,wEAAwE;IACxE,SAAS,EAAE,SAAS,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,4FAA4F;IAC5F,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB;AAED,8FAA8F;AAC9F,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CACvB;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,cAAc,GAAG,cAAc,GAAG,aAAa,CAAA;IACvD;;;;;OAKG;IACH,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,IAAI,EAAE,WAAW,CAAA;IACjB,2DAA2D;IAC3D,KAAK,EAAE,QAAQ,CAAA;IACf,gEAAgE;IAChE,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,EAAE,cAAc,EAAE,CAAA;CACtC"}
|