@jantstack/adonis-authz 1.1.0 → 2.4.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 +837 -51
- 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_prune_orphans.d.ts +78 -0
- package/build/commands/authz_catalog_prune_orphans.d.ts.map +1 -0
- package/build/commands/authz_catalog_prune_orphans.js +136 -0
- package/build/commands/authz_catalog_prune_orphans.js.map +1 -0
- package/build/commands/authz_catalog_sync.d.ts +37 -0
- package/build/commands/authz_catalog_sync.d.ts.map +1 -0
- package/build/commands/authz_catalog_sync.js +81 -0
- package/build/commands/authz_catalog_sync.js.map +1 -0
- package/build/commands/authz_freeze.d.ts +44 -0
- package/build/commands/authz_freeze.d.ts.map +1 -0
- package/build/commands/authz_freeze.js +95 -0
- package/build/commands/authz_freeze.js.map +1 -0
- package/build/commands/authz_reconcile.d.ts +102 -0
- package/build/commands/authz_reconcile.d.ts.map +1 -0
- package/build/commands/authz_reconcile.js +294 -0
- package/build/commands/authz_reconcile.js.map +1 -0
- package/build/commands/authz_relations_reconcile.d.ts +73 -0
- package/build/commands/authz_relations_reconcile.d.ts.map +1 -0
- package/build/commands/authz_relations_reconcile.js +225 -0
- package/build/commands/authz_relations_reconcile.js.map +1 -0
- package/build/commands/authz_scopes_relay.d.ts +47 -0
- package/build/commands/authz_scopes_relay.d.ts.map +1 -0
- package/build/commands/authz_scopes_relay.js +141 -0
- package/build/commands/authz_scopes_relay.js.map +1 -0
- package/build/commands/authz_unfreeze.d.ts +37 -0
- package/build/commands/authz_unfreeze.d.ts.map +1 -0
- package/build/commands/authz_unfreeze.js +92 -0
- package/build/commands/authz_unfreeze.js.map +1 -0
- package/build/commands/main.d.ts +8 -1
- package/build/commands/main.d.ts.map +1 -1
- package/build/commands/main.js +8 -1
- package/build/commands/main.js.map +1 -1
- package/build/commands/openfga_provision.d.ts +46 -4
- package/build/commands/openfga_provision.d.ts.map +1 -1
- package/build/commands/openfga_provision.js +91 -7
- package/build/commands/openfga_provision.js.map +1 -1
- package/build/configure.d.ts +11 -0
- package/build/configure.d.ts.map +1 -1
- package/build/configure.js +37 -1
- package/build/configure.js.map +1 -1
- package/build/index.d.ts +75 -7
- package/build/index.d.ts.map +1 -1
- package/build/index.js +67 -5
- package/build/index.js.map +1 -1
- package/build/providers/authz_provider.d.ts +26 -2
- package/build/providers/authz_provider.d.ts.map +1 -1
- package/build/providers/authz_provider.js +48 -2
- package/build/providers/authz_provider.js.map +1 -1
- package/build/services/relations.d.ts +14 -0
- package/build/services/relations.d.ts.map +1 -0
- package/build/services/relations.js +17 -0
- package/build/services/relations.js.map +1 -0
- package/build/src/catalog/catalog.d.ts +289 -0
- package/build/src/catalog/catalog.d.ts.map +1 -0
- package/build/src/catalog/catalog.js +859 -0
- package/build/src/catalog/catalog.js.map +1 -0
- package/build/src/catalog/catalog_cache.d.ts +324 -0
- package/build/src/catalog/catalog_cache.d.ts.map +1 -0
- package/build/src/catalog/catalog_cache.js +666 -0
- package/build/src/catalog/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 +201 -7
- package/build/src/define_config.d.ts.map +1 -1
- package/build/src/define_config.js.map +1 -1
- package/build/src/drivers/database_driver.d.ts +324 -15
- package/build/src/drivers/database_driver.d.ts.map +1 -1
- package/build/src/drivers/database_driver.js +1107 -128
- package/build/src/drivers/database_driver.js.map +1 -1
- package/build/src/drivers/database_relations_driver.d.ts +75 -0
- package/build/src/drivers/database_relations_driver.d.ts.map +1 -0
- package/build/src/drivers/database_relations_driver.js +450 -0
- package/build/src/drivers/database_relations_driver.js.map +1 -0
- package/build/src/drivers/openfga_driver.d.ts +963 -55
- package/build/src/drivers/openfga_driver.d.ts.map +1 -1
- package/build/src/drivers/openfga_driver.js +2693 -372
- package/build/src/drivers/openfga_driver.js.map +1 -1
- package/build/src/drivers/openfga_facts.d.ts +369 -0
- package/build/src/drivers/openfga_facts.d.ts.map +1 -0
- package/build/src/drivers/openfga_facts.js +813 -0
- package/build/src/drivers/openfga_facts.js.map +1 -0
- package/build/src/drivers/openfga_relations_driver.d.ts +120 -0
- package/build/src/drivers/openfga_relations_driver.d.ts.map +1 -0
- package/build/src/drivers/openfga_relations_driver.js +466 -0
- package/build/src/drivers/openfga_relations_driver.js.map +1 -0
- package/build/src/errors.d.ts +586 -0
- package/build/src/errors.d.ts.map +1 -1
- package/build/src/errors.js +583 -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/freeze.d.ts +120 -0
- package/build/src/freeze.d.ts.map +1 -0
- package/build/src/freeze.js +172 -0
- package/build/src/freeze.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/{middleware → http}/app_access_middleware.d.ts +8 -6
- package/build/src/http/app_access_middleware.d.ts.map +1 -0
- package/build/src/{middleware → http}/app_access_middleware.js +10 -26
- package/build/src/http/app_access_middleware.js.map +1 -0
- package/build/src/http/resource_access_middleware.d.ts +105 -0
- package/build/src/http/resource_access_middleware.d.ts.map +1 -0
- package/build/src/http/resource_access_middleware.js +81 -0
- package/build/src/http/resource_access_middleware.js.map +1 -0
- package/build/src/identity.d.ts +228 -0
- package/build/src/identity.d.ts.map +1 -0
- package/build/src/identity.js +457 -0
- package/build/src/identity.js.map +1 -0
- package/build/src/manager.d.ts +536 -7
- package/build/src/manager.d.ts.map +1 -1
- package/build/src/manager.js +2676 -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/models/authz_assignment.d.ts +11 -11
- package/build/src/models/authz_assignment.d.ts.map +1 -1
- package/build/src/models/authz_deny.d.ts +11 -11
- package/build/src/models/authz_deny.d.ts.map +1 -1
- package/build/src/models/authz_permission.d.ts +17 -11
- 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 +19 -12
- 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 +11 -11
- package/build/src/models/authz_role_permission.d.ts.map +1 -1
- package/build/src/openfga.d.ts +29 -0
- package/build/src/openfga.d.ts.map +1 -0
- package/build/src/openfga.js +26 -0
- package/build/src/openfga.js.map +1 -0
- package/build/src/reconcile.d.ts +37 -0
- package/build/src/reconcile.d.ts.map +1 -0
- package/build/src/reconcile.js +69 -0
- package/build/src/reconcile.js.map +1 -0
- package/build/src/relation_partition_trigger.d.ts +8 -0
- package/build/src/relation_partition_trigger.d.ts.map +1 -0
- package/build/src/relation_partition_trigger.js +85 -0
- package/build/src/relation_partition_trigger.js.map +1 -0
- package/build/src/relations/define_relations_config.d.ts +58 -0
- package/build/src/relations/define_relations_config.d.ts.map +1 -0
- package/build/src/relations/define_relations_config.js +144 -0
- package/build/src/relations/define_relations_config.js.map +1 -0
- package/build/src/relations/manager.d.ts +38 -0
- package/build/src/relations/manager.d.ts.map +1 -0
- package/build/src/relations/manager.js +156 -0
- package/build/src/relations/manager.js.map +1 -0
- package/build/src/relations/reconcile.d.ts +62 -0
- package/build/src/relations/reconcile.d.ts.map +1 -0
- package/build/src/relations/reconcile.js +138 -0
- package/build/src/relations/reconcile.js.map +1 -0
- package/build/src/relations_config_store.d.ts +22 -0
- package/build/src/relations_config_store.d.ts.map +1 -0
- package/build/src/relations_config_store.js +74 -0
- package/build/src/relations_config_store.js.map +1 -0
- package/build/src/scope_outbox.d.ts +69 -0
- package/build/src/scope_outbox.d.ts.map +1 -0
- package/build/src/scope_outbox.js +291 -0
- package/build/src/scope_outbox.js.map +1 -0
- package/build/src/shared/backend_guard.d.ts +106 -0
- package/build/src/shared/backend_guard.d.ts.map +1 -0
- package/build/src/shared/backend_guard.js +246 -0
- package/build/src/shared/backend_guard.js.map +1 -0
- package/build/src/shared/sql_expiry.d.ts +53 -0
- package/build/src/shared/sql_expiry.d.ts.map +1 -0
- package/build/src/shared/sql_expiry.js +66 -0
- package/build/src/shared/sql_expiry.js.map +1 -0
- package/build/src/sql_descendants.d.ts +97 -0
- package/build/src/sql_descendants.d.ts.map +1 -0
- package/build/src/sql_descendants.js +203 -0
- package/build/src/sql_descendants.js.map +1 -0
- package/build/src/testing/contract.d.ts +212 -7
- package/build/src/testing/contract.d.ts.map +1 -1
- package/build/src/testing/contract.js +3449 -24
- package/build/src/testing/contract.js.map +1 -1
- package/build/src/testing/main.d.ts +10 -2
- package/build/src/testing/main.d.ts.map +1 -1
- package/build/src/testing/main.js +5 -1
- package/build/src/testing/main.js.map +1 -1
- package/build/src/testing/migration_contract.d.ts +284 -0
- package/build/src/testing/migration_contract.d.ts.map +1 -0
- package/build/src/testing/migration_contract.js +586 -0
- package/build/src/testing/migration_contract.js.map +1 -0
- package/build/src/testing/relations_contract.d.ts +51 -0
- package/build/src/testing/relations_contract.d.ts.map +1 -0
- package/build/src/testing/relations_contract.js +654 -0
- package/build/src/testing/relations_contract.js.map +1 -0
- package/build/src/testing/relations_reconcile_contract.d.ts +24 -0
- package/build/src/testing/relations_reconcile_contract.d.ts.map +1 -0
- package/build/src/testing/relations_reconcile_contract.js +172 -0
- package/build/src/testing/relations_reconcile_contract.js.map +1 -0
- 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 +12 -12
- package/build/src/traits/has_uuid.d.ts.map +1 -1
- package/build/src/types.d.ts +1313 -28
- package/build/src/types.d.ts.map +1 -1
- package/build/src/types.js +20 -4
- package/build/src/types.js.map +1 -1
- package/build/stubs/config/app_acl.stub +4 -2
- package/build/stubs/config/authorization.stub +168 -14
- package/build/stubs/migration.stub +183 -13
- package/build/stubs/scopes_outbox_migration.stub +57 -0
- package/package.json +14 -7
- package/build/commands/openfga_import.d.ts +0 -28
- package/build/commands/openfga_import.d.ts.map +0 -1
- package/build/commands/openfga_import.js +0 -74
- package/build/commands/openfga_import.js.map +0 -1
- package/build/src/catalog.d.ts +0 -3
- package/build/src/catalog.d.ts.map +0 -1
- package/build/src/catalog.js +0 -103
- package/build/src/catalog.js.map +0 -1
- package/build/src/middleware/app_access_middleware.d.ts.map +0 -1
- package/build/src/middleware/app_access_middleware.js.map +0 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
import type { NextFn } from '@adonisjs/core/types/http';
|
|
3
|
+
import type { ScopeRef } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Enforcement de acceso A UN RECURSO concreto. COMPONE el `authorize` que ya
|
|
6
|
+
* existe: el consumidor dice CÓMO cargar el recurso (`load(ctx, id)`) y en qué
|
|
7
|
+
* `{ scope }` vive, y el middleware pregunta al motor sobre ese scope. No es un
|
|
8
|
+
* modelo ni un driver nuevo: es el borde HTTP del scope de un recurso.
|
|
9
|
+
*
|
|
10
|
+
* El ORDEN de las respuestas ES la propiedad de seguridad — un 403 donde
|
|
11
|
+
* debería ir un 404 filtra qué recursos existen (enumeración):
|
|
12
|
+
*
|
|
13
|
+
* 1. 401 si no hay usuario autenticado (antes de nada).
|
|
14
|
+
* 2. 403 si el `gate(ctx)` del consumidor (una ability previa, p. ej.
|
|
15
|
+
* «es admin del tenant») lo niega.
|
|
16
|
+
* 3. 404 si el CONTENEDOR no existe (`containerParam`, cuando se declara).
|
|
17
|
+
* 4. 404 si el RECURSO no existe (`load` ⇒ null) — MISMO cuerpo que el 404
|
|
18
|
+
* del contenedor, para no distinguir «no existe» de «no es tuyo».
|
|
19
|
+
* 5. authorize sobre el `{ scope }` que devolvió `load`, con `permission`
|
|
20
|
+
* (o `readPermission` en métodos seguros): un `false` aquí también
|
|
21
|
+
* es 404 con el MISMO cuerpo, NO un 403 — que no puedas verlo no
|
|
22
|
+
* revela que existe.
|
|
23
|
+
*
|
|
24
|
+
* Invariantes que NO se negocian:
|
|
25
|
+
* - `AuthorizationBackendError` (503) NUNCA se disfraza de 404/403: si el
|
|
26
|
+
* backend cae (`gate`/`load`/`authorize` lanzan), el error sube tal cual.
|
|
27
|
+
* Es lo que distingue «denegado» de «no pude comprobar». El middleware NO
|
|
28
|
+
* envuelve estas llamadas en try/catch a propósito.
|
|
29
|
+
* - Prohibido `role`: como `appAccess`, solo acepta `permission`/
|
|
30
|
+
* `readPermission`. `{ role }` es 500 con receta (membresía no es acceso, y
|
|
31
|
+
* el deny no la gobierna: toda decisión pasa por `authorize`).
|
|
32
|
+
* - Sin segundo `authorize`: se llama UNA sola vez. No re-autoriza.
|
|
33
|
+
*
|
|
34
|
+
* Limitación conocida — canal de TEMPORIZACIÓN (auditor Fase 5, ⚪): el status
|
|
35
|
+
* y el cuerpo de «no existe» y «existe pero no es tuyo» son idénticos, pero el
|
|
36
|
+
* TIEMPO no: un id inexistente responde 404 sin round-trip a `authorize`; uno
|
|
37
|
+
* ajeno responde el mismo 404 tras esa llamada. Es inherente a load→authorize
|
|
38
|
+
* (no se puede autorizar el scope de algo que no se ha cargado), no un defecto;
|
|
39
|
+
* quien necesite cerrar el canal iguala el tiempo en su capa (delay constante),
|
|
40
|
+
* no en el middleware.
|
|
41
|
+
*
|
|
42
|
+
* Pureza: el middleware NO importa aliases del consumidor; `load`/`gate` llegan
|
|
43
|
+
* INYECTADOS en las opciones de la ruta.
|
|
44
|
+
*
|
|
45
|
+
* Uso:
|
|
46
|
+
* router
|
|
47
|
+
* .get('/orgs/:orgId/documents/:id', [DocumentsController, 'show'])
|
|
48
|
+
* .use(middleware.resourceAccess({
|
|
49
|
+
* resource: 'document',
|
|
50
|
+
* param: 'id',
|
|
51
|
+
* containerParam: 'orgId',
|
|
52
|
+
* permission: 'documents:write',
|
|
53
|
+
* readPermission: 'documents:read',
|
|
54
|
+
* load: (ctx, id) => Document.query().where('id', id).first(),
|
|
55
|
+
* gate: (ctx) => ctx.auth.user!.isMemberOf(ctx.params.orgId),
|
|
56
|
+
* }))
|
|
57
|
+
*/
|
|
58
|
+
export interface ResourceAccessOptions {
|
|
59
|
+
/**
|
|
60
|
+
* Nombre del recurso. Es la clave bajo la que el recurso cargado (con su
|
|
61
|
+
* scope) se PROPAGA al `ctx` para el controlador (`ctx[resource]`), así que
|
|
62
|
+
* dos `resourceAccess` anidados no se pisan.
|
|
63
|
+
*/
|
|
64
|
+
resource: string;
|
|
65
|
+
/** Nombre del param de ruta que trae el id del recurso (`ctx.params[param]`). */
|
|
66
|
+
param: string;
|
|
67
|
+
/**
|
|
68
|
+
* Nombre del param de ruta del contenedor (p. ej. el tenant de una ruta
|
|
69
|
+
* anidada). Cuando se declara, un contenedor ausente en la ruta es 404 —el
|
|
70
|
+
* MISMO cuerpo que un recurso ausente— y se comprueba ANTES de cargar el
|
|
71
|
+
* recurso. La pertenencia real del recurso al contenedor la comprueba
|
|
72
|
+
* `load` (que ve `ctx.params`): devolver `null` allí es el mismo 404, así que
|
|
73
|
+
* «no existe» y «no está en este contenedor» son indistinguibles.
|
|
74
|
+
*/
|
|
75
|
+
containerParam?: string;
|
|
76
|
+
/** Permiso requerido (métodos que mutan, y lectura si no hay `readPermission`). */
|
|
77
|
+
permission: string;
|
|
78
|
+
/**
|
|
79
|
+
* Permiso requerido en métodos SEGUROS (GET/HEAD). Si se omite, una lectura
|
|
80
|
+
* exige `permission` — nunca es gratis. Suele ser el permiso MENOS
|
|
81
|
+
* privilegiado (ver vs escribir).
|
|
82
|
+
*/
|
|
83
|
+
readPermission?: string;
|
|
84
|
+
/**
|
|
85
|
+
* Carga el recurso por su id y devuelve dónde vive (`{ scope }`) o `null` si
|
|
86
|
+
* no existe / no es visible. Del consumidor: ve el `ctx` entero (params,
|
|
87
|
+
* usuario). Un throw es «no pude comprobar» (sube como 503), NO un 404.
|
|
88
|
+
*/
|
|
89
|
+
load: (ctx: HttpContext, id: string) => Promise<{
|
|
90
|
+
scope: ScopeRef;
|
|
91
|
+
} | null> | {
|
|
92
|
+
scope: ScopeRef;
|
|
93
|
+
} | null;
|
|
94
|
+
/**
|
|
95
|
+
* Ability previa del consumidor (p. ej. «es admin del tenant»). Opcional; si
|
|
96
|
+
* se declara y devuelve `false`, es 403 —una restricción ADICIONAL, evaluada
|
|
97
|
+
* antes de tocar el recurso—. Si se omite, no hay pre-check: la seguridad la
|
|
98
|
+
* da igualmente `authorize`.
|
|
99
|
+
*/
|
|
100
|
+
gate?: (ctx: HttpContext) => Promise<boolean> | boolean;
|
|
101
|
+
}
|
|
102
|
+
export default class ResourceAccessMiddleware {
|
|
103
|
+
handle(ctx: HttpContext, next: NextFn, options: ResourceAccessOptions): Promise<any>;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=resource_access_middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource_access_middleware.d.ts","sourceRoot":"","sources":["../../../src/http/resource_access_middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAGvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAG3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,mFAAmF;IACnF,UAAU,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,IAAI,EAAE,CACJ,GAAG,EAAE,WAAW,EAChB,EAAE,EAAE,MAAM,KACP,OAAO,CAAC;QAAE,KAAK,EAAE,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAC,GAAG;QAAE,KAAK,EAAE,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAA;IACrE;;;;;OAKG;IACH,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;CACxD;AAKD,MAAM,CAAC,OAAO,OAAO,wBAAwB;IACrC,MAAM,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB;CA6F5E"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Exception } from '@adonisjs/core/exceptions';
|
|
2
|
+
import authorization from '../../services/main.js';
|
|
3
|
+
import { AuthorizationConfigError, RoleIsNotAccessError } from '../errors.js';
|
|
4
|
+
/** Métodos HTTP seguros: usan `readPermission` si se declara. */
|
|
5
|
+
const SAFE_METHODS = new Set(['GET', 'HEAD']);
|
|
6
|
+
export default class ResourceAccessMiddleware {
|
|
7
|
+
async handle(ctx, next, options) {
|
|
8
|
+
// Config PRIMERO, antes de mirar si hay usuario: una ruta mal declarada
|
|
9
|
+
// tiene que saltar en el primer request de desarrollo, autenticado o no.
|
|
10
|
+
if (options && 'role' in options) {
|
|
11
|
+
throw new RoleIsNotAccessError(options.role);
|
|
12
|
+
}
|
|
13
|
+
if (!options?.permission) {
|
|
14
|
+
throw new AuthorizationConfigError("middleware.resourceAccess requiere `{ permission }` (p. ej. resourceAccess({ permission: 'documents:write', ... }))");
|
|
15
|
+
}
|
|
16
|
+
if (!options.param) {
|
|
17
|
+
throw new AuthorizationConfigError('middleware.resourceAccess requiere `{ param }` — el nombre del param de ruta con el id del recurso');
|
|
18
|
+
}
|
|
19
|
+
if (typeof options.load !== 'function') {
|
|
20
|
+
throw new AuthorizationConfigError('middleware.resourceAccess requiere `load(ctx, id)` — cómo cargar el recurso y en qué `{ scope }` vive');
|
|
21
|
+
}
|
|
22
|
+
if (!options.resource) {
|
|
23
|
+
throw new AuthorizationConfigError('middleware.resourceAccess requiere `{ resource }` — el nombre bajo el que se propaga el recurso al controlador');
|
|
24
|
+
}
|
|
25
|
+
// ── 1. 401 ────────────────────────────────────────────────────────────
|
|
26
|
+
const holder = ctx.auth?.user;
|
|
27
|
+
if (!holder) {
|
|
28
|
+
return ctx.response.unauthorized({ message: 'No autenticado', statusCode: 401, errors: [] });
|
|
29
|
+
}
|
|
30
|
+
// El holder tiene que ser un subject válido (mismos guards que appAccess).
|
|
31
|
+
// Son errores de programación (500), no de acceso: nunca degradan a 404.
|
|
32
|
+
const morph = holder.__morphMapName;
|
|
33
|
+
if (!morph) {
|
|
34
|
+
throw new Exception('El modelo autenticado no tiene @MorphMap — no es un holder válido', {
|
|
35
|
+
status: 500,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (!holder.uuid) {
|
|
39
|
+
throw new Exception(`El holder '${morph}' no expone 'uuid' — el motor identifica a los holders por uuid, ` +
|
|
40
|
+
`no por la clave primaria del modelo.`, { status: 500 });
|
|
41
|
+
}
|
|
42
|
+
const subject = { type: morph, uuid: holder.uuid };
|
|
43
|
+
// ── 2. 403 gate del consumidor ────────────────────────────────────────
|
|
44
|
+
// Sin try/catch: si el gate lanza (backend caído), el 503 sube tal cual.
|
|
45
|
+
if (options.gate && !(await options.gate(ctx))) {
|
|
46
|
+
return ctx.response.forbidden({ message: 'Permiso insuficiente', statusCode: 403, errors: [] });
|
|
47
|
+
}
|
|
48
|
+
// El 404 del contenedor, del recurso ausente y del authorize negativo
|
|
49
|
+
// comparten EXACTAMENTE el mismo cuerpo: nada distingue los tres.
|
|
50
|
+
const notFound = () => ctx.response.notFound({ message: 'Recurso no encontrado', statusCode: 404, errors: [] });
|
|
51
|
+
// ── 3. 404 contenedor (ANTES que el recurso) ──────────────────────────
|
|
52
|
+
if (options.containerParam) {
|
|
53
|
+
const containerId = ctx.params?.[options.containerParam];
|
|
54
|
+
if (!containerId)
|
|
55
|
+
return notFound();
|
|
56
|
+
}
|
|
57
|
+
// ── 4. 404 recurso ────────────────────────────────────────────────────
|
|
58
|
+
const id = ctx.params?.[options.param];
|
|
59
|
+
if (!id)
|
|
60
|
+
return notFound();
|
|
61
|
+
// Un throw de `load` NO se atrapa: «no pude comprobar» (503) ≠ «no existe».
|
|
62
|
+
const loaded = await options.load(ctx, String(id));
|
|
63
|
+
if (!loaded)
|
|
64
|
+
return notFound();
|
|
65
|
+
// ── 5. authorize (UNA vez) sobre el scope del recurso ─────────────────
|
|
66
|
+
const method = String(ctx.request?.method?.() ?? 'GET').toUpperCase();
|
|
67
|
+
const permission = SAFE_METHODS.has(method) && options.readPermission
|
|
68
|
+
? options.readPermission
|
|
69
|
+
: options.permission;
|
|
70
|
+
if (!(await authorization.authorize(subject, permission, loaded.scope))) {
|
|
71
|
+
// Negado ⇒ 404 con el MISMO cuerpo, NO 403: que no puedas verlo no
|
|
72
|
+
// revela que existe.
|
|
73
|
+
return notFound();
|
|
74
|
+
}
|
|
75
|
+
// El recurso cargado (con su scope) llega al controlador sin recargarlo.
|
|
76
|
+
;
|
|
77
|
+
ctx[options.resource] = loaded;
|
|
78
|
+
return next();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=resource_access_middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource_access_middleware.js","sourceRoot":"","sources":["../../../src/http/resource_access_middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,228 @@
|
|
|
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
|
+
* Longitud máxima del id de un objeto/holder de RELACIÓN (columna
|
|
59
|
+
* `varchar(64)` de `authz_relations`). Más ancha que `IDENTITY_LIMITS.uuid`
|
|
60
|
+
* (36): el `object.id` de ReBAC lo elige el consumidor y no tiene por qué ser
|
|
61
|
+
* un uuid.
|
|
62
|
+
*/
|
|
63
|
+
export declare const RELATION_ID_LIMIT = 64;
|
|
64
|
+
/**
|
|
65
|
+
* **La gramática del id de un objeto de RELACIÓN** (R-16, Fase 4 · lote 4-7).
|
|
66
|
+
*
|
|
67
|
+
* Un `object.id` de relaciones va en la columna `varchar(64)` de
|
|
68
|
+
* `authz_relations` y, en el store compartido, en el id de objeto de FGA
|
|
69
|
+
* (`document:<partitionKey>|<object.id>`). El driver `database` ya lo validaba
|
|
70
|
+
* (`RELATION_ID_FORMAT`), pero ni el manager ni el driver `openfga` lo hacían:
|
|
71
|
+
* el `openfga` solo medía la LONGITUD. Un `object.id` con un `|` dentro
|
|
72
|
+
* (`'a|b'`) se ESCRIBÍA y `check` lo honraba, pero al parsear de vuelta (corte
|
|
73
|
+
* por el ÚLTIMO `|`) la partición salía mal, así que `listObjects` lo filtraba
|
|
74
|
+
* y `enumerateRelations` lo descartaba: una relación que concede y que NO se ve
|
|
75
|
+
* en la enumeración ni migra —pérdida SILENCIOSA en `reconcile`, justo lo que
|
|
76
|
+
* el driver promete que no pasa— y una divergencia con `database`, que lo
|
|
77
|
+
* rechaza con 422. Y los caracteres estructurales (`:`/`#`/espacio) salían como
|
|
78
|
+
* 503 mal clasificado, cuando por invariante 5 una entrada malformada es 422.
|
|
79
|
+
*
|
|
80
|
+
* Misma lista blanca que el resto de la identidad (`COMPONENT_FORMAT`):
|
|
81
|
+
* minúsculas, dígitos y `._-`; excluye `|`, `#`, `:`, `@`, `*`, `~`, espacios y
|
|
82
|
+
* control. La aplican el manager (una vez por llamada) Y cada driver (defensa
|
|
83
|
+
* en profundidad), como el resto de la gramática.
|
|
84
|
+
*/
|
|
85
|
+
export declare function assertRelationId(kind: string, value: unknown): asserts value is string;
|
|
86
|
+
/** Versión booleana para caminos de LECTURA (ids que vienen del backend): no lanza. */
|
|
87
|
+
export declare function isValidRelationId(value: unknown): value is string;
|
|
88
|
+
/**
|
|
89
|
+
* Clave textual de un scope: `app` para la raíz, `<tipo>|<uuid>` para el
|
|
90
|
+
* resto (≤ 57 caracteres). Es la MISMA en todo el paquete: el id de los
|
|
91
|
+
* bindings de FGA (`role_binding:<scopeKey>|<roleUuid>`) y el owner de un
|
|
92
|
+
* rol local (`authz_roles.owner_scope_key`, 3B · B1). `|` es el separador:
|
|
93
|
+
* como ningún componente lo admite (`assertScope`), dos scopes distintos no
|
|
94
|
+
* pueden producir la misma clave, y la clave reservada `global` (el catálogo
|
|
95
|
+
* del config) no la produce ningún scope — la raíz da `app` y todo lo demás
|
|
96
|
+
* lleva `|`. Un scope mal formado (`{app, uuid}`, centinela, mayúsculas) es
|
|
97
|
+
* 422 aquí, antes de serializarse a nada.
|
|
98
|
+
*/
|
|
99
|
+
export declare function scopeKey(scope: ScopeRef): string;
|
|
100
|
+
/**
|
|
101
|
+
* **Las ORTOGRAFÍAS de un scope con las que hay que limpiar cuando ya no hay
|
|
102
|
+
* cadena con la que canonizar** (3b-2h · 🟠 3, auditor R2).
|
|
103
|
+
*
|
|
104
|
+
* La identidad de un scope es la canónica que devuelve el resolutor
|
|
105
|
+
* (invariante 17) y el paquete la usa para TODOS los hechos… mientras la fila
|
|
106
|
+
* del consumidor existe. `scopes.detached` es justo la operación que admite
|
|
107
|
+
* que ya NO exista (3F · S1: se purga igual, con el scope tal cual), y ahí no
|
|
108
|
+
* hay con qué canonizar: se usa la ortografía del llamante. Medido contra
|
|
109
|
+
* PostgreSQL: el tipo `uuid` funde `01a0…-…` con el mismo uuid SIN guiones,
|
|
110
|
+
* así que el `DELETE` del controlador acierta con el alias, el `detached`
|
|
111
|
+
* llega con el alias, `purgeScope` demuestra cero sobre un objeto que no
|
|
112
|
+
* existe y el scope REAL conserva su `parent` y su `binding`: en modo
|
|
113
|
+
* `facts`, **concede para siempre**. En lectura el mismo alias es
|
|
114
|
+
* fail-closed; en escritura era fail-OPEN.
|
|
115
|
+
*
|
|
116
|
+
* La salida es no elegir: una purga sin cadena limpia la ortografía del
|
|
117
|
+
* llamante **y** la canónica de la que puede ser alias. Solo se expande el
|
|
118
|
+
* alias SIN guiones (32 hex ⇒ también la forma 8-4-4-4-12), que es la única
|
|
119
|
+
* que un motor produce fundiendo —PostgreSQL canoniza CON guiones; el alias
|
|
120
|
+
* por mayúsculas ya muere en la puerta (`assertScope`)—; así el consumidor
|
|
121
|
+
* que guarda sus uuids con guiones (el caso normal) no paga NADA, y el que
|
|
122
|
+
* los guarda sin guiones sigue purgando su clave y, además, una que nunca
|
|
123
|
+
* tiene hechos. Normalizar en vez de expandir no vale: sin la fila no se sabe
|
|
124
|
+
* cuál de las dos es la buena, y elegir mal deja los hechos vivos, que es el
|
|
125
|
+
* fallo que se está cerrando.
|
|
126
|
+
*/
|
|
127
|
+
export declare function scopeSpellings(scope: ScopeRef): ScopeRef[];
|
|
128
|
+
/**
|
|
129
|
+
* La inversa de `scopeKey` para claves que vienen de la BASE (el owner de un
|
|
130
|
+
* rol): `app` ⇒ la raíz; `<tipo>|<uuid>` ⇒ el scope; cualquier otra forma
|
|
131
|
+
* (incluida `global`, que no es un scope) ⇒ `null`. No lanza: es camino de
|
|
132
|
+
* lectura, y una clave que no es un scope simplemente no resuelve.
|
|
133
|
+
*/
|
|
134
|
+
export declare function scopeFromKey(key: string): ScopeRef | null;
|
|
135
|
+
/**
|
|
136
|
+
* Nombres que el modelo FGA del modo `facts` usa como relaciones propias
|
|
137
|
+
* (`scope#parent`, `role_binding#assignee`, `scope#denied_<P>`…). Un
|
|
138
|
+
* permiso llamado `parent` invalidaría el modelo entero (S14): se rechaza en
|
|
139
|
+
* el núcleo, para ambos drivers, no el día de la migración.
|
|
140
|
+
*/
|
|
141
|
+
export declare const RESERVED_SLUGS: ReadonlySet<string>;
|
|
142
|
+
/**
|
|
143
|
+
* Familias de relaciones derivadas del modelo (`can_<P>`, `denied_<P>`,
|
|
144
|
+
* `permits_<P>`). Un slug que EMPIECE por una de ellas colisiona con la
|
|
145
|
+
* relación derivada de otro permiso y el generador la colapsaría en silencio
|
|
146
|
+
* (S4: `can_docs_write` anulaba el deny de `docs:write`).
|
|
147
|
+
*/
|
|
148
|
+
export declare const RESERVED_SLUG_PREFIXES: readonly string[];
|
|
149
|
+
/**
|
|
150
|
+
* **Los TIPOS de objeto que el modelo `facts` (y el `group` de relaciones) YA
|
|
151
|
+
* declaran** (Fase 4 · ⚪4). Un tipo de objeto de relaciones que se llame como
|
|
152
|
+
* uno de estos DUPLICARÍA un tipo del store compartido: `role_binding` sobre
|
|
153
|
+
* todo —si el driver de relaciones pudiera declararlo, podría componer el id
|
|
154
|
+
* de un binding real y `relate` sería una escalada a `roles.authorize`—. Vive
|
|
155
|
+
* en `identity.ts` (la gramática compartida) porque lo consumen DOS módulos
|
|
156
|
+
* que la frontera de pureza mantiene disjuntos: el generador del modelo
|
|
157
|
+
* (`src/drivers/openfga_facts.ts`, que no puede depender de `relations/`) y
|
|
158
|
+
* `defineRelationsConfig` (`src/relations/`, que no puede depender de
|
|
159
|
+
* `drivers/`). `deny_binding` sigue reservado aunque el modelo (c2r) ya no lo
|
|
160
|
+
* emita: un catálogo migrado del modo `resolver` no puede resucitarlo.
|
|
161
|
+
*/
|
|
162
|
+
export declare const RESERVED_FACTS_TYPES: ReadonlySet<string>;
|
|
163
|
+
/**
|
|
164
|
+
* Longitud máxima de un slug. Un nombre de relación en FGA admite 50
|
|
165
|
+
* caracteres y el modelo deriva `permits_<slug>` (el prefijo más largo, 8):
|
|
166
|
+
* 42 es lo que cabe con cualquier prefijo. Es más estricto que la columna
|
|
167
|
+
* (100) a propósito: un catálogo legal en `database` tiene que ser publicable
|
|
168
|
+
* en `openfga` (S13), o el cambio de driver no es una migración de hechos.
|
|
169
|
+
*/
|
|
170
|
+
export declare const MAX_SLUG_LENGTH: number;
|
|
171
|
+
export type SlugKind = 'rol' | 'permiso';
|
|
172
|
+
/** Slug de rol o permiso válido para AMBOS drivers (formato, longitud, reservados). */
|
|
173
|
+
export declare function assertValidSlug(kind: SlugKind, slug: unknown): asserts slug is string;
|
|
174
|
+
/**
|
|
175
|
+
* Cómo se proyecta un slug a un nombre de relación FGA (`:` → `_`). Dos slugs
|
|
176
|
+
* distintos con la misma proyección (`docs:write` / `docs_write`) serían UNA
|
|
177
|
+
* relación: el catálogo los rechaza juntos (L0.8a).
|
|
178
|
+
*/
|
|
179
|
+
export declare function slugAsRelation(slug: string): string;
|
|
180
|
+
/** Ningún par de slugs del conjunto colisiona tras proyectarse a relación. */
|
|
181
|
+
export declare function assertNoSlugCollisions(kind: SlugKind, slugs: Iterable<string>): void;
|
|
182
|
+
/** Versiones booleanas para caminos de LECTURA que no deben lanzar (ids que vienen del backend). */
|
|
183
|
+
export declare function isValidScope(scope: ScopeRef): boolean;
|
|
184
|
+
export declare function isValidScopeType(scopeType: unknown): scopeType is string;
|
|
185
|
+
export declare function isValidSlug(kind: SlugKind, slug: string): boolean;
|
|
186
|
+
/**
|
|
187
|
+
* La pregunta de `hasRole`, validada: string ⇒ `{ slug }`; objeto ⇒
|
|
188
|
+
* `{ slug, scopeType }` (ambos con la gramática del motor).
|
|
189
|
+
*/
|
|
190
|
+
export declare function normalizeRoleQuery(role: RoleQuery): NormalizedRoleQuery;
|
|
191
|
+
/**
|
|
192
|
+
* Las claves de la cadena desde cada nivel hacia la raíz (3B · B2; movida
|
|
193
|
+
* aquí en 3D · N5 para que exista UNA sola codificación de clave de scope):
|
|
194
|
+
* `keysFrom[i]` = `chain.slice(i).map(scopeKey)`. Un rol es visible en el
|
|
195
|
+
* nivel `i` si es global o su owner está en `keysFrom[i]` — el owner tiene
|
|
196
|
+
* que ser ancestro-o-igual del scope de la ASIGNACIÓN, no de la pregunta.
|
|
197
|
+
*/
|
|
198
|
+
export declare function chainKeysFrom(chain: ScopeRef[]): string[][];
|
|
199
|
+
/**
|
|
200
|
+
* `expiresAt` de un `grant`, en sus tres estados legales: omitido, `null` o
|
|
201
|
+
* una `Date` válida. Una cadena, un número o un `Invalid Date` no son una
|
|
202
|
+
* caducidad: un driver lanzaba un TypeError al serializar y el otro persistía
|
|
203
|
+
* basura (D7). Se rechaza como identidad mal formada (422).
|
|
204
|
+
*/
|
|
205
|
+
export declare function assertExpiresAt(value: unknown): asserts value is Date | null | undefined;
|
|
206
|
+
export interface IdentityParts {
|
|
207
|
+
subject?: SubjectRef;
|
|
208
|
+
scope?: ScopeRef;
|
|
209
|
+
scopeType?: string;
|
|
210
|
+
/** Pregunta de `hasRole`: string u objeto `{ slug, scopeType }`. */
|
|
211
|
+
role?: RoleQuery;
|
|
212
|
+
/**
|
|
213
|
+
* Slug de rol donde el contrato pide un string (`grant`, `revoke`,
|
|
214
|
+
* `listSubjects`): un `RoleQuery` objeto aquí es 422, no un 503 ni un
|
|
215
|
+
* TypeError (D11).
|
|
216
|
+
*/
|
|
217
|
+
roleSlug?: unknown;
|
|
218
|
+
permission?: string;
|
|
219
|
+
/** Caducidad de un `grant`: `undefined` | `null` | `Date` válida. */
|
|
220
|
+
expiresAt?: unknown;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Valida en un solo paso los componentes que recibe una operación. Se llama
|
|
224
|
+
* ANTES de tocar catálogo, árbol o backend: una pregunta mal formada no
|
|
225
|
+
* cuesta una consulta ni deja rastro.
|
|
226
|
+
*/
|
|
227
|
+
export declare function assertIdentity(parts: IdentityParts): void;
|
|
228
|
+
//# 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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"}
|