@jantstack/adonis-authz 1.1.0 → 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 -58
- 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 +45 -6
- package/build/index.d.ts.map +1 -1
- package/build/index.js +37 -4
- 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 +1020 -271
- 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 +333 -0
- package/build/src/errors.d.ts.map +1 -1
- package/build/src/errors.js +352 -0
- package/build/src/errors.js.map +1 -1
- 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,155 @@
|
|
|
1
|
+
import type { NormalizedRoleQuery, RoleQuery, ScopeRef, SubjectRef } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Validación de identidad del motor: holder, scope, rol y permiso.
|
|
4
|
+
*
|
|
5
|
+
* Vive en un solo sitio y la aplican el manager (una vez por llamada) Y los
|
|
6
|
+
* drivers (defensa en profundidad: el juez del contrato habla con el driver
|
|
7
|
+
* directamente, y un driver de terceros puede usarse sin manager). Nadie
|
|
8
|
+
* duplica la regla: se importa.
|
|
9
|
+
*
|
|
10
|
+
* Por qué es 422 y no "se acepta lo que venga":
|
|
11
|
+
* - `uuid: undefined` en el driver openfga escribía `user:undefined`, y a
|
|
12
|
+
* partir de ahí CUALQUIER holder sin uuid heredaba ese permiso (L0.5).
|
|
13
|
+
* - `{ type: 'app', uuid: 'X' }` colapsaba a la raíz global en openfga y no
|
|
14
|
+
* en database: escalada de tenant a plataforma en un driver y `false` en
|
|
15
|
+
* el otro (L0.10).
|
|
16
|
+
* - `#`, `:`, `|`, `~`, `*` y espacios son sintaxis de FGA o separadores de
|
|
17
|
+
* los ids de binding: un componente que los contenga puede fabricar un
|
|
18
|
+
* userset, un comodín o la clave de OTRO scope (L0.5, L0.8). Desde 3A
|
|
19
|
+
* (2.2) el slug ya NO viaja en los ids de FGA (van por uuid de catálogo),
|
|
20
|
+
* pero los tipos y uuids de scope sí, y los slugs serán relaciones del
|
|
21
|
+
* modelo `facts` (3b): la lista blanca se mantiene entera.
|
|
22
|
+
* - Las longitudes son las de las columnas `authz_*`: lo que no cabe se
|
|
23
|
+
* truncaría o fallaría con un error de SQL ilegible.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Longitudes de columna del esquema publicado (`stubs/migration.stub`) que
|
|
27
|
+
* la identidad aplica tal cual. El slug NO está aquí: su límite es
|
|
28
|
+
* `MAX_SLUG_LENGTH` (42), más estricto que la columna (100) para que el
|
|
29
|
+
* catálogo sea publicable en FGA (E2).
|
|
30
|
+
*/
|
|
31
|
+
export declare const IDENTITY_LIMITS: Readonly<{
|
|
32
|
+
holderType: 50;
|
|
33
|
+
scopeType: 20;
|
|
34
|
+
uuid: 36;
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Centinela con el que el driver `database` almacena el uuid del scope `app`
|
|
38
|
+
* (NOT NULL + unique). Fuera de `app` sería una identidad de scope que
|
|
39
|
+
* colisiona con la raíz: se rechaza (L0.15).
|
|
40
|
+
*/
|
|
41
|
+
export declare const SENTINEL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
42
|
+
/** El uuid de un rol o permiso del catálogo (opcional en el spec): canónico y en minúsculas, o 422. */
|
|
43
|
+
export declare function assertCatalogUuid(kind: string, value: unknown): asserts value is string;
|
|
44
|
+
/** Versión booleana para caminos de LECTURA (ids que vienen del backend): no lanza. */
|
|
45
|
+
export declare function isCatalogUuid(value: unknown): value is string;
|
|
46
|
+
/** `SubjectRef` bien formado: type y uuid presentes, con formato y longitud. */
|
|
47
|
+
export declare function assertSubject(subject: SubjectRef): void;
|
|
48
|
+
/**
|
|
49
|
+
* `ScopeRef` bien formado. `app` es la raíz y NO admite uuid (L0.10): un
|
|
50
|
+
* `{ app, 'X' }` no es "otro app", es una identidad que un driver colapsaría
|
|
51
|
+
* a la global. Fuera de `app` el uuid es obligatorio y no puede ser el
|
|
52
|
+
* centinela con el que `database` representa la raíz (L0.15).
|
|
53
|
+
*/
|
|
54
|
+
export declare function assertScope(scope: ScopeRef): void;
|
|
55
|
+
/** Tipo de scope suelto (el de `listRoleScopes`). */
|
|
56
|
+
export declare function assertScopeType(scopeType: string): void;
|
|
57
|
+
/**
|
|
58
|
+
* Clave textual de un scope: `app` para la raíz, `<tipo>|<uuid>` para el
|
|
59
|
+
* resto (≤ 57 caracteres). Es la MISMA en todo el paquete: el id de los
|
|
60
|
+
* bindings de FGA (`role_binding:<scopeKey>|<roleUuid>`) y el owner de un
|
|
61
|
+
* rol local (`authz_roles.owner_scope_key`, 3B · B1). `|` es el separador:
|
|
62
|
+
* como ningún componente lo admite (`assertScope`), dos scopes distintos no
|
|
63
|
+
* pueden producir la misma clave, y la clave reservada `global` (el catálogo
|
|
64
|
+
* del config) no la produce ningún scope — la raíz da `app` y todo lo demás
|
|
65
|
+
* lleva `|`. Un scope mal formado (`{app, uuid}`, centinela, mayúsculas) es
|
|
66
|
+
* 422 aquí, antes de serializarse a nada.
|
|
67
|
+
*/
|
|
68
|
+
export declare function scopeKey(scope: ScopeRef): string;
|
|
69
|
+
/**
|
|
70
|
+
* La inversa de `scopeKey` para claves que vienen de la BASE (el owner de un
|
|
71
|
+
* rol): `app` ⇒ la raíz; `<tipo>|<uuid>` ⇒ el scope; cualquier otra forma
|
|
72
|
+
* (incluida `global`, que no es un scope) ⇒ `null`. No lanza: es camino de
|
|
73
|
+
* lectura, y una clave que no es un scope simplemente no resuelve.
|
|
74
|
+
*/
|
|
75
|
+
export declare function scopeFromKey(key: string): ScopeRef | null;
|
|
76
|
+
/**
|
|
77
|
+
* Nombres que el modelo FGA del modo `facts` usa como relaciones propias
|
|
78
|
+
* (`scope#parent`, `role_binding#assignee`, `deny_binding#denied`…). Un
|
|
79
|
+
* permiso llamado `parent` invalidaría el modelo entero (S14): se rechaza en
|
|
80
|
+
* el núcleo, para ambos drivers, no el día de la migración.
|
|
81
|
+
*/
|
|
82
|
+
export declare const RESERVED_SLUGS: ReadonlySet<string>;
|
|
83
|
+
/**
|
|
84
|
+
* Familias de relaciones derivadas del modelo (`can_<P>`, `denied_<P>`,
|
|
85
|
+
* `permits_<P>`). Un slug que EMPIECE por una de ellas colisiona con la
|
|
86
|
+
* relación derivada de otro permiso y el generador la colapsaría en silencio
|
|
87
|
+
* (S4: `can_docs_write` anulaba el deny de `docs:write`).
|
|
88
|
+
*/
|
|
89
|
+
export declare const RESERVED_SLUG_PREFIXES: readonly string[];
|
|
90
|
+
/**
|
|
91
|
+
* Longitud máxima de un slug. Un nombre de relación en FGA admite 50
|
|
92
|
+
* caracteres y el modelo deriva `permits_<slug>` (el prefijo más largo, 8):
|
|
93
|
+
* 42 es lo que cabe con cualquier prefijo. Es más estricto que la columna
|
|
94
|
+
* (100) a propósito: un catálogo legal en `database` tiene que ser publicable
|
|
95
|
+
* en `openfga` (S13), o el cambio de driver no es una migración de hechos.
|
|
96
|
+
*/
|
|
97
|
+
export declare const MAX_SLUG_LENGTH: number;
|
|
98
|
+
export type SlugKind = 'rol' | 'permiso';
|
|
99
|
+
/** Slug de rol o permiso válido para AMBOS drivers (formato, longitud, reservados). */
|
|
100
|
+
export declare function assertValidSlug(kind: SlugKind, slug: unknown): asserts slug is string;
|
|
101
|
+
/**
|
|
102
|
+
* Cómo se proyecta un slug a un nombre de relación FGA (`:` → `_`). Dos slugs
|
|
103
|
+
* distintos con la misma proyección (`docs:write` / `docs_write`) serían UNA
|
|
104
|
+
* relación: el catálogo los rechaza juntos (L0.8a).
|
|
105
|
+
*/
|
|
106
|
+
export declare function slugAsRelation(slug: string): string;
|
|
107
|
+
/** Ningún par de slugs del conjunto colisiona tras proyectarse a relación. */
|
|
108
|
+
export declare function assertNoSlugCollisions(kind: SlugKind, slugs: Iterable<string>): void;
|
|
109
|
+
/** Versiones booleanas para caminos de LECTURA que no deben lanzar (ids que vienen del backend). */
|
|
110
|
+
export declare function isValidScope(scope: ScopeRef): boolean;
|
|
111
|
+
export declare function isValidScopeType(scopeType: unknown): scopeType is string;
|
|
112
|
+
export declare function isValidSlug(kind: SlugKind, slug: string): boolean;
|
|
113
|
+
/**
|
|
114
|
+
* La pregunta de `hasRole`, validada: string ⇒ `{ slug }`; objeto ⇒
|
|
115
|
+
* `{ slug, scopeType }` (ambos con la gramática del motor).
|
|
116
|
+
*/
|
|
117
|
+
export declare function normalizeRoleQuery(role: RoleQuery): NormalizedRoleQuery;
|
|
118
|
+
/**
|
|
119
|
+
* Las claves de la cadena desde cada nivel hacia la raíz (3B · B2; movida
|
|
120
|
+
* aquí en 3D · N5 para que exista UNA sola codificación de clave de scope):
|
|
121
|
+
* `keysFrom[i]` = `chain.slice(i).map(scopeKey)`. Un rol es visible en el
|
|
122
|
+
* nivel `i` si es global o su owner está en `keysFrom[i]` — el owner tiene
|
|
123
|
+
* que ser ancestro-o-igual del scope de la ASIGNACIÓN, no de la pregunta.
|
|
124
|
+
*/
|
|
125
|
+
export declare function chainKeysFrom(chain: ScopeRef[]): string[][];
|
|
126
|
+
/**
|
|
127
|
+
* `expiresAt` de un `grant`, en sus tres estados legales: omitido, `null` o
|
|
128
|
+
* una `Date` válida. Una cadena, un número o un `Invalid Date` no son una
|
|
129
|
+
* caducidad: un driver lanzaba un TypeError al serializar y el otro persistía
|
|
130
|
+
* basura (D7). Se rechaza como identidad mal formada (422).
|
|
131
|
+
*/
|
|
132
|
+
export declare function assertExpiresAt(value: unknown): asserts value is Date | null | undefined;
|
|
133
|
+
export interface IdentityParts {
|
|
134
|
+
subject?: SubjectRef;
|
|
135
|
+
scope?: ScopeRef;
|
|
136
|
+
scopeType?: string;
|
|
137
|
+
/** Pregunta de `hasRole`: string u objeto `{ slug, scopeType }`. */
|
|
138
|
+
role?: RoleQuery;
|
|
139
|
+
/**
|
|
140
|
+
* Slug de rol donde el contrato pide un string (`grant`, `revoke`,
|
|
141
|
+
* `listSubjects`): un `RoleQuery` objeto aquí es 422, no un 503 ni un
|
|
142
|
+
* TypeError (D11).
|
|
143
|
+
*/
|
|
144
|
+
roleSlug?: unknown;
|
|
145
|
+
permission?: string;
|
|
146
|
+
/** Caducidad de un `grant`: `undefined` | `null` | `Date` válida. */
|
|
147
|
+
expiresAt?: unknown;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Valida en un solo paso los componentes que recibe una operación. Se llama
|
|
151
|
+
* ANTES de tocar catálogo, árbol o backend: una pregunta mal formada no
|
|
152
|
+
* cuesta una consulta ni deja rastro.
|
|
153
|
+
*/
|
|
154
|
+
export declare function assertIdentity(parts: IdentityParts): void;
|
|
155
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/identity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { InvalidIdentityError, InvalidSlugError } from './errors.js';
|
|
2
|
+
import { APP_SCOPE_TYPE } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Validación de identidad del motor: holder, scope, rol y permiso.
|
|
5
|
+
*
|
|
6
|
+
* Vive en un solo sitio y la aplican el manager (una vez por llamada) Y los
|
|
7
|
+
* drivers (defensa en profundidad: el juez del contrato habla con el driver
|
|
8
|
+
* directamente, y un driver de terceros puede usarse sin manager). Nadie
|
|
9
|
+
* duplica la regla: se importa.
|
|
10
|
+
*
|
|
11
|
+
* Por qué es 422 y no "se acepta lo que venga":
|
|
12
|
+
* - `uuid: undefined` en el driver openfga escribía `user:undefined`, y a
|
|
13
|
+
* partir de ahí CUALQUIER holder sin uuid heredaba ese permiso (L0.5).
|
|
14
|
+
* - `{ type: 'app', uuid: 'X' }` colapsaba a la raíz global en openfga y no
|
|
15
|
+
* en database: escalada de tenant a plataforma en un driver y `false` en
|
|
16
|
+
* el otro (L0.10).
|
|
17
|
+
* - `#`, `:`, `|`, `~`, `*` y espacios son sintaxis de FGA o separadores de
|
|
18
|
+
* los ids de binding: un componente que los contenga puede fabricar un
|
|
19
|
+
* userset, un comodín o la clave de OTRO scope (L0.5, L0.8). Desde 3A
|
|
20
|
+
* (2.2) el slug ya NO viaja en los ids de FGA (van por uuid de catálogo),
|
|
21
|
+
* pero los tipos y uuids de scope sí, y los slugs serán relaciones del
|
|
22
|
+
* modelo `facts` (3b): la lista blanca se mantiene entera.
|
|
23
|
+
* - Las longitudes son las de las columnas `authz_*`: lo que no cabe se
|
|
24
|
+
* truncaría o fallaría con un error de SQL ilegible.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Longitudes de columna del esquema publicado (`stubs/migration.stub`) que
|
|
28
|
+
* la identidad aplica tal cual. El slug NO está aquí: su límite es
|
|
29
|
+
* `MAX_SLUG_LENGTH` (42), más estricto que la columna (100) para que el
|
|
30
|
+
* catálogo sea publicable en FGA (E2).
|
|
31
|
+
*/
|
|
32
|
+
export const IDENTITY_LIMITS = Object.freeze({
|
|
33
|
+
holderType: 50,
|
|
34
|
+
scopeType: 20,
|
|
35
|
+
uuid: 36,
|
|
36
|
+
});
|
|
37
|
+
/**
|
|
38
|
+
* Centinela con el que el driver `database` almacena el uuid del scope `app`
|
|
39
|
+
* (NOT NULL + unique). Fuera de `app` sería una identidad de scope que
|
|
40
|
+
* colisiona con la raíz: se rechaza (L0.15).
|
|
41
|
+
*/
|
|
42
|
+
export const SENTINEL_UUID = '00000000-0000-0000-0000-000000000000';
|
|
43
|
+
/**
|
|
44
|
+
* UUID del CATÁLOGO (rol o permiso): la columna `uuid` de `authz_roles` y
|
|
45
|
+
* `authz_permissions` es `uuid` real, y desde 3A (2.2) es lo que viaja en
|
|
46
|
+
* los ids de binding de FGA (`role_binding:<scopeKey>|<roleUuid>`). Se exige
|
|
47
|
+
* canónico y en MINÚSCULAS (RFC 9562, 8-4-4-4-12 hex): PostgreSQL lo
|
|
48
|
+
* normaliza así y MySQL/SQLite lo guardan tal cual, así que un uuid en
|
|
49
|
+
* mayúsculas del spec produciría dos identidades para el mismo rol según el
|
|
50
|
+
* motor, y un id de binding que el propio driver no leería de vuelta. Es más
|
|
51
|
+
* estricto que la gramática de los uuids de holder/scope (esos son del
|
|
52
|
+
* consumidor, `[a-z0-9._-]{1,36}`): un id 1.x con slug (`app|editor`) no
|
|
53
|
+
* puede parecer un id 2.2 (`app|<uuid>`).
|
|
54
|
+
*/
|
|
55
|
+
const CATALOG_UUID_FORMAT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
56
|
+
/** El uuid de un rol o permiso del catálogo (opcional en el spec): canónico y en minúsculas, o 422. */
|
|
57
|
+
export function assertCatalogUuid(kind, value) {
|
|
58
|
+
if (typeof value !== 'string' || !CATALOG_UUID_FORMAT.test(value)) {
|
|
59
|
+
throw new InvalidIdentityError(`UUID del ${kind} inválido: se esperaba un UUID canónico en minúsculas (8-4-4-4-12 hex) y llegó ${describe(value)}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Versión booleana para caminos de LECTURA (ids que vienen del backend): no lanza. */
|
|
63
|
+
export function isCatalogUuid(value) {
|
|
64
|
+
return typeof value === 'string' && CATALOG_UUID_FORMAT.test(value);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Un componente de identidad: MINÚSCULAS, dígitos, `_`, `.` y `-`. Lista
|
|
68
|
+
* blanca y no negra: lo que no está aquí no se serializa a ningún backend.
|
|
69
|
+
* Excluye por construcción `#`, `:`, `|`, `~`, `*`, `@`, espacios y control.
|
|
70
|
+
*
|
|
71
|
+
* Los TIPOS (de holder y de scope) van en minúsculas desde E4 (auditor H14):
|
|
72
|
+
* en un motor SQL con collation `*_ci` `Users` y `users` serían la misma fila
|
|
73
|
+
* y en FGA dos holders distintos. Los UUIDs también desde 2.5-B · K1
|
|
74
|
+
* (auditor 🔴 1): el árbol del consumidor (tipo `uuid` de PG, `char(36)`
|
|
75
|
+
* `*_ci` de MySQL) fundía `BBBB…` con `bbbb…`, la cadena resolvía con el
|
|
76
|
+
* alias y el deny —escrito canónico en `authz_*`, `utf8mb4_bin`— no casaba.
|
|
77
|
+
* La cadena canónica del resolutor cierra el alias por guiones; el alias por
|
|
78
|
+
* mayúsculas muere aquí, en la puerta, antes de tocar nada. Un id en
|
|
79
|
+
* mayúsculas del consumidor se pasa en minúsculas (`uuid.toLowerCase()`) en
|
|
80
|
+
* su borde: un UUID es el mismo id en cualquier caja.
|
|
81
|
+
*/
|
|
82
|
+
const COMPONENT_FORMAT = /^[a-z0-9_.-]+$/;
|
|
83
|
+
const TYPE_FORMAT = COMPONENT_FORMAT;
|
|
84
|
+
function assertComponent(kind, value, maxLength, format = COMPONENT_FORMAT) {
|
|
85
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
86
|
+
throw new InvalidIdentityError(`${kind} inválido: se esperaba una cadena no vacía y llegó ${describe(value)}`);
|
|
87
|
+
}
|
|
88
|
+
if (value.length > maxLength) {
|
|
89
|
+
throw new InvalidIdentityError(`${kind} inválido: '${value}' supera los ${maxLength} caracteres de la columna`);
|
|
90
|
+
}
|
|
91
|
+
if (!format.test(value)) {
|
|
92
|
+
throw new InvalidIdentityError(`${kind} inválido: '${value}'. Solo se admiten minúsculas, dígitos y . _ - ` +
|
|
93
|
+
`(ni mayúsculas, '#', ':', '|', '~', '*', espacios ni caracteres de control)`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function describe(value) {
|
|
97
|
+
if (value === undefined)
|
|
98
|
+
return 'undefined';
|
|
99
|
+
if (value === null)
|
|
100
|
+
return 'null';
|
|
101
|
+
if (typeof value === 'string')
|
|
102
|
+
return `'${value}'`;
|
|
103
|
+
return typeof value;
|
|
104
|
+
}
|
|
105
|
+
/** `SubjectRef` bien formado: type y uuid presentes, con formato y longitud. */
|
|
106
|
+
export function assertSubject(subject) {
|
|
107
|
+
if (!subject || typeof subject !== 'object') {
|
|
108
|
+
throw new InvalidIdentityError(`Holder inválido: llegó ${describe(subject)}`);
|
|
109
|
+
}
|
|
110
|
+
assertComponent('Tipo de holder', subject.type, IDENTITY_LIMITS.holderType, TYPE_FORMAT);
|
|
111
|
+
assertComponent(`UUID del holder '${subject.type}'`, subject.uuid, IDENTITY_LIMITS.uuid);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* `ScopeRef` bien formado. `app` es la raíz y NO admite uuid (L0.10): un
|
|
115
|
+
* `{ app, 'X' }` no es "otro app", es una identidad que un driver colapsaría
|
|
116
|
+
* a la global. Fuera de `app` el uuid es obligatorio y no puede ser el
|
|
117
|
+
* centinela con el que `database` representa la raíz (L0.15).
|
|
118
|
+
*/
|
|
119
|
+
export function assertScope(scope) {
|
|
120
|
+
if (!scope || typeof scope !== 'object') {
|
|
121
|
+
throw new InvalidIdentityError(`Scope inválido: llegó ${describe(scope)}`);
|
|
122
|
+
}
|
|
123
|
+
assertComponent('Tipo de scope', scope.type, IDENTITY_LIMITS.scopeType, TYPE_FORMAT);
|
|
124
|
+
if (scope.type === APP_SCOPE_TYPE) {
|
|
125
|
+
if (scope.uuid !== null) {
|
|
126
|
+
throw new InvalidIdentityError(`Scope inválido: el scope '${APP_SCOPE_TYPE}' es la raíz y no admite uuid ` +
|
|
127
|
+
`(llegó ${describe(scope.uuid)}). Usa APP_SCOPE.`);
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
assertComponent(`UUID del scope '${scope.type}'`, scope.uuid, IDENTITY_LIMITS.uuid);
|
|
132
|
+
if (scope.uuid === SENTINEL_UUID) {
|
|
133
|
+
throw new InvalidIdentityError(`Scope inválido: '${SENTINEL_UUID}' está reservado para la raíz y no puede ` +
|
|
134
|
+
`identificar un scope de tipo '${scope.type}'`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** Tipo de scope suelto (el de `listRoleScopes`). */
|
|
138
|
+
export function assertScopeType(scopeType) {
|
|
139
|
+
assertComponent('Tipo de scope', scopeType, IDENTITY_LIMITS.scopeType, TYPE_FORMAT);
|
|
140
|
+
}
|
|
141
|
+
/* ── Clave de scope ─────────────────────────────────────────────────────── */
|
|
142
|
+
/**
|
|
143
|
+
* Clave textual de un scope: `app` para la raíz, `<tipo>|<uuid>` para el
|
|
144
|
+
* resto (≤ 57 caracteres). Es la MISMA en todo el paquete: el id de los
|
|
145
|
+
* bindings de FGA (`role_binding:<scopeKey>|<roleUuid>`) y el owner de un
|
|
146
|
+
* rol local (`authz_roles.owner_scope_key`, 3B · B1). `|` es el separador:
|
|
147
|
+
* como ningún componente lo admite (`assertScope`), dos scopes distintos no
|
|
148
|
+
* pueden producir la misma clave, y la clave reservada `global` (el catálogo
|
|
149
|
+
* del config) no la produce ningún scope — la raíz da `app` y todo lo demás
|
|
150
|
+
* lleva `|`. Un scope mal formado (`{app, uuid}`, centinela, mayúsculas) es
|
|
151
|
+
* 422 aquí, antes de serializarse a nada.
|
|
152
|
+
*/
|
|
153
|
+
export function scopeKey(scope) {
|
|
154
|
+
assertScope(scope);
|
|
155
|
+
if (scope.type === APP_SCOPE_TYPE)
|
|
156
|
+
return APP_SCOPE_TYPE;
|
|
157
|
+
return `${scope.type}|${scope.uuid}`;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* La inversa de `scopeKey` para claves que vienen de la BASE (el owner de un
|
|
161
|
+
* rol): `app` ⇒ la raíz; `<tipo>|<uuid>` ⇒ el scope; cualquier otra forma
|
|
162
|
+
* (incluida `global`, que no es un scope) ⇒ `null`. No lanza: es camino de
|
|
163
|
+
* lectura, y una clave que no es un scope simplemente no resuelve.
|
|
164
|
+
*/
|
|
165
|
+
export function scopeFromKey(key) {
|
|
166
|
+
if (key === APP_SCOPE_TYPE)
|
|
167
|
+
return { type: APP_SCOPE_TYPE, uuid: null };
|
|
168
|
+
const parts = key.split('|');
|
|
169
|
+
if (parts.length !== 2)
|
|
170
|
+
return null;
|
|
171
|
+
const scope = { type: parts[0], uuid: parts[1] };
|
|
172
|
+
return isValidScope(scope) ? scope : null;
|
|
173
|
+
}
|
|
174
|
+
/* ── Slugs del catálogo ─────────────────────────────────────────────────── */
|
|
175
|
+
/**
|
|
176
|
+
* Gramática de un slug: minúsculas, dígitos y . _ - ; los permisos admiten UN
|
|
177
|
+
* `:` (`recurso:accion`), los roles ninguno.
|
|
178
|
+
*/
|
|
179
|
+
const PERMISSION_FORMAT = /^[a-z0-9][a-z0-9_.-]*(:[a-z0-9][a-z0-9_.-]*)?$/;
|
|
180
|
+
const ROLE_FORMAT = /^[a-z0-9][a-z0-9_.-]*$/;
|
|
181
|
+
/**
|
|
182
|
+
* Nombres que el modelo FGA del modo `facts` usa como relaciones propias
|
|
183
|
+
* (`scope#parent`, `role_binding#assignee`, `deny_binding#denied`…). Un
|
|
184
|
+
* permiso llamado `parent` invalidaría el modelo entero (S14): se rechaza en
|
|
185
|
+
* el núcleo, para ambos drivers, no el día de la migración.
|
|
186
|
+
*/
|
|
187
|
+
export const RESERVED_SLUGS = new Set([
|
|
188
|
+
'parent',
|
|
189
|
+
'binding',
|
|
190
|
+
'ancestor',
|
|
191
|
+
'role',
|
|
192
|
+
'assignee',
|
|
193
|
+
'denied',
|
|
194
|
+
]);
|
|
195
|
+
/**
|
|
196
|
+
* Familias de relaciones derivadas del modelo (`can_<P>`, `denied_<P>`,
|
|
197
|
+
* `permits_<P>`). Un slug que EMPIECE por una de ellas colisiona con la
|
|
198
|
+
* relación derivada de otro permiso y el generador la colapsaría en silencio
|
|
199
|
+
* (S4: `can_docs_write` anulaba el deny de `docs:write`).
|
|
200
|
+
*/
|
|
201
|
+
export const RESERVED_SLUG_PREFIXES = Object.freeze(['can_', 'denied_', 'permits_']);
|
|
202
|
+
/**
|
|
203
|
+
* Longitud máxima de un slug. Un nombre de relación en FGA admite 50
|
|
204
|
+
* caracteres y el modelo deriva `permits_<slug>` (el prefijo más largo, 8):
|
|
205
|
+
* 42 es lo que cabe con cualquier prefijo. Es más estricto que la columna
|
|
206
|
+
* (100) a propósito: un catálogo legal en `database` tiene que ser publicable
|
|
207
|
+
* en `openfga` (S13), o el cambio de driver no es una migración de hechos.
|
|
208
|
+
*/
|
|
209
|
+
export const MAX_SLUG_LENGTH = 50 - Math.max(...RESERVED_SLUG_PREFIXES.map((p) => p.length));
|
|
210
|
+
/** Slug de rol o permiso válido para AMBOS drivers (formato, longitud, reservados). */
|
|
211
|
+
export function assertValidSlug(kind, slug) {
|
|
212
|
+
if (typeof slug !== 'string' || slug.length === 0) {
|
|
213
|
+
throw new InvalidSlugError(`Slug de ${kind} inválido: se esperaba una cadena no vacía y llegó ${describe(slug)}`);
|
|
214
|
+
}
|
|
215
|
+
if (slug.length > MAX_SLUG_LENGTH) {
|
|
216
|
+
throw new InvalidSlugError(`Slug de ${kind} inválido: '${slug}' supera los ${MAX_SLUG_LENGTH} caracteres ` +
|
|
217
|
+
`(50 de una relación FGA menos el prefijo derivado más largo)`);
|
|
218
|
+
}
|
|
219
|
+
const format = kind === 'permiso' ? PERMISSION_FORMAT : ROLE_FORMAT;
|
|
220
|
+
if (!format.test(slug)) {
|
|
221
|
+
throw new InvalidSlugError(`Slug de ${kind} inválido: '${slug}'. Formato: minúsculas/dígitos/._-` +
|
|
222
|
+
(kind === 'permiso' ? " con ':' único opcional (recurso:accion)" : ' (sin ":")') +
|
|
223
|
+
"; '~', '|', '#', '*' y espacios están prohibidos");
|
|
224
|
+
}
|
|
225
|
+
if (RESERVED_SLUGS.has(slug)) {
|
|
226
|
+
throw new InvalidSlugError(`Slug de ${kind} inválido: '${slug}' es un nombre reservado del modelo ` +
|
|
227
|
+
`(${[...RESERVED_SLUGS].join(', ')})`);
|
|
228
|
+
}
|
|
229
|
+
const family = RESERVED_SLUG_PREFIXES.find((prefix) => slug.startsWith(prefix));
|
|
230
|
+
if (family) {
|
|
231
|
+
throw new InvalidSlugError(`Slug de ${kind} inválido: '${slug}' empieza por '${family}', prefijo reservado ` +
|
|
232
|
+
`de las relaciones derivadas (${RESERVED_SLUG_PREFIXES.join(', ')})`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Cómo se proyecta un slug a un nombre de relación FGA (`:` → `_`). Dos slugs
|
|
237
|
+
* distintos con la misma proyección (`docs:write` / `docs_write`) serían UNA
|
|
238
|
+
* relación: el catálogo los rechaza juntos (L0.8a).
|
|
239
|
+
*/
|
|
240
|
+
export function slugAsRelation(slug) {
|
|
241
|
+
return slug.replaceAll(':', '_');
|
|
242
|
+
}
|
|
243
|
+
/** Ningún par de slugs del conjunto colisiona tras proyectarse a relación. */
|
|
244
|
+
export function assertNoSlugCollisions(kind, slugs) {
|
|
245
|
+
const seen = new Map();
|
|
246
|
+
for (const slug of slugs) {
|
|
247
|
+
const relation = slugAsRelation(slug);
|
|
248
|
+
const other = seen.get(relation);
|
|
249
|
+
if (other !== undefined && other !== slug) {
|
|
250
|
+
throw new InvalidSlugError(`Slugs de ${kind} en colisión: '${other}' y '${slug}' se proyectan a la misma ` +
|
|
251
|
+
`relación '${relation}' (':' y '_' son equivalentes en el modelo)`);
|
|
252
|
+
}
|
|
253
|
+
seen.set(relation, slug);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/** Versiones booleanas para caminos de LECTURA que no deben lanzar (ids que vienen del backend). */
|
|
257
|
+
export function isValidScope(scope) {
|
|
258
|
+
try {
|
|
259
|
+
assertScope(scope);
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
export function isValidScopeType(scopeType) {
|
|
267
|
+
try {
|
|
268
|
+
assertScopeType(scopeType);
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
export function isValidSlug(kind, slug) {
|
|
276
|
+
try {
|
|
277
|
+
assertValidSlug(kind, slug);
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* La pregunta de `hasRole`, validada: string ⇒ `{ slug }`; objeto ⇒
|
|
286
|
+
* `{ slug, scopeType }` (ambos con la gramática del motor).
|
|
287
|
+
*/
|
|
288
|
+
export function normalizeRoleQuery(role) {
|
|
289
|
+
if (typeof role === 'string') {
|
|
290
|
+
assertValidSlug('rol', role);
|
|
291
|
+
return { slug: role };
|
|
292
|
+
}
|
|
293
|
+
if (!role || typeof role !== 'object') {
|
|
294
|
+
throw new InvalidIdentityError(`Rol inválido: llegó ${describe(role)}`);
|
|
295
|
+
}
|
|
296
|
+
// 3D · M1(c): `{ uuid }` es la forma EXACTA (sin ambigüedad posible). No se
|
|
297
|
+
// mezcla con `{ slug, scopeType }`: la pregunta es por identidad o por
|
|
298
|
+
// nombre, nunca por las dos a la vez (un `{ uuid, slug }` que no casaran
|
|
299
|
+
// sería una pregunta con dos respuestas).
|
|
300
|
+
if ('uuid' in role) {
|
|
301
|
+
const query = role;
|
|
302
|
+
if (query.slug !== undefined || query.scopeType !== undefined) {
|
|
303
|
+
throw new InvalidIdentityError(`Rol inválido: { uuid } es la forma exacta y no se combina con 'slug'/'scopeType' (llegó ${describe(role)})`);
|
|
304
|
+
}
|
|
305
|
+
assertCatalogUuid('rol', query.uuid);
|
|
306
|
+
return { uuid: query.uuid };
|
|
307
|
+
}
|
|
308
|
+
assertValidSlug('rol', role.slug);
|
|
309
|
+
assertScopeType(role.scopeType);
|
|
310
|
+
return { slug: role.slug, scopeType: role.scopeType };
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Las claves de la cadena desde cada nivel hacia la raíz (3B · B2; movida
|
|
314
|
+
* aquí en 3D · N5 para que exista UNA sola codificación de clave de scope):
|
|
315
|
+
* `keysFrom[i]` = `chain.slice(i).map(scopeKey)`. Un rol es visible en el
|
|
316
|
+
* nivel `i` si es global o su owner está en `keysFrom[i]` — el owner tiene
|
|
317
|
+
* que ser ancestro-o-igual del scope de la ASIGNACIÓN, no de la pregunta.
|
|
318
|
+
*/
|
|
319
|
+
export function chainKeysFrom(chain) {
|
|
320
|
+
const keys = chain.map(scopeKey);
|
|
321
|
+
return keys.map((_, i) => keys.slice(i));
|
|
322
|
+
}
|
|
323
|
+
/* ── Punto de entrada único ─────────────────────────────────────────────── */
|
|
324
|
+
/**
|
|
325
|
+
* `expiresAt` de un `grant`, en sus tres estados legales: omitido, `null` o
|
|
326
|
+
* una `Date` válida. Una cadena, un número o un `Invalid Date` no son una
|
|
327
|
+
* caducidad: un driver lanzaba un TypeError al serializar y el otro persistía
|
|
328
|
+
* basura (D7). Se rechaza como identidad mal formada (422).
|
|
329
|
+
*/
|
|
330
|
+
export function assertExpiresAt(value) {
|
|
331
|
+
if (value === undefined || value === null)
|
|
332
|
+
return;
|
|
333
|
+
if (!(value instanceof Date) || Number.isNaN(value.getTime())) {
|
|
334
|
+
throw new InvalidIdentityError(`expiresAt inválido: se esperaba una Date válida, null (sin caducidad) u omitido ` +
|
|
335
|
+
`(preservar la vigente) y llegó ${value instanceof Date ? 'Invalid Date' : describe(value)}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Valida en un solo paso los componentes que recibe una operación. Se llama
|
|
340
|
+
* ANTES de tocar catálogo, árbol o backend: una pregunta mal formada no
|
|
341
|
+
* cuesta una consulta ni deja rastro.
|
|
342
|
+
*/
|
|
343
|
+
export function assertIdentity(parts) {
|
|
344
|
+
if ('subject' in parts)
|
|
345
|
+
assertSubject(parts.subject);
|
|
346
|
+
if ('scope' in parts)
|
|
347
|
+
assertScope(parts.scope);
|
|
348
|
+
if ('scopeType' in parts)
|
|
349
|
+
assertScopeType(parts.scopeType);
|
|
350
|
+
if ('role' in parts)
|
|
351
|
+
normalizeRoleQuery(parts.role);
|
|
352
|
+
if ('roleSlug' in parts)
|
|
353
|
+
assertValidSlug('rol', parts.roleSlug);
|
|
354
|
+
if ('permission' in parts)
|
|
355
|
+
assertValidSlug('permiso', parts.permission);
|
|
356
|
+
if ('expiresAt' in parts)
|
|
357
|
+
assertExpiresAt(parts.expiresAt);
|
|
358
|
+
}
|
|
359
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/identity.ts"],"names":[],"mappings":"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"}
|