@authhero/multi-tenancy 14.2.0 → 14.4.0
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/dist/multi-tenancy.cjs +1 -1
- package/dist/multi-tenancy.mjs +429 -426
- package/dist/types/hooks/access-control.d.ts +25 -0
- package/dist/types/hooks/access-control.d.ts.map +1 -0
- package/dist/types/hooks/database.d.ts +35 -0
- package/dist/types/hooks/database.d.ts.map +1 -0
- package/dist/types/hooks/index.d.ts +5 -0
- package/dist/types/hooks/index.d.ts.map +1 -0
- package/dist/types/hooks/provisioning.d.ts +15 -0
- package/dist/types/hooks/provisioning.d.ts.map +1 -0
- package/dist/types/hooks/resource-server-sync.d.ts +140 -0
- package/dist/types/hooks/resource-server-sync.d.ts.map +1 -0
- package/dist/types/hooks/role-sync.d.ts +145 -0
- package/dist/types/hooks/role-sync.d.ts.map +1 -0
- package/dist/types/hooks/sync.d.ts +54 -0
- package/dist/types/hooks/sync.d.ts.map +1 -0
- package/dist/types/index.d.ts +117 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/init.d.ts +110 -0
- package/dist/types/init.d.ts.map +1 -0
- package/dist/types/middleware/index.d.ts +114 -0
- package/dist/types/middleware/index.d.ts.map +1 -0
- package/dist/types/middleware/protect-synced.d.ts +40 -0
- package/dist/types/middleware/protect-synced.d.ts.map +1 -0
- package/dist/types/middleware/settings-inheritance.d.ts +89 -0
- package/dist/types/middleware/settings-inheritance.d.ts.map +1 -0
- package/dist/types/plugin.d.ts +66 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/routes/index.d.ts +2 -0
- package/dist/types/routes/index.d.ts.map +1 -0
- package/dist/types/routes/tenants.d.ts +18 -0
- package/dist/types/routes/tenants.d.ts.map +1 -0
- package/dist/types/types.d.ts +295 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +3 -0
- package/dist/types/utils/index.d.ts.map +1 -0
- package/package.json +9 -9
- package/dist/multi-tenancy.d.ts +0 -41331
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AccessControlConfig, MultiTenancyHooks } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Creates hooks for organization-based tenant access control.
|
|
4
|
+
*
|
|
5
|
+
* This implements the following access model:
|
|
6
|
+
* - Control plane: Accessible without an organization claim
|
|
7
|
+
* - Child tenants: Require an organization claim matching the tenant ID
|
|
8
|
+
* - org_name (organization name) takes precedence and should match tenant ID
|
|
9
|
+
* - org_id (organization ID) is checked as fallback
|
|
10
|
+
*
|
|
11
|
+
* @param config - Access control configuration
|
|
12
|
+
* @returns Hooks for access validation
|
|
13
|
+
*/
|
|
14
|
+
export declare function createAccessControlHooks(config: AccessControlConfig): Pick<MultiTenancyHooks, "onTenantAccessValidation">;
|
|
15
|
+
/**
|
|
16
|
+
* Validates that a token can access a specific tenant based on its organization claim.
|
|
17
|
+
*
|
|
18
|
+
* @param organizationId - The organization ID from the token (may be undefined)
|
|
19
|
+
* @param orgName - The organization name from the token (may be undefined, takes precedence)
|
|
20
|
+
* @param targetTenantId - The tenant ID being accessed
|
|
21
|
+
* @param controlPlaneTenantId - The control plane/management tenant ID
|
|
22
|
+
* @returns true if access is allowed
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateTenantAccess(organizationId: string | undefined, targetTenantId: string, controlPlaneTenantId: string, orgName?: string): boolean;
|
|
25
|
+
//# sourceMappingURL=access-control.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-control.d.ts","sourceRoot":"","sources":["../../../src/hooks/access-control.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EAEnB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,mBAAmB,GAC1B,IAAI,CAAC,iBAAiB,EAAE,0BAA0B,CAAC,CAuCrD;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,cAAc,EAAE,MAAM,EACtB,oBAAoB,EAAE,MAAM,EAC5B,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAiBT"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { DataAdapters } from "authhero";
|
|
2
|
+
import { DatabaseIsolationConfig, MultiTenancyHooks } from "../types";
|
|
3
|
+
/**
|
|
4
|
+
* Creates hooks for per-tenant database resolution.
|
|
5
|
+
*
|
|
6
|
+
* This enables scenarios where each tenant has its own database instance,
|
|
7
|
+
* providing complete data isolation.
|
|
8
|
+
*
|
|
9
|
+
* @param config - Database isolation configuration
|
|
10
|
+
* @returns Hooks for database resolution
|
|
11
|
+
*/
|
|
12
|
+
export declare function createDatabaseHooks(config: DatabaseIsolationConfig): Pick<MultiTenancyHooks, "resolveDataAdapters">;
|
|
13
|
+
/**
|
|
14
|
+
* Database factory interface for creating tenant-specific database adapters.
|
|
15
|
+
*
|
|
16
|
+
* Implementations of this interface should live in the respective adapter packages:
|
|
17
|
+
* - D1: @authhero/cloudflare
|
|
18
|
+
* - Turso: @authhero/turso (or similar)
|
|
19
|
+
* - Custom: Implement your own
|
|
20
|
+
*/
|
|
21
|
+
export interface DatabaseFactory {
|
|
22
|
+
/**
|
|
23
|
+
* Get or create a database adapter for a tenant.
|
|
24
|
+
*/
|
|
25
|
+
getAdapters(tenantId: string): Promise<DataAdapters>;
|
|
26
|
+
/**
|
|
27
|
+
* Provision a new database for a tenant.
|
|
28
|
+
*/
|
|
29
|
+
provision(tenantId: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Deprovision (delete) a tenant's database.
|
|
32
|
+
*/
|
|
33
|
+
deprovision(tenantId: string): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../src/hooks/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAEtE;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,uBAAuB,GAC9B,IAAI,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,CAgBhD;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAErD;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3C;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createAccessControlHooks, validateTenantAccess, } from "./access-control";
|
|
2
|
+
export { createDatabaseHooks, type DatabaseFactory } from "./database";
|
|
3
|
+
export { createProvisioningHooks } from "./provisioning";
|
|
4
|
+
export { createSyncHooks, type EntitySyncConfig, type SyncHooksResult, } from "./sync";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,mBAAmB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EACL,eAAe,EACf,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,QAAQ,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MultiTenancyConfig, TenantEntityHooks } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Creates hooks for tenant provisioning and deprovisioning.
|
|
4
|
+
*
|
|
5
|
+
* This handles:
|
|
6
|
+
* - Setting the correct audience for new tenants (urn:authhero:tenant:{id})
|
|
7
|
+
* - Creating organizations on the control plane when a new tenant is created
|
|
8
|
+
* - Provisioning databases for new tenants
|
|
9
|
+
* - Cleaning up organizations and databases when tenants are deleted
|
|
10
|
+
*
|
|
11
|
+
* @param config - Multi-tenancy configuration
|
|
12
|
+
* @returns Tenant entity hooks for lifecycle events
|
|
13
|
+
*/
|
|
14
|
+
export declare function createProvisioningHooks(config: MultiTenancyConfig): TenantEntityHooks;
|
|
15
|
+
//# sourceMappingURL=provisioning.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provisioning.d.ts","sourceRoot":"","sources":["../../../src/hooks/provisioning.ts"],"names":[],"mappings":"AAeA,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EAElB,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,kBAAkB,GACzB,iBAAiB,CAyEnB"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { DataAdapters, ResourceServer, ResourceServerInsert } from "authhero";
|
|
2
|
+
import { TenantEntityHooks } from "../types";
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for resource server synchronization
|
|
5
|
+
*/
|
|
6
|
+
export interface ResourceServerSyncConfig {
|
|
7
|
+
/**
|
|
8
|
+
* The control plane tenant ID from which resource servers are synced
|
|
9
|
+
*/
|
|
10
|
+
controlPlaneTenantId: string;
|
|
11
|
+
/**
|
|
12
|
+
* Function to get the list of all tenant IDs to sync to.
|
|
13
|
+
* Called when a resource server is created/updated/deleted on the control plane.
|
|
14
|
+
*/
|
|
15
|
+
getChildTenantIds: () => Promise<string[]>;
|
|
16
|
+
/**
|
|
17
|
+
* Function to get adapters for a specific tenant.
|
|
18
|
+
* Used to write resource servers to child tenants.
|
|
19
|
+
*/
|
|
20
|
+
getAdapters: (tenantId: string) => Promise<DataAdapters>;
|
|
21
|
+
/**
|
|
22
|
+
* Optional: Filter function to determine if a resource server should be synced.
|
|
23
|
+
* Return true to sync, false to skip.
|
|
24
|
+
* @default All resource servers are synced
|
|
25
|
+
*/
|
|
26
|
+
shouldSync?: (resourceServer: ResourceServer) => boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Optional: Transform the resource server before syncing to child tenants.
|
|
29
|
+
* Useful for modifying identifiers or removing sensitive data.
|
|
30
|
+
*/
|
|
31
|
+
transformForSync?: (resourceServer: ResourceServer, targetTenantId: string) => ResourceServerInsert;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Context passed to entity hooks
|
|
35
|
+
*/
|
|
36
|
+
interface EntityHookContext {
|
|
37
|
+
tenantId: string;
|
|
38
|
+
adapters: DataAdapters;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Entity hooks for resource server CRUD operations
|
|
42
|
+
*/
|
|
43
|
+
export interface ResourceServerEntityHooks {
|
|
44
|
+
afterCreate?: (ctx: EntityHookContext, entity: ResourceServer) => Promise<void>;
|
|
45
|
+
afterUpdate?: (ctx: EntityHookContext, id: string, entity: ResourceServer) => Promise<void>;
|
|
46
|
+
afterDelete?: (ctx: EntityHookContext, id: string) => Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Creates entity hooks for syncing resource servers from the control plane to all child tenants.
|
|
50
|
+
*
|
|
51
|
+
* When a resource server is created, updated, or deleted on the control plane,
|
|
52
|
+
* the change is automatically propagated to all child tenants.
|
|
53
|
+
*
|
|
54
|
+
* @param config - Resource server sync configuration
|
|
55
|
+
* @returns Entity hooks for resource server synchronization
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* import { createResourceServerSyncHooks } from "@authhero/multi-tenancy";
|
|
60
|
+
*
|
|
61
|
+
* const resourceServerHooks = createResourceServerSyncHooks({
|
|
62
|
+
* controlPlaneTenantId: "main",
|
|
63
|
+
* getChildTenantIds: async () => {
|
|
64
|
+
* const tenants = await db.tenants.list();
|
|
65
|
+
* return tenants.filter(t => t.id !== "main").map(t => t.id);
|
|
66
|
+
* },
|
|
67
|
+
* getAdapters: async (tenantId) => {
|
|
68
|
+
* return createAdaptersForTenant(tenantId);
|
|
69
|
+
* },
|
|
70
|
+
* });
|
|
71
|
+
*
|
|
72
|
+
* // Use with AuthHero config
|
|
73
|
+
* const config: AuthHeroConfig = {
|
|
74
|
+
* dataAdapter,
|
|
75
|
+
* entityHooks: {
|
|
76
|
+
* resourceServers: resourceServerHooks,
|
|
77
|
+
* },
|
|
78
|
+
* };
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function createResourceServerSyncHooks(config: ResourceServerSyncConfig): ResourceServerEntityHooks;
|
|
82
|
+
/**
|
|
83
|
+
* Configuration for syncing resource servers to new tenants
|
|
84
|
+
*/
|
|
85
|
+
export interface TenantResourceServerSyncConfig {
|
|
86
|
+
/**
|
|
87
|
+
* The control plane tenant ID from which resource servers are copied
|
|
88
|
+
*/
|
|
89
|
+
controlPlaneTenantId: string;
|
|
90
|
+
/**
|
|
91
|
+
* Function to get adapters for the control plane.
|
|
92
|
+
* Used to read existing resource servers.
|
|
93
|
+
*/
|
|
94
|
+
getControlPlaneAdapters: () => Promise<DataAdapters>;
|
|
95
|
+
/**
|
|
96
|
+
* Function to get adapters for the new tenant.
|
|
97
|
+
* Used to write resource servers to the new tenant.
|
|
98
|
+
*/
|
|
99
|
+
getAdapters: (tenantId: string) => Promise<DataAdapters>;
|
|
100
|
+
/**
|
|
101
|
+
* Optional: Filter function to determine if a resource server should be synced.
|
|
102
|
+
* Return true to sync, false to skip.
|
|
103
|
+
* @default All resource servers are synced
|
|
104
|
+
*/
|
|
105
|
+
shouldSync?: (resourceServer: ResourceServer) => boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Optional: Transform the resource server before syncing to the new tenant.
|
|
108
|
+
* Useful for modifying identifiers or removing sensitive data.
|
|
109
|
+
*/
|
|
110
|
+
transformForSync?: (resourceServer: ResourceServer, targetTenantId: string) => ResourceServerInsert;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Creates a tenant afterCreate hook that copies all resource servers from the control plane
|
|
114
|
+
* to a newly created tenant.
|
|
115
|
+
*
|
|
116
|
+
* This should be used with the MultiTenancyHooks.tenants.afterCreate hook.
|
|
117
|
+
*
|
|
118
|
+
* @param config - Configuration for tenant resource server sync
|
|
119
|
+
* @returns A TenantEntityHooks object with afterCreate implemented
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* import { createTenantResourceServerSyncHooks } from "@authhero/multi-tenancy";
|
|
124
|
+
*
|
|
125
|
+
* const resourceServerSyncHooks = createTenantResourceServerSyncHooks({
|
|
126
|
+
* controlPlaneTenantId: "main",
|
|
127
|
+
* getControlPlaneAdapters: async () => controlPlaneAdapters,
|
|
128
|
+
* getAdapters: async (tenantId) => createAdaptersForTenant(tenantId),
|
|
129
|
+
* });
|
|
130
|
+
*
|
|
131
|
+
* const multiTenancyHooks: MultiTenancyHooks = {
|
|
132
|
+
* tenants: {
|
|
133
|
+
* afterCreate: resourceServerSyncHooks.afterCreate,
|
|
134
|
+
* },
|
|
135
|
+
* };
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export declare function createTenantResourceServerSyncHooks(config: TenantResourceServerSyncConfig): TenantEntityHooks;
|
|
139
|
+
export {};
|
|
140
|
+
//# sourceMappingURL=resource-server-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-server-sync.d.ts","sourceRoot":"","sources":["../../../src/hooks/resource-server-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,cAAc,EACd,oBAAoB,EAErB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,iBAAiB,EAAqB,MAAM,UAAU,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IACH,iBAAiB,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE3C;;;OAGG;IACH,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzD;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,cAAc,EAAE,cAAc,KAAK,OAAO,CAAC;IAEzD;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CACjB,cAAc,EAAE,cAAc,EAC9B,cAAc,EAAE,MAAM,KACnB,oBAAoB,CAAC;CAC3B;AAED;;GAEG;AACH,UAAU,iBAAiB;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,YAAY,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,WAAW,CAAC,EAAE,CACZ,GAAG,EAAE,iBAAiB,EACtB,MAAM,EAAE,cAAc,KACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,WAAW,CAAC,EAAE,CACZ,GAAG,EAAE,iBAAiB,EACtB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,cAAc,KACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,wBAAwB,GAC/B,yBAAyB,CA2L3B;AAED;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC7C;;OAEG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IACH,uBAAuB,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;IAErD;;;OAGG;IACH,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzD;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,cAAc,EAAE,cAAc,KAAK,OAAO,CAAC;IAEzD;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CACjB,cAAc,EAAE,cAAc,EAC9B,cAAc,EAAE,MAAM,KACnB,oBAAoB,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,mCAAmC,CACjD,MAAM,EAAE,8BAA8B,GACrC,iBAAiB,CAiFnB"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { DataAdapters, Role, RoleInsert } from "authhero";
|
|
2
|
+
import { TenantEntityHooks } from "../types";
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for role synchronization
|
|
5
|
+
*/
|
|
6
|
+
export interface RoleSyncConfig {
|
|
7
|
+
/**
|
|
8
|
+
* The control plane tenant ID from which roles are synced
|
|
9
|
+
*/
|
|
10
|
+
controlPlaneTenantId: string;
|
|
11
|
+
/**
|
|
12
|
+
* Function to get the list of all tenant IDs to sync to.
|
|
13
|
+
* Called when a role is created/updated/deleted on the control plane.
|
|
14
|
+
*/
|
|
15
|
+
getChildTenantIds: () => Promise<string[]>;
|
|
16
|
+
/**
|
|
17
|
+
* Function to get adapters for a specific tenant.
|
|
18
|
+
* Used to write roles to child tenants.
|
|
19
|
+
*/
|
|
20
|
+
getAdapters: (tenantId: string) => Promise<DataAdapters>;
|
|
21
|
+
/**
|
|
22
|
+
* Optional: Filter function to determine if a role should be synced.
|
|
23
|
+
* Return true to sync, false to skip.
|
|
24
|
+
* @default All roles are synced
|
|
25
|
+
*/
|
|
26
|
+
shouldSync?: (role: Role) => boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Optional: Transform the role before syncing to child tenants.
|
|
29
|
+
* Useful for modifying names or removing sensitive data.
|
|
30
|
+
*/
|
|
31
|
+
transformForSync?: (role: Role, targetTenantId: string) => RoleInsert;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Context passed to entity hooks
|
|
35
|
+
*/
|
|
36
|
+
interface EntityHookContext {
|
|
37
|
+
tenantId: string;
|
|
38
|
+
adapters: DataAdapters;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Entity hooks for role CRUD operations
|
|
42
|
+
*/
|
|
43
|
+
export interface RoleEntityHooks {
|
|
44
|
+
afterCreate?: (ctx: EntityHookContext, entity: Role) => Promise<void>;
|
|
45
|
+
afterUpdate?: (ctx: EntityHookContext, id: string, entity: Role) => Promise<void>;
|
|
46
|
+
afterDelete?: (ctx: EntityHookContext, id: string) => Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Creates entity hooks for syncing roles from the control plane to all child tenants.
|
|
50
|
+
*
|
|
51
|
+
* When a role is created, updated, or deleted on the control plane,
|
|
52
|
+
* the change is automatically propagated to all child tenants.
|
|
53
|
+
*
|
|
54
|
+
* @param config - Role sync configuration
|
|
55
|
+
* @returns Entity hooks for role synchronization
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* import { createRoleSyncHooks } from "@authhero/multi-tenancy";
|
|
60
|
+
*
|
|
61
|
+
* const roleHooks = createRoleSyncHooks({
|
|
62
|
+
* controlPlaneTenantId: "main",
|
|
63
|
+
* getChildTenantIds: async () => {
|
|
64
|
+
* const tenants = await db.tenants.list();
|
|
65
|
+
* return tenants.filter(t => t.id !== "main").map(t => t.id);
|
|
66
|
+
* },
|
|
67
|
+
* getAdapters: async (tenantId) => {
|
|
68
|
+
* return createAdaptersForTenant(tenantId);
|
|
69
|
+
* },
|
|
70
|
+
* });
|
|
71
|
+
*
|
|
72
|
+
* // Use with AuthHero config
|
|
73
|
+
* const config: AuthHeroConfig = {
|
|
74
|
+
* dataAdapter,
|
|
75
|
+
* entityHooks: {
|
|
76
|
+
* roles: roleHooks,
|
|
77
|
+
* },
|
|
78
|
+
* };
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function createRoleSyncHooks(config: RoleSyncConfig): RoleEntityHooks;
|
|
82
|
+
/**
|
|
83
|
+
* Configuration for syncing roles to new tenants
|
|
84
|
+
*/
|
|
85
|
+
export interface TenantRoleSyncConfig {
|
|
86
|
+
/**
|
|
87
|
+
* The control plane tenant ID from which roles are copied
|
|
88
|
+
*/
|
|
89
|
+
controlPlaneTenantId: string;
|
|
90
|
+
/**
|
|
91
|
+
* Function to get adapters for the control plane.
|
|
92
|
+
* Used to read existing roles.
|
|
93
|
+
*/
|
|
94
|
+
getControlPlaneAdapters: () => Promise<DataAdapters>;
|
|
95
|
+
/**
|
|
96
|
+
* Function to get adapters for the new tenant.
|
|
97
|
+
* Used to write roles to the new tenant.
|
|
98
|
+
*/
|
|
99
|
+
getAdapters: (tenantId: string) => Promise<DataAdapters>;
|
|
100
|
+
/**
|
|
101
|
+
* Optional: Filter function to determine if a role should be synced.
|
|
102
|
+
* Return true to sync, false to skip.
|
|
103
|
+
* @default All roles are synced
|
|
104
|
+
*/
|
|
105
|
+
shouldSync?: (role: Role) => boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Optional: Transform the role before syncing to the new tenant.
|
|
108
|
+
* Useful for modifying names or removing sensitive data.
|
|
109
|
+
*/
|
|
110
|
+
transformForSync?: (role: Role, targetTenantId: string) => RoleInsert;
|
|
111
|
+
/**
|
|
112
|
+
* Whether to also sync role permissions (scopes from resource servers).
|
|
113
|
+
* @default true
|
|
114
|
+
*/
|
|
115
|
+
syncPermissions?: boolean;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Creates a tenant afterCreate hook that copies all roles from the control plane
|
|
119
|
+
* to a newly created tenant.
|
|
120
|
+
*
|
|
121
|
+
* This should be used with the MultiTenancyHooks.tenants.afterCreate hook.
|
|
122
|
+
*
|
|
123
|
+
* @param config - Configuration for tenant role sync
|
|
124
|
+
* @returns A TenantEntityHooks object with afterCreate implemented
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* import { createTenantRoleSyncHooks } from "@authhero/multi-tenancy";
|
|
129
|
+
*
|
|
130
|
+
* const roleSyncHooks = createTenantRoleSyncHooks({
|
|
131
|
+
* controlPlaneTenantId: "main",
|
|
132
|
+
* getControlPlaneAdapters: async () => controlPlaneAdapters,
|
|
133
|
+
* getAdapters: async (tenantId) => createAdaptersForTenant(tenantId),
|
|
134
|
+
* });
|
|
135
|
+
*
|
|
136
|
+
* const multiTenancyHooks: MultiTenancyHooks = {
|
|
137
|
+
* tenants: {
|
|
138
|
+
* afterCreate: roleSyncHooks.afterCreate,
|
|
139
|
+
* },
|
|
140
|
+
* };
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export declare function createTenantRoleSyncHooks(config: TenantRoleSyncConfig): TenantEntityHooks;
|
|
144
|
+
export {};
|
|
145
|
+
//# sourceMappingURL=role-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"role-sync.d.ts","sourceRoot":"","sources":["../../../src/hooks/role-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAY,MAAM,UAAU,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAqB,MAAM,UAAU,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IACH,iBAAiB,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE3C;;;OAGG;IACH,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzD;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC;IAErC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,KAAK,UAAU,CAAC;CACvE;AAED;;GAEG;AACH,UAAU,iBAAiB;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,YAAY,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,EAAE,MAAM,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,WAAW,CAAC,EAAE,CACZ,GAAG,EAAE,iBAAiB,EACtB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,IAAI,KACT,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,eAAe,CAwI3E;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IACH,uBAAuB,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;IAErD;;;OAGG;IACH,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzD;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC;IAErC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,KAAK,UAAU,CAAC;IAEtE;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,oBAAoB,GAC3B,iBAAiB,CA4GnB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { DataAdapters, Role, RoleInsert, ResourceServer, ResourceServerInsert, EntityHooks } from "authhero";
|
|
2
|
+
import { TenantEntityHooks } from "../types";
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for entity synchronization
|
|
5
|
+
*/
|
|
6
|
+
export interface EntitySyncConfig {
|
|
7
|
+
/**
|
|
8
|
+
* The control plane tenant ID from which entities are synced
|
|
9
|
+
*/
|
|
10
|
+
controlPlaneTenantId: string;
|
|
11
|
+
/**
|
|
12
|
+
* Function to get the list of all tenant IDs to sync to.
|
|
13
|
+
*/
|
|
14
|
+
getChildTenantIds: () => Promise<string[]>;
|
|
15
|
+
/**
|
|
16
|
+
* Function to get adapters for a specific tenant.
|
|
17
|
+
*/
|
|
18
|
+
getAdapters: (tenantId: string) => Promise<DataAdapters>;
|
|
19
|
+
/**
|
|
20
|
+
* Function to get adapters for the control plane (used for tenant creation sync).
|
|
21
|
+
*/
|
|
22
|
+
getControlPlaneAdapters: () => Promise<DataAdapters>;
|
|
23
|
+
/**
|
|
24
|
+
* Which entities to sync. All default to true.
|
|
25
|
+
* Note: Connections are NOT synced - they use runtime fallback by strategy instead.
|
|
26
|
+
*/
|
|
27
|
+
sync?: {
|
|
28
|
+
resourceServers?: boolean;
|
|
29
|
+
roles?: boolean;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Optional filters for each entity type
|
|
33
|
+
*/
|
|
34
|
+
filters?: {
|
|
35
|
+
resourceServers?: (entity: ResourceServer) => boolean;
|
|
36
|
+
roles?: (entity: Role) => boolean;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Result from createSyncHooks containing all entity and tenant hooks
|
|
41
|
+
*
|
|
42
|
+
* Note: Connections are NOT synced - they use runtime fallback by strategy instead.
|
|
43
|
+
* Tenants can define a connection with a strategy (e.g., "google") and leave keys blank,
|
|
44
|
+
* and the system will fallback to the control plane's connection with the same strategy.
|
|
45
|
+
*/
|
|
46
|
+
export interface SyncHooksResult {
|
|
47
|
+
entityHooks: {
|
|
48
|
+
resourceServers?: EntityHooks<ResourceServer, ResourceServerInsert>;
|
|
49
|
+
roles?: EntityHooks<Role, RoleInsert>;
|
|
50
|
+
};
|
|
51
|
+
tenantHooks: TenantEntityHooks;
|
|
52
|
+
}
|
|
53
|
+
export declare function createSyncHooks(config: EntitySyncConfig): SyncHooksResult;
|
|
54
|
+
//# sourceMappingURL=sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../src/hooks/sync.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,IAAI,EACJ,UAAU,EACV,cAAc,EACd,oBAAoB,EAGpB,WAAW,EAEZ,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,iBAAiB,EAAqB,MAAM,UAAU,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAE7B;;OAEG;IACH,iBAAiB,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE3C;;OAEG;IACH,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzD;;OAEG;IACH,uBAAuB,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;IAErD;;;OAGG;IACH,IAAI,CAAC,EAAE;QACL,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,CAAC;IAEF;;OAEG;IACH,OAAO,CAAC,EAAE;QACR,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC;QACtD,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,KAAK,OAAO,CAAC;KACnC,CAAC;CACH;AAoQD;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE;QACX,eAAe,CAAC,EAAE,WAAW,CAAC,cAAc,EAAE,oBAAoB,CAAC,CAAC;QACpE,KAAK,CAAC,EAAE,WAAW,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;KACvC,CAAC;IACF,WAAW,EAAE,iBAAiB,CAAC;CAChC;AAqCD,wBAAgB,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,eAAe,CAwLzE"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { MultiTenancyConfig, MultiTenancyHooks, MultiTenancyBindings, MultiTenancyVariables } from "./types";
|
|
3
|
+
export * from "./types";
|
|
4
|
+
export { initMultiTenant } from "./init";
|
|
5
|
+
export type { MultiTenantConfig, MultiTenantResult } from "./init";
|
|
6
|
+
export { createSyncHooks } from "./hooks/sync";
|
|
7
|
+
export type { EntitySyncConfig, SyncHooksResult } from "./hooks/sync";
|
|
8
|
+
export { createTenantsOpenAPIRouter } from "./routes";
|
|
9
|
+
export { createMultiTenancyMiddleware, createAccessControlMiddleware, createControlPlaneTenantMiddleware, createSubdomainMiddleware, createDatabaseMiddleware, createProtectSyncedMiddleware, createRuntimeFallbackAdapter, withRuntimeFallback, createSettingsInheritanceAdapter, withSettingsInheritance, } from "./middleware";
|
|
10
|
+
export type { RuntimeFallbackConfig, SettingsInheritanceConfig, } from "./middleware";
|
|
11
|
+
export { createMultiTenancyPlugin } from "./plugin";
|
|
12
|
+
export type { AuthHeroPlugin } from "./plugin";
|
|
13
|
+
export { createAccessControlHooks, createDatabaseHooks, createProvisioningHooks, } from "./hooks";
|
|
14
|
+
export { validateTenantAccess } from "./hooks/access-control";
|
|
15
|
+
export type { DatabaseFactory } from "./hooks/database";
|
|
16
|
+
/**
|
|
17
|
+
* Creates multi-tenancy hooks from configuration.
|
|
18
|
+
*
|
|
19
|
+
* This combines access control, database resolution, and provisioning hooks
|
|
20
|
+
* into a single hooks object that can be passed to AuthHero.
|
|
21
|
+
*
|
|
22
|
+
* @param config - Multi-tenancy configuration
|
|
23
|
+
* @returns Combined hooks for multi-tenancy support
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* import { createMultiTenancyHooks } from "@authhero/multi-tenancy";
|
|
28
|
+
*
|
|
29
|
+
* const hooks = createMultiTenancyHooks({
|
|
30
|
+
* accessControl: {
|
|
31
|
+
* controlPlaneTenantId: "control_plane",
|
|
32
|
+
* defaultPermissions: ["tenant:admin"],
|
|
33
|
+
* },
|
|
34
|
+
* databaseIsolation: {
|
|
35
|
+
* getAdapters: async (tenantId) => getDatabase(tenantId),
|
|
36
|
+
* },
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function createMultiTenancyHooks(config: MultiTenancyConfig): MultiTenancyHooks;
|
|
41
|
+
/**
|
|
42
|
+
* Creates a complete multi-tenancy Hono app with routes and middleware.
|
|
43
|
+
*
|
|
44
|
+
* This creates a Hono app with:
|
|
45
|
+
* - Tenant management routes (CRUD for tenants)
|
|
46
|
+
* - Access control middleware
|
|
47
|
+
* - Subdomain routing (optional)
|
|
48
|
+
* - Database resolution (optional)
|
|
49
|
+
*
|
|
50
|
+
* @param config - Multi-tenancy configuration
|
|
51
|
+
* @returns Hono app with multi-tenancy routes
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* import { createMultiTenancy } from "@authhero/multi-tenancy";
|
|
56
|
+
*
|
|
57
|
+
* const multiTenancyApp = createMultiTenancy({
|
|
58
|
+
* accessControl: {
|
|
59
|
+
* controlPlaneTenantId: "main",
|
|
60
|
+
* },
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
63
|
+
* // Mount on your main app
|
|
64
|
+
* app.route("/management", multiTenancyApp);
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function createMultiTenancy(config: MultiTenancyConfig): Hono<{
|
|
68
|
+
Bindings: MultiTenancyBindings;
|
|
69
|
+
Variables: MultiTenancyVariables;
|
|
70
|
+
}, import("hono/types").BlankSchema, "/">;
|
|
71
|
+
/**
|
|
72
|
+
* Creates a multi-tenancy setup with both hooks and middleware.
|
|
73
|
+
*
|
|
74
|
+
* This is a convenience function that returns everything needed to
|
|
75
|
+
* integrate multi-tenancy into an AuthHero application.
|
|
76
|
+
*
|
|
77
|
+
* @param config - Multi-tenancy configuration
|
|
78
|
+
* @returns Object with hooks, middleware, and routes
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* import { setupMultiTenancy } from "@authhero/multi-tenancy";
|
|
83
|
+
*
|
|
84
|
+
* const multiTenancy = setupMultiTenancy({
|
|
85
|
+
* accessControl: {
|
|
86
|
+
* controlPlaneTenantId: "main",
|
|
87
|
+
* },
|
|
88
|
+
* subdomainRouting: {
|
|
89
|
+
* baseDomain: "auth.example.com",
|
|
90
|
+
* },
|
|
91
|
+
* });
|
|
92
|
+
*
|
|
93
|
+
* // Use the middleware
|
|
94
|
+
* app.use("*", multiTenancy.middleware);
|
|
95
|
+
*
|
|
96
|
+
* // Mount the routes
|
|
97
|
+
* app.route("/management", multiTenancy.app);
|
|
98
|
+
*
|
|
99
|
+
* // Pass hooks to AuthHero
|
|
100
|
+
* const authhero = createAuthhero({
|
|
101
|
+
* hooks: multiTenancy.hooks,
|
|
102
|
+
* });
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export declare function setupMultiTenancy(config: MultiTenancyConfig): {
|
|
106
|
+
hooks: MultiTenancyHooks;
|
|
107
|
+
middleware: import("hono").MiddlewareHandler<{
|
|
108
|
+
Bindings: MultiTenancyBindings;
|
|
109
|
+
Variables: MultiTenancyVariables;
|
|
110
|
+
}>;
|
|
111
|
+
app: Hono<{
|
|
112
|
+
Bindings: MultiTenancyBindings;
|
|
113
|
+
Variables: MultiTenancyVariables;
|
|
114
|
+
}, import("hono/types").BlankSchema, "/">;
|
|
115
|
+
config: MultiTenancyConfig;
|
|
116
|
+
};
|
|
117
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,SAAS,CAAC;AAUjB,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AACzC,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAGnE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEtE,OAAO,EAAE,0BAA0B,EAAE,MAAM,UAAU,CAAC;AAEtD,OAAO,EACL,4BAA4B,EAC5B,6BAA6B,EAC7B,kCAAkC,EAClC,yBAAyB,EACzB,wBAAwB,EACxB,6BAA6B,EAC7B,4BAA4B,EAC5B,mBAAmB,EAEnB,gCAAgC,EAChC,uBAAuB,GACxB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AACpD,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG/C,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,kBAAkB,GACzB,iBAAiB,CAgBnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB;cAE/C,oBAAoB;eACnB,qBAAqB;0CASnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,kBAAkB;;;;;;;kBA9C9C,oBAAoB;mBACnB,qBAAqB;;;EAoDnC"}
|