@authhero/multi-tenancy 13.16.0 → 13.18.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/README.md +59 -24
- package/dist/multi-tenancy.cjs +1 -1
- package/dist/multi-tenancy.d.ts +14066 -63169
- package/dist/multi-tenancy.mjs +461 -598
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -9,15 +9,19 @@ Multi-tenancy support for AuthHero with organization-based access control, per-t
|
|
|
9
9
|
- ⚙️ **Settings Inheritance** - Inherit configuration from main tenant to child tenants
|
|
10
10
|
- 🌐 **Subdomain Routing** - Automatic subdomain-to-tenant resolution
|
|
11
11
|
- 🔄 **Tenant Lifecycle** - Automated provisioning and deprovisioning
|
|
12
|
-
- 🪝 **
|
|
13
|
-
- 📡 **
|
|
12
|
+
- 🪝 **Composable Architecture** - Combine multi-tenancy features with the base AuthHero package
|
|
13
|
+
- 📡 **Entity Sync** - Automatically sync resource servers, roles, and connections from control plane to all child tenants
|
|
14
14
|
|
|
15
15
|
## Installation
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
pnpm add @authhero/multi-tenancy
|
|
18
|
+
pnpm add authhero @authhero/multi-tenancy
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
::: tip Peer Dependency
|
|
22
|
+
`@authhero/multi-tenancy` requires `authhero` as a peer dependency. Both packages must be installed.
|
|
23
|
+
:::
|
|
24
|
+
|
|
21
25
|
## Documentation
|
|
22
26
|
|
|
23
27
|
📚 **Full documentation**: [https://authhero.net/packages/multi-tenancy/](https://authhero.net/packages/multi-tenancy/)
|
|
@@ -29,33 +33,64 @@ pnpm add @authhero/multi-tenancy
|
|
|
29
33
|
## Quick Start
|
|
30
34
|
|
|
31
35
|
```typescript
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
import { init, AuthHeroConfig, fetchAll } from "authhero";
|
|
37
|
+
import {
|
|
38
|
+
createSyncHooks,
|
|
39
|
+
createTenantsOpenAPIRouter,
|
|
40
|
+
createProtectSyncedMiddleware,
|
|
41
|
+
} from "@authhero/multi-tenancy";
|
|
42
|
+
import createAdapters from "@authhero/kysely-adapter";
|
|
43
|
+
|
|
44
|
+
const CONTROL_PLANE_TENANT_ID = "control_plane";
|
|
45
|
+
const dataAdapter = createAdapters(db);
|
|
46
|
+
|
|
47
|
+
// Create sync hooks for syncing entities from control plane to child tenants
|
|
48
|
+
const { entityHooks, tenantHooks } = createSyncHooks({
|
|
49
|
+
controlPlaneTenantId: CONTROL_PLANE_TENANT_ID,
|
|
50
|
+
getChildTenantIds: async () => {
|
|
51
|
+
const allTenants = await fetchAll(
|
|
52
|
+
(params) => dataAdapter.tenants.list(params),
|
|
53
|
+
"tenants",
|
|
54
|
+
{ cursorField: "id", pageSize: 100 }
|
|
55
|
+
);
|
|
56
|
+
return allTenants
|
|
57
|
+
.filter((t) => t.id !== CONTROL_PLANE_TENANT_ID)
|
|
58
|
+
.map((t) => t.id);
|
|
59
|
+
},
|
|
60
|
+
getAdapters: async () => dataAdapter,
|
|
61
|
+
getControlPlaneAdapters: async () => dataAdapter,
|
|
62
|
+
sync: {
|
|
63
|
+
resourceServers: true,
|
|
64
|
+
roles: true,
|
|
65
|
+
connections: true,
|
|
40
66
|
},
|
|
41
67
|
});
|
|
42
68
|
|
|
43
|
-
|
|
69
|
+
// Create tenants router
|
|
70
|
+
const tenantsRouter = createTenantsOpenAPIRouter(
|
|
71
|
+
{
|
|
72
|
+
accessControl: {
|
|
73
|
+
controlPlaneTenantId: CONTROL_PLANE_TENANT_ID,
|
|
74
|
+
requireOrganizationMatch: false,
|
|
75
|
+
defaultPermissions: ["tenant:admin"],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{ tenants: tenantHooks }
|
|
79
|
+
);
|
|
44
80
|
|
|
45
|
-
//
|
|
46
|
-
app
|
|
81
|
+
// Initialize AuthHero with sync hooks and tenant routes
|
|
82
|
+
const { app } = init({
|
|
83
|
+
dataAdapter,
|
|
84
|
+
entityHooks,
|
|
85
|
+
managementApiExtensions: [
|
|
86
|
+
{ path: "/tenants", router: tenantsRouter },
|
|
87
|
+
],
|
|
88
|
+
});
|
|
47
89
|
|
|
48
|
-
//
|
|
49
|
-
app.
|
|
90
|
+
// Add middleware to protect synced entities
|
|
91
|
+
app.use("/api/v2/*", createProtectSyncedMiddleware());
|
|
50
92
|
|
|
51
|
-
|
|
52
|
-
app.route(
|
|
53
|
-
"/",
|
|
54
|
-
createAuthhero({
|
|
55
|
-
dataAdapter: env.data,
|
|
56
|
-
hooks: multiTenancy.hooks,
|
|
57
|
-
}),
|
|
58
|
-
);
|
|
93
|
+
export default app;
|
|
59
94
|
```
|
|
60
95
|
|
|
61
96
|
## Key Concepts
|
package/dist/multi-tenancy.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var me=Object.defineProperty;var pe=(t,e,n)=>e in t?me(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var G=(t,e,n)=>pe(t,typeof e!="symbol"?e+"":e,n);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const x=require("hono"),P=require("authhero"),I=require("@hono/zod-openapi"),D=require("@authhero/adapter-interfaces");var S=class extends Error{constructor(e=500,n){super(n==null?void 0:n.message,{cause:n==null?void 0:n.cause});G(this,"res");G(this,"status");this.res=n==null?void 0:n.res,this.status=e}getResponse(){return this.res?new Response(this.res.body,{status:this.status,headers:this.res.headers}):new Response(this.message,{status:this.status})}};function ee(t){const{controlPlaneTenantId:e,requireOrganizationMatch:n=!0}=t;return{async onTenantAccessValidation(r,a){if(a===e)return!0;if(n){const c=r.var.org_name,s=r.var.organization_id,i=c||s;return i?i===a:!1}return!0}}}function te(t,e,n,r){if(e===n)return!0;const a=r||t;return a?a===e:!1}function ne(t){return{async resolveDataAdapters(e){try{return await t.getAdapters(e)}catch(n){console.error(`Failed to resolve data adapters for tenant ${e}:`,n);return}}}}function re(t){return{async beforeCreate(e,n){return!n.audience&&n.id?{...n,audience:P.getTenantAudience(n.id)}:n},async afterCreate(e,n){const{accessControl:r,databaseIsolation:a}=t;r&&e.ctx&&await fe(e,n,r),a!=null&&a.onProvision&&await a.onProvision(n.id)},async beforeDelete(e,n){const{accessControl:r,databaseIsolation:a}=t;if(r)try{const s=(await e.adapters.organizations.list(r.controlPlaneTenantId)).organizations.find(i=>i.name===n);s&&await e.adapters.organizations.remove(r.controlPlaneTenantId,s.id)}catch(c){console.warn(`Failed to remove organization for tenant ${n}:`,c)}if(a!=null&&a.onDeprovision)try{await a.onDeprovision(n)}catch(c){console.warn(`Failed to deprovision database for tenant ${n}:`,c)}}}}async function fe(t,e,n){const{controlPlaneTenantId:r,defaultPermissions:a,defaultRoles:c,issuer:s,adminRoleName:i="Tenant Admin",adminRoleDescription:p="Full access to all tenant management operations",addCreatorToOrganization:o=!0}=n,l=await t.adapters.organizations.create(r,{name:e.id,display_name:e.friendly_name||e.id});let u;if(s&&(u=await ge(t,r,i,p)),o&&t.ctx){const d=t.ctx.var.user;if(d!=null&&d.sub&&!await we(t,r,d.sub))try{await t.adapters.userOrganizations.create(r,{user_id:d.sub,organization_id:l.id}),u&&await t.adapters.userRoles.create(r,d.sub,u,l.id)}catch(f){console.warn(`Failed to add creator ${d.sub} to organization ${l.id}:`,f)}}c&&c.length>0&&console.log(`Would assign roles ${c.join(", ")} to organization ${l.id}`),a&&a.length>0&&console.log(`Would grant permissions ${a.join(", ")} to organization ${l.id}`)}async function we(t,e,n){const r=await t.adapters.userRoles.list(e,n,void 0,"");for(const a of r)if((await t.adapters.rolePermissions.list(e,a.id,{per_page:1e3})).some(i=>i.permission_name==="admin:organizations"))return!0;return!1}async function ge(t,e,n,r){const c=(await t.adapters.roles.list(e,{})).roles.find(o=>o.name===n);if(c)return c.id;const s=await t.adapters.roles.create(e,{name:n,description:r}),i=P.MANAGEMENT_API_AUDIENCE,p=P.MANAGEMENT_API_SCOPES.map(o=>({role_id:s.id,resource_server_identifier:i,permission_name:o.value}));return await t.adapters.rolePermissions.assign(e,s.id,p),s.id}const he=["client_id","client_secret","app_secret","kid","team_id","twilio_sid","twilio_token"];function K(t,e,n=()=>!0){const{controlPlaneTenantId:r,getChildTenantIds:a,getAdapters:c}=t,s=new Map;async function i(l,u,d){return(await e(l).list(u,{q:`name:${d}`,per_page:1}))[0]??null}async function p(l){const u=await a(),d=e(await c(r));await Promise.all(u.map(async m=>{try{const f=await c(m),w=e(f),h={...d.transform(l),is_system:!0},v=await i(f,m,l.name),_=v?w.getId(v):void 0;if(v&&_){const T=w.preserveOnUpdate?w.preserveOnUpdate(v,h):h;await w.update(m,_,T)}else await w.create(m,h)}catch(f){console.error(`Failed to sync ${d.listKey} "${l.name}" to tenant "${m}":`,f)}}))}async function o(l){const u=await a();await Promise.all(u.map(async d=>{try{const m=await c(d),f=e(m),w=await i(m,d,l),g=w?f.getId(w):void 0;w&&g&&await f.remove(d,g)}catch(m){console.error(`Failed to delete entity "${l}" from tenant "${d}":`,m)}}))}return{afterCreate:async(l,u)=>{l.tenantId===r&&n(u)&&await p(u)},afterUpdate:async(l,u,d)=>{l.tenantId===r&&n(d)&&await p(d)},beforeDelete:async(l,u)=>{if(l.tenantId!==r)return;const m=await e(l.adapters).get(l.tenantId,u);m&&n(m)&&s.set(u,m)},afterDelete:async(l,u)=>{if(l.tenantId!==r)return;const d=s.get(u);d&&(s.delete(u),await o(d.name))}}}function L(t,e,n=()=>!0){const{controlPlaneTenantId:r,getControlPlaneAdapters:a,getAdapters:c}=t;return{async afterCreate(s,i){if(i.id!==r)try{const p=await a(),o=await c(i.id),l=e(p),u=e(o),d=await P.fetchAll(m=>l.listPaginated(r,m),l.listKey,{cursorField:"id",pageSize:100});await Promise.all(d.filter(m=>n(m)).map(async m=>{try{const f=l.transform(m);await u.create(i.id,{...f,is_system:!0})}catch(f){console.error(`Failed to sync entity to new tenant "${i.id}":`,f)}}))}catch(p){console.error(`Failed to sync entities to new tenant "${i.id}":`,p)}}}}const k=t=>({list:async(e,n)=>(await t.resourceServers.list(e,n)).resource_servers,listPaginated:(e,n)=>t.resourceServers.list(e,n),get:(e,n)=>t.resourceServers.get(e,n),create:(e,n)=>t.resourceServers.create(e,n),update:(e,n,r)=>t.resourceServers.update(e,n,r),remove:(e,n)=>t.resourceServers.remove(e,n),listKey:"resource_servers",getId:e=>e.id,transform:e=>({id:e.id,name:e.name,identifier:e.identifier,scopes:e.scopes,signing_alg:e.signing_alg,token_lifetime:e.token_lifetime,token_lifetime_for_web:e.token_lifetime_for_web})}),E=t=>({list:async(e,n)=>(await t.roles.list(e,n)).roles,listPaginated:(e,n)=>t.roles.list(e,n),get:(e,n)=>t.roles.get(e,n),create:(e,n)=>t.roles.create(e,n),update:(e,n,r)=>t.roles.update(e,n,r),remove:(e,n)=>t.roles.remove(e,n),listKey:"roles",getId:e=>e.id,transform:e=>({id:e.id,name:e.name,description:e.description})}),H=t=>({list:async(e,n)=>(await t.connections.list(e,n)).connections,listPaginated:(e,n)=>t.connections.list(e,n),get:(e,n)=>t.connections.get(e,n),create:(e,n)=>t.connections.create(e,n),update:(e,n,r)=>t.connections.update(e,n,r),remove:(e,n)=>t.connections.remove(e,n),listKey:"connections",getId:e=>e.id,transform:e=>{const n=e.options?{...e.options}:{};for(const r of he)delete n[r];return{id:e.id,name:e.name,display_name:e.display_name,strategy:e.strategy,options:n,response_type:e.response_type,response_mode:e.response_mode,is_domain_connection:e.is_domain_connection,show_as_button:e.show_as_button,metadata:e.metadata}},preserveOnUpdate:(e,n)=>{const r=e.options||{};return{...n,options:{...n.options,client_id:r.client_id,client_secret:r.client_secret,app_secret:r.app_secret,kid:r.kid,team_id:r.team_id,twilio_sid:r.twilio_sid,twilio_token:r.twilio_token}}}});function ae(t){const{sync:e={},filters:n={}}=t,r=e.resourceServers??!0,a=e.roles??!0,c=e.connections??!0,s=r?K(t,k,n.resourceServers):void 0,i=a?K(t,E,n.roles):void 0,p=c?K(t,H,n.connections):void 0,o=r?L(t,k,n.resourceServers):void 0,l=a?L(t,E,n.roles):void 0,u=c?L(t,H,n.connections):void 0,d=a?{async afterCreate(w,g){var h;if(g.id!==t.controlPlaneTenantId){await((h=l==null?void 0:l.afterCreate)==null?void 0:h.call(l,w,g));try{const v=await t.getControlPlaneAdapters(),_=await t.getAdapters(g.id),T=await P.fetchAll(b=>v.roles.list(t.controlPlaneTenantId,b),"roles",{cursorField:"id",pageSize:100}),M=new Map;for(const b of T.filter(y=>{var A;return((A=n.roles)==null?void 0:A.call(n,y))??!0})){const y=await m(_,g.id,b.name);y&&M.set(b.name,y.id)}for(const b of T.filter(y=>{var A;return((A=n.roles)==null?void 0:A.call(n,y))??!0})){const y=M.get(b.name);if(y)try{const A=await v.rolePermissions.list(t.controlPlaneTenantId,b.id,{});A.length>0&&await _.rolePermissions.assign(g.id,y,A.map(O=>({role_id:y,resource_server_identifier:O.resource_server_identifier,permission_name:O.permission_name})))}catch(A){console.error(`Failed to sync permissions for role "${b.name}" to tenant "${g.id}":`,A)}}}catch(v){console.error(`Failed to sync role permissions to tenant "${g.id}":`,v)}}}}:void 0;async function m(w,g,h){return(await w.roles.list(g,{q:`name:${h}`,per_page:1})).roles[0]??null}return{entityHooks:{resourceServers:s,roles:i,connections:p},tenantHooks:{async afterCreate(w,g){const h=[o==null?void 0:o.afterCreate,(d==null?void 0:d.afterCreate)??(l==null?void 0:l.afterCreate),u==null?void 0:u.afterCreate],v=[];for(const _ of h)if(_)try{await _(w,g)}catch(T){v.push(T instanceof Error?T:new Error(String(T)))}if(v.length===1)throw v[0];if(v.length>1)throw new AggregateError(v,v.map(_=>_.message).join("; "))}}}}function N(t,e){const n=new I.OpenAPIHono;return n.openapi(I.createRoute({tags:["tenants"],method:"get",path:"/",request:{query:D.auth0QuerySchema},security:[{Bearer:[]}],responses:{200:{content:{"application/json":{schema:I.z.object({tenants:I.z.array(D.tenantSchema),start:I.z.number().optional(),limit:I.z.number().optional(),length:I.z.number().optional()})}},description:"List of tenants"}}}),async r=>{var m,f,w,g;const a=r.req.valid("query"),{page:c,per_page:s,include_totals:i,q:p}=a,o=r.var.user,l=(o==null?void 0:o.permissions)||[];if(l.includes("auth:read")||l.includes("admin:organizations")){const h=await r.env.data.tenants.list({page:c,per_page:s,include_totals:i,q:p});return i?r.json({tenants:h.tenants,start:((m=h.totals)==null?void 0:m.start)??0,limit:((f=h.totals)==null?void 0:f.limit)??s,length:h.tenants.length}):r.json({tenants:h.tenants})}if(t.accessControl&&(o!=null&&o.sub)){const h=t.accessControl.controlPlaneTenantId,_=(await P.fetchAll(R=>r.env.data.userOrganizations.listUserOrganizations(h,o.sub,R),"organizations")).map(R=>R.name);if(_.length===0)return i?r.json({tenants:[],start:0,limit:s??50,length:0}):r.json({tenants:[]});const T=_.length,M=c??0,b=s??50,y=M*b,A=_.slice(y,y+b);if(A.length===0)return i?r.json({tenants:[],start:y,limit:b,length:T}):r.json({tenants:[]});const O=A.map(R=>`id:${R}`).join(" OR "),j=p?`(${O}) AND (${p})`:O,q=await r.env.data.tenants.list({q:j,per_page:b,include_totals:!1});return i?r.json({tenants:q.tenants,start:y,limit:b,length:T}):r.json({tenants:q.tenants})}const d=await r.env.data.tenants.list({page:c,per_page:s,include_totals:i,q:p});return i?r.json({tenants:d.tenants,start:((w=d.totals)==null?void 0:w.start)??0,limit:((g=d.totals)==null?void 0:g.limit)??s,length:d.tenants.length}):r.json({tenants:d.tenants})}),n.openapi(I.createRoute({tags:["tenants"],method:"post",path:"/",request:{body:{content:{"application/json":{schema:D.tenantInsertSchema}}}},security:[{Bearer:[]}],responses:{201:{content:{"application/json":{schema:D.tenantSchema}},description:"Tenant created"},400:{description:"Validation error"},409:{description:"Tenant with this ID already exists"}}}),async r=>{var p,o;const a=r.var.user;if(!(a!=null&&a.sub))throw new S(401,{message:"Authentication required to create tenants"});let c=r.req.valid("json");const s={adapters:r.env.data,ctx:r};(p=e.tenants)!=null&&p.beforeCreate&&(c=await e.tenants.beforeCreate(s,c));const i=await r.env.data.tenants.create(c);return(o=e.tenants)!=null&&o.afterCreate&&await e.tenants.afterCreate(s,i),r.json(i,201)}),n.openapi(I.createRoute({tags:["tenants"],method:"delete",path:"/{id}",request:{params:I.z.object({id:I.z.string()})},security:[{Bearer:["delete:tenants"]}],responses:{204:{description:"Tenant deleted"},403:{description:"Access denied or cannot delete the control plane"},404:{description:"Tenant not found"}}}),async r=>{var i,p;const{id:a}=r.req.valid("param");if(t.accessControl){const o=r.var.user,l=t.accessControl.controlPlaneTenantId;if(!(o!=null&&o.sub))throw new S(401,{message:"Authentication required"});if(a===l)throw new S(403,{message:"Cannot delete the control plane"});if(!(await P.fetchAll(m=>r.env.data.userOrganizations.listUserOrganizations(l,o.sub,m),"organizations")).some(m=>m.name===a))throw new S(403,{message:"Access denied to this tenant"})}if(!await r.env.data.tenants.get(a))throw new S(404,{message:"Tenant not found"});const s={adapters:r.env.data,ctx:r};return(i=e.tenants)!=null&&i.beforeDelete&&await e.tenants.beforeDelete(s,a),await r.env.data.tenants.remove(a),(p=e.tenants)!=null&&p.afterDelete&&await e.tenants.afterDelete(s,a),r.body(null,204)}),n}function ve(t){const e=[{pattern:/\/api\/v2\/resource-servers\/([^/]+)$/,type:"resource_server"},{pattern:/\/api\/v2\/roles\/([^/]+)$/,type:"role"},{pattern:/\/api\/v2\/connections\/([^/]+)$/,type:"connection"}];for(const{pattern:n,type:r}of e){const a=t.match(n);if(a&&a[1])return{type:r,id:a[1]}}return null}async function _e(t,e,n){try{switch(n.type){case"resource_server":{const r=await t.resourceServers.get(e,n.id);return(r==null?void 0:r.is_system)===!0}case"role":{const r=await t.roles.get(e,n.id);return(r==null?void 0:r.is_system)===!0}case"connection":{const r=await t.connections.get(e,n.id);return(r==null?void 0:r.is_system)===!0}default:return!1}}catch{return!1}}function ye(t){return{resource_server:"resource server",role:"role",connection:"connection"}[t]}function se(){return async(t,e)=>{if(!["PATCH","PUT","DELETE"].includes(t.req.method))return e();const n=ve(t.req.path);if(!n)return e();const r=t.var.tenant_id||t.req.header("x-tenant-id")||t.req.header("tenant-id");if(!r)return e();if(await _e(t.env.data,r,n))throw new S(403,{message:`This ${ye(n.type)} is a system resource and cannot be modified. Make changes in the control plane instead.`});return e()}}function V(t,e){const{controlPlaneTenantId:n,controlPlaneClientId:r}=e;return{...t,legacyClients:{...t.legacyClients,get:async a=>{var u;const c=await t.legacyClients.get(a);if(!c)return null;const s=r?await t.legacyClients.get(r):void 0,i=await t.connections.list(c.tenant.id),p=n?await t.connections.list(n):{connections:[]},o=i.connections.map(d=>{var w;const m=(w=p.connections)==null?void 0:w.find(g=>g.name===d.name);if(!(m!=null&&m.options))return d;const f=D.connectionSchema.parse({...m||{},...d});return f.options=D.connectionOptionsSchema.parse({...m.options||{},...d.options}),f}).filter(d=>d),l={...(s==null?void 0:s.tenant)||{},...c.tenant};return!c.tenant.audience&&((u=s==null?void 0:s.tenant)!=null&&u.audience)&&(l.audience=s.tenant.audience),{...c,web_origins:[...(s==null?void 0:s.web_origins)||[],...c.web_origins||[]],allowed_logout_urls:[...(s==null?void 0:s.allowed_logout_urls)||[],...c.allowed_logout_urls||[]],callbacks:[...(s==null?void 0:s.callbacks)||[],...c.callbacks||[]],connections:o,tenant:l}}},connections:{...t.connections,get:async(a,c)=>{const s=await t.connections.get(a,c);if(!s||!n)return s;const i=await t.connections.get(n,c);if(!i)return s;const p=D.connectionSchema.parse({...i,...s});return p.options=D.connectionOptionsSchema.parse({...i.options||{},...s.options}),p},list:async(a,c)=>{const s=await t.connections.list(a,c);if(!n||a===n)return s;const i=await t.connections.list(n),p=s.connections.map(o=>{var d;const l=(d=i.connections)==null?void 0:d.find(m=>m.name===o.name);if(!(l!=null&&l.options))return o;const u=D.connectionSchema.parse({...l,...o});return u.options=D.connectionOptionsSchema.parse({...l.options||{},...o.options}),u});return{...s,connections:p}}}}}function oe(t,e){return V(t,e)}const be=V,Ae=oe;function ie(t){return async(e,n)=>{if(!t.accessControl)return n();const r=e.var.tenant_id,a=e.var.organization_id;if(!r)throw new S(400,{message:"Tenant ID not found in request"});if(!te(a,r,t.accessControl.controlPlaneTenantId))throw new S(403,{message:`Access denied to tenant ${r}`});return n()}}function ce(t){return async(e,n)=>{if(!t.subdomainRouting)return n();const{baseDomain:r,reservedSubdomains:a=[],resolveSubdomain:c}=t.subdomainRouting,s=e.req.header("host")||"";let i=null;if(s.endsWith(r)){const o=s.slice(0,-(r.length+1));o&&!o.includes(".")&&(i=o)}if(i&&a.includes(i)&&(i=null),!i)return t.accessControl&&e.set("tenant_id",t.accessControl.controlPlaneTenantId),n();let p=null;if(c)p=await c(i);else if(t.subdomainRouting.useOrganizations!==!1&&t.accessControl)try{const o=await e.env.data.organizations.get(t.accessControl.controlPlaneTenantId,i);o&&(p=o.id)}catch{}if(!p)throw new S(404,{message:`Tenant not found for subdomain: ${i}`});return e.set("tenant_id",p),n()}}function le(t){return async(e,n)=>{if(!t.databaseIsolation)return n();const r=e.var.tenant_id;if(!r)throw new S(400,{message:"Tenant ID not found in request"});try{const a=await t.databaseIsolation.getAdapters(r);e.env.data=a}catch(a){throw console.error(`Failed to resolve database for tenant ${r}:`,a),new S(500,{message:"Failed to resolve tenant database"})}return n()}}function W(t){const e=ce(t),n=ie(t),r=le(t);return async(a,c)=>(await e(a,async()=>{}),await n(a,async()=>{}),await r(a,async()=>{}),c())}function Te(t){const e=F(t);return{name:"multi-tenancy",middleware:W(t),hooks:e,routes:[{path:"/management",handler:N(t,e)}],onRegister:async()=>{console.log("Multi-tenancy plugin registered"),t.accessControl&&console.log(` - Access control enabled (control plane: ${t.accessControl.controlPlaneTenantId})`),t.subdomainRouting&&console.log(` - Subdomain routing enabled (base domain: ${t.subdomainRouting.baseDomain})`),t.databaseIsolation&&console.log(" - Database isolation enabled")}}}function F(t){const e=t.accessControl?ee(t.accessControl):{},n=t.databaseIsolation?ne(t.databaseIsolation):{},r=re(t);return{...e,...n,tenants:r}}function de(t){const e=new x.Hono,n=F(t);return e.route("/tenants",N(t,n)),e}function Ce(t){return{hooks:F(t),middleware:W(t),app:de(t),config:t}}function Se(t){var T,M,b,y,A,O,j,q,R,Q,J,X,Y,Z;const{controlPlaneTenantId:e="control_plane",sync:n,multiTenancy:r,entityHooks:a,...c}=t,s={...r,accessControl:{controlPlaneTenantId:e,requireOrganizationMatch:!1,defaultPermissions:["tenant:admin"],...r==null?void 0:r.accessControl}},i=F(s),p=((T=r==null?void 0:r.databaseIsolation)==null?void 0:T.getAdapters)??(async()=>t.dataAdapter),{entityHooks:o,tenantHooks:l}=ae({controlPlaneTenantId:e,getChildTenantIds:async()=>(await P.fetchAll(C=>t.dataAdapter.tenants.list(C),"tenants",{cursorField:"id",pageSize:100})).filter(C=>C.id!==e).map(C=>C.id),getAdapters:p,getControlPlaneAdapters:async()=>p(e),sync:n});function u(z,C){if(!(!z&&!C))return z?C?async(...ue)=>{const $=[];for(const U of[z,C])try{await U(...ue)}catch(B){$.push(B instanceof Error?B:new Error(String(B)))}if($.length===1)throw $[0];if($.length>1)throw new AggregateError($,$.map(U=>U.message).join("; "))}:z:C}const d={...a,resourceServers:o!=null&&o.resourceServers?{...a==null?void 0:a.resourceServers,afterCreate:u((M=a==null?void 0:a.resourceServers)==null?void 0:M.afterCreate,o.resourceServers.afterCreate),afterUpdate:u((b=a==null?void 0:a.resourceServers)==null?void 0:b.afterUpdate,o.resourceServers.afterUpdate),beforeDelete:u((y=a==null?void 0:a.resourceServers)==null?void 0:y.beforeDelete,o.resourceServers.beforeDelete),afterDelete:u((A=a==null?void 0:a.resourceServers)==null?void 0:A.afterDelete,o.resourceServers.afterDelete)}:a==null?void 0:a.resourceServers,roles:o!=null&&o.roles?{...a==null?void 0:a.roles,afterCreate:u((O=a==null?void 0:a.roles)==null?void 0:O.afterCreate,o.roles.afterCreate),afterUpdate:u((j=a==null?void 0:a.roles)==null?void 0:j.afterUpdate,o.roles.afterUpdate),beforeDelete:u((q=a==null?void 0:a.roles)==null?void 0:q.beforeDelete,o.roles.beforeDelete),afterDelete:u((R=a==null?void 0:a.roles)==null?void 0:R.afterDelete,o.roles.afterDelete)}:a==null?void 0:a.roles,connections:o!=null&&o.connections?{...a==null?void 0:a.connections,afterCreate:u((Q=a==null?void 0:a.connections)==null?void 0:Q.afterCreate,o.connections.afterCreate),afterUpdate:u((J=a==null?void 0:a.connections)==null?void 0:J.afterUpdate,o.connections.afterUpdate),beforeDelete:u((X=a==null?void 0:a.connections)==null?void 0:X.beforeDelete,o.connections.beforeDelete),afterDelete:u((Y=a==null?void 0:a.connections)==null?void 0:Y.afterDelete,o.connections.afterDelete)}:a==null?void 0:a.connections,tenants:a==null?void 0:a.tenants},m={...i,tenants:l?{...i.tenants,afterCreate:u((Z=i.tenants)==null?void 0:Z.afterCreate,l.afterCreate)}:i.tenants},f=N(s,m),w=P.init({...c,entityHooks:d,managementApiExtensions:[...c.managementApiExtensions||[],{path:"/tenants",router:f}]}),{app:g,managementApp:h,...v}=w,_=new x.Hono;return _.onError((z,C)=>z instanceof S?z.getResponse():(console.error(z),C.json({message:"Internal Server Error"},500))),_.use("/api/v2/*",se()),_.route("/",g),{app:_,managementApp:h,...v,multiTenancyConfig:s,multiTenancyHooks:i}}exports.createAccessControlHooks=ee;exports.createAccessControlMiddleware=ie;exports.createDatabaseHooks=ne;exports.createDatabaseMiddleware=le;exports.createMultiTenancy=de;exports.createMultiTenancyHooks=F;exports.createMultiTenancyMiddleware=W;exports.createMultiTenancyPlugin=Te;exports.createProtectSyncedMiddleware=se;exports.createProvisioningHooks=re;exports.createRuntimeFallbackAdapter=V;exports.createSettingsInheritanceAdapter=be;exports.createSubdomainMiddleware=ce;exports.createSyncHooks=ae;exports.createTenantsOpenAPIRouter=N;exports.init=Se;exports.setupMultiTenancy=Ce;exports.validateTenantAccess=te;exports.withRuntimeFallback=oe;exports.withSettingsInheritance=Ae;Object.keys(P).forEach(t=>{t!=="default"&&!Object.prototype.hasOwnProperty.call(exports,t)&&Object.defineProperty(exports,t,{enumerable:!0,get:()=>P[t]})});
|
|
1
|
+
"use strict";var Z=Object.defineProperty;var x=(t,e,n)=>e in t?Z(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var k=(t,e,n)=>x(t,typeof e!="symbol"?e+"":e,n);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const ee=require("hono"),P=require("authhero"),S=require("@hono/zod-openapi"),I=require("@authhero/adapter-interfaces");function B(t){const{controlPlaneTenantId:e,requireOrganizationMatch:n=!0}=t;return{async onTenantAccessValidation(s,a){if(a===e)return!0;if(n){const o=s.var.org_name,r=s.var.organization_id,i=o||r;return i?i===a:!1}return!0}}}function G(t,e,n,s){if(e===n)return!0;const a=s||t;return a?a===e:!1}function K(t){return{async resolveDataAdapters(e){try{return await t.getAdapters(e)}catch(n){console.error(`Failed to resolve data adapters for tenant ${e}:`,n);return}}}}function L(t){return{async beforeCreate(e,n){return!n.audience&&n.id?{...n,audience:P.getTenantAudience(n.id)}:n},async afterCreate(e,n){const{accessControl:s,databaseIsolation:a}=t;s&&e.ctx&&await te(e,n,s),a!=null&&a.onProvision&&await a.onProvision(n.id)},async beforeDelete(e,n){const{accessControl:s,databaseIsolation:a}=t;if(s)try{const r=(await e.adapters.organizations.list(s.controlPlaneTenantId)).organizations.find(i=>i.name===n);r&&await e.adapters.organizations.remove(s.controlPlaneTenantId,r.id)}catch(o){console.warn(`Failed to remove organization for tenant ${n}:`,o)}if(a!=null&&a.onDeprovision)try{await a.onDeprovision(n)}catch(o){console.warn(`Failed to deprovision database for tenant ${n}:`,o)}}}}async function te(t,e,n){const{controlPlaneTenantId:s,defaultPermissions:a,defaultRoles:o,issuer:r,adminRoleName:i="Tenant Admin",adminRoleDescription:m="Full access to all tenant management operations",addCreatorToOrganization:d=!0}=n,c=await t.adapters.organizations.create(s,{name:e.id,display_name:e.friendly_name||e.id});let p;if(r&&(p=await se(t,s,i,m)),d&&t.ctx){const l=t.ctx.var.user;if(l!=null&&l.sub&&!await ne(t,s,l.sub))try{await t.adapters.userOrganizations.create(s,{user_id:l.sub,organization_id:c.id}),p&&await t.adapters.userRoles.create(s,l.sub,p,c.id)}catch(f){console.warn(`Failed to add creator ${l.sub} to organization ${c.id}:`,f)}}o&&o.length>0&&console.log(`Would assign roles ${o.join(", ")} to organization ${c.id}`),a&&a.length>0&&console.log(`Would grant permissions ${a.join(", ")} to organization ${c.id}`)}async function ne(t,e,n){const s=await t.adapters.userRoles.list(e,n,void 0,"");for(const a of s)if((await t.adapters.rolePermissions.list(e,a.id,{per_page:1e3})).some(i=>i.permission_name==="admin:organizations"))return!0;return!1}async function se(t,e,n,s){const o=(await t.adapters.roles.list(e,{})).roles.find(d=>d.name===n);if(o)return o.id;const r=await t.adapters.roles.create(e,{name:n,description:s}),i=P.MANAGEMENT_API_AUDIENCE,m=P.MANAGEMENT_API_SCOPES.map(d=>({role_id:r.id,resource_server_identifier:i,permission_name:d.value}));return await t.adapters.rolePermissions.assign(e,r.id,m),r.id}const ae=["client_id","client_secret","app_secret","kid","team_id","twilio_sid","twilio_token"];function R(t,e,n=()=>!0){const{controlPlaneTenantId:s,getChildTenantIds:a,getAdapters:o}=t,r=new Map;async function i(c,p,l){return(await e(c).list(p,{q:`name:${l}`,per_page:1}))[0]??null}async function m(c){const p=await a(),l=e(await o(s));await Promise.all(p.map(async u=>{try{const f=await o(u),g=e(f),h={...l.transform(c),is_system:!0},y=await i(f,u,c.name),v=y?g.getId(y):void 0;if(y&&v){const A=g.preserveOnUpdate?g.preserveOnUpdate(y,h):h;await g.update(u,v,A)}else await g.create(u,h)}catch(f){console.error(`Failed to sync ${l.listKey} "${c.name}" to tenant "${u}":`,f)}}))}async function d(c){const p=await a();await Promise.all(p.map(async l=>{try{const u=await o(l),f=e(u),g=await i(u,l,c),w=g?f.getId(g):void 0;g&&w&&await f.remove(l,w)}catch(u){console.error(`Failed to delete entity "${c}" from tenant "${l}":`,u)}}))}return{afterCreate:async(c,p)=>{c.tenantId===s&&n(p)&&await m(p)},afterUpdate:async(c,p,l)=>{c.tenantId===s&&n(l)&&await m(l)},beforeDelete:async(c,p)=>{if(c.tenantId!==s)return;const u=await e(c.adapters).get(c.tenantId,p);u&&n(u)&&r.set(p,u)},afterDelete:async(c,p)=>{if(c.tenantId!==s)return;const l=r.get(p);l&&(r.delete(p),await d(l.name))}}}function q(t,e,n=()=>!0){const{controlPlaneTenantId:s,getControlPlaneAdapters:a,getAdapters:o}=t;return{async afterCreate(r,i){if(i.id!==s)try{const m=await a(),d=await o(i.id),c=e(m),p=e(d),l=await P.fetchAll(u=>c.listPaginated(s,u),c.listKey,{cursorField:"id",pageSize:100});await Promise.all(l.filter(u=>n(u)).map(async u=>{try{const f=c.transform(u);await p.create(i.id,{...f,is_system:!0})}catch(f){console.error(`Failed to sync entity to new tenant "${i.id}":`,f)}}))}catch(m){console.error(`Failed to sync entities to new tenant "${i.id}":`,m)}}}}const H=t=>({list:async(e,n)=>(await t.resourceServers.list(e,n)).resource_servers,listPaginated:(e,n)=>t.resourceServers.list(e,n),get:(e,n)=>t.resourceServers.get(e,n),create:(e,n)=>t.resourceServers.create(e,n),update:(e,n,s)=>t.resourceServers.update(e,n,s),remove:(e,n)=>t.resourceServers.remove(e,n),listKey:"resource_servers",getId:e=>e.id,transform:e=>({id:e.id,name:e.name,identifier:e.identifier,scopes:e.scopes,signing_alg:e.signing_alg,token_lifetime:e.token_lifetime,token_lifetime_for_web:e.token_lifetime_for_web})}),N=t=>({list:async(e,n)=>(await t.roles.list(e,n)).roles,listPaginated:(e,n)=>t.roles.list(e,n),get:(e,n)=>t.roles.get(e,n),create:(e,n)=>t.roles.create(e,n),update:(e,n,s)=>t.roles.update(e,n,s),remove:(e,n)=>t.roles.remove(e,n),listKey:"roles",getId:e=>e.id,transform:e=>({id:e.id,name:e.name,description:e.description})}),U=t=>({list:async(e,n)=>(await t.connections.list(e,n)).connections,listPaginated:(e,n)=>t.connections.list(e,n),get:(e,n)=>t.connections.get(e,n),create:(e,n)=>t.connections.create(e,n),update:(e,n,s)=>t.connections.update(e,n,s),remove:(e,n)=>t.connections.remove(e,n),listKey:"connections",getId:e=>e.id,transform:e=>{const n=e.options?{...e.options}:{};for(const s of ae)delete n[s];return{id:e.id,name:e.name,display_name:e.display_name,strategy:e.strategy,options:n,response_type:e.response_type,response_mode:e.response_mode,is_domain_connection:e.is_domain_connection,show_as_button:e.show_as_button,metadata:e.metadata}},preserveOnUpdate:(e,n)=>{const s=e.options||{};return{...n,options:{...n.options,client_id:s.client_id,client_secret:s.client_secret,app_secret:s.app_secret,kid:s.kid,team_id:s.team_id,twilio_sid:s.twilio_sid,twilio_token:s.twilio_token}}}});function re(t){const{sync:e={},filters:n={}}=t,s=e.resourceServers??!0,a=e.roles??!0,o=e.connections??!0,r=s?R(t,H,n.resourceServers):void 0,i=a?R(t,N,n.roles):void 0,m=o?R(t,U,n.connections):void 0,d=s?q(t,H,n.resourceServers):void 0,c=a?q(t,N,n.roles):void 0,p=o?q(t,U,n.connections):void 0,l=a?{async afterCreate(g,w){var h;if(w.id!==t.controlPlaneTenantId){await((h=c==null?void 0:c.afterCreate)==null?void 0:h.call(c,g,w));try{const y=await t.getControlPlaneAdapters(),v=await t.getAdapters(w.id),A=await P.fetchAll(b=>y.roles.list(t.controlPlaneTenantId,b),"roles",{cursorField:"id",pageSize:100}),M=new Map;for(const b of A.filter(_=>{var T;return((T=n.roles)==null?void 0:T.call(n,_))??!0})){const _=await u(v,w.id,b.name);_&&M.set(b.name,_.id)}for(const b of A.filter(_=>{var T;return((T=n.roles)==null?void 0:T.call(n,_))??!0})){const _=M.get(b.name);if(_)try{const T=await y.rolePermissions.list(t.controlPlaneTenantId,b.id,{});T.length>0&&await v.rolePermissions.assign(w.id,_,T.map(z=>({role_id:_,resource_server_identifier:z.resource_server_identifier,permission_name:z.permission_name})))}catch(T){console.error(`Failed to sync permissions for role "${b.name}" to tenant "${w.id}":`,T)}}}catch(y){console.error(`Failed to sync role permissions to tenant "${w.id}":`,y)}}}}:void 0;async function u(g,w,h){return(await g.roles.list(w,{q:`name:${h}`,per_page:1})).roles[0]??null}return{entityHooks:{resourceServers:r,roles:i,connections:m},tenantHooks:{async afterCreate(g,w){const h=[d==null?void 0:d.afterCreate,(l==null?void 0:l.afterCreate)??(c==null?void 0:c.afterCreate),p==null?void 0:p.afterCreate],y=[];for(const v of h)if(v)try{await v(g,w)}catch(A){y.push(A instanceof Error?A:new Error(String(A)))}if(y.length===1)throw y[0];if(y.length>1)throw new AggregateError(y,y.map(v=>v.message).join("; "))}}}}var C=class extends Error{constructor(e=500,n){super(n==null?void 0:n.message,{cause:n==null?void 0:n.cause});k(this,"res");k(this,"status");this.res=n==null?void 0:n.res,this.status=e}getResponse(){return this.res?new Response(this.res.body,{status:this.status,headers:this.res.headers}):new Response(this.message,{status:this.status})}};function D(t,e){const n=new S.OpenAPIHono;return n.openapi(S.createRoute({tags:["tenants"],method:"get",path:"/",request:{query:I.auth0QuerySchema},security:[{Bearer:[]}],responses:{200:{content:{"application/json":{schema:S.z.object({tenants:S.z.array(I.tenantSchema),start:S.z.number().optional(),limit:S.z.number().optional(),length:S.z.number().optional()})}},description:"List of tenants"}}}),async s=>{var u,f,g,w;const a=s.req.valid("query"),{page:o,per_page:r,include_totals:i,q:m}=a,d=s.var.user,c=(d==null?void 0:d.permissions)||[];if(c.includes("auth:read")||c.includes("admin:organizations")){const h=await s.env.data.tenants.list({page:o,per_page:r,include_totals:i,q:m});return i?s.json({tenants:h.tenants,start:((u=h.totals)==null?void 0:u.start)??0,limit:((f=h.totals)==null?void 0:f.limit)??r,length:h.tenants.length}):s.json({tenants:h.tenants})}if(t.accessControl&&(d!=null&&d.sub)){const h=t.accessControl.controlPlaneTenantId,v=(await P.fetchAll(O=>s.env.data.userOrganizations.listUserOrganizations(h,d.sub,O),"organizations")).map(O=>O.name);if(v.length===0)return i?s.json({tenants:[],start:0,limit:r??50,length:0}):s.json({tenants:[]});const A=v.length,M=o??0,b=r??50,_=M*b,T=v.slice(_,_+b);if(T.length===0)return i?s.json({tenants:[],start:_,limit:b,length:A}):s.json({tenants:[]});const z=T.map(O=>`id:${O}`).join(" OR "),Y=m?`(${z}) AND (${m})`:z,j=await s.env.data.tenants.list({q:Y,per_page:b,include_totals:!1});return i?s.json({tenants:j.tenants,start:_,limit:b,length:A}):s.json({tenants:j.tenants})}const l=await s.env.data.tenants.list({page:o,per_page:r,include_totals:i,q:m});return i?s.json({tenants:l.tenants,start:((g=l.totals)==null?void 0:g.start)??0,limit:((w=l.totals)==null?void 0:w.limit)??r,length:l.tenants.length}):s.json({tenants:l.tenants})}),n.openapi(S.createRoute({tags:["tenants"],method:"post",path:"/",request:{body:{content:{"application/json":{schema:I.tenantInsertSchema}}}},security:[{Bearer:[]}],responses:{201:{content:{"application/json":{schema:I.tenantSchema}},description:"Tenant created"},400:{description:"Validation error"},409:{description:"Tenant with this ID already exists"}}}),async s=>{var m,d;const a=s.var.user;if(!(a!=null&&a.sub))throw new C(401,{message:"Authentication required to create tenants"});let o=s.req.valid("json");const r={adapters:s.env.data,ctx:s};(m=e.tenants)!=null&&m.beforeCreate&&(o=await e.tenants.beforeCreate(r,o));const i=await s.env.data.tenants.create(o);return(d=e.tenants)!=null&&d.afterCreate&&await e.tenants.afterCreate(r,i),s.json(i,201)}),n.openapi(S.createRoute({tags:["tenants"],method:"delete",path:"/{id}",request:{params:S.z.object({id:S.z.string()})},security:[{Bearer:["delete:tenants"]}],responses:{204:{description:"Tenant deleted"},403:{description:"Access denied or cannot delete the control plane"},404:{description:"Tenant not found"}}}),async s=>{var i,m;const{id:a}=s.req.valid("param");if(t.accessControl){const d=s.var.user,c=t.accessControl.controlPlaneTenantId;if(!(d!=null&&d.sub))throw new C(401,{message:"Authentication required"});if(a===c)throw new C(403,{message:"Cannot delete the control plane"});if(!(await P.fetchAll(u=>s.env.data.userOrganizations.listUserOrganizations(c,d.sub,u),"organizations")).some(u=>u.name===a))throw new C(403,{message:"Access denied to this tenant"})}if(!await s.env.data.tenants.get(a))throw new C(404,{message:"Tenant not found"});const r={adapters:s.env.data,ctx:s};return(i=e.tenants)!=null&&i.beforeDelete&&await e.tenants.beforeDelete(r,a),await s.env.data.tenants.remove(a),(m=e.tenants)!=null&&m.afterDelete&&await e.tenants.afterDelete(r,a),s.body(null,204)}),n}function oe(t){const e=[{pattern:/\/api\/v2\/resource-servers\/([^/]+)$/,type:"resource_server"},{pattern:/\/api\/v2\/roles\/([^/]+)$/,type:"role"},{pattern:/\/api\/v2\/connections\/([^/]+)$/,type:"connection"}];for(const{pattern:n,type:s}of e){const a=t.match(n);if(a&&a[1])return{type:s,id:a[1]}}return null}async function ie(t,e,n){try{switch(n.type){case"resource_server":{const s=await t.resourceServers.get(e,n.id);return(s==null?void 0:s.is_system)===!0}case"role":{const s=await t.roles.get(e,n.id);return(s==null?void 0:s.is_system)===!0}case"connection":{const s=await t.connections.get(e,n.id);return(s==null?void 0:s.is_system)===!0}default:return!1}}catch{return!1}}function ce(t){return{resource_server:"resource server",role:"role",connection:"connection"}[t]}function le(){return async(t,e)=>{if(!["PATCH","PUT","DELETE"].includes(t.req.method))return e();const n=oe(t.req.path);if(!n)return e();const s=t.var.tenant_id||t.req.header("x-tenant-id")||t.req.header("tenant-id");if(!s)return e();if(await ie(t.env.data,s,n))throw new C(403,{message:`This ${ce(n.type)} is a system resource and cannot be modified. Make changes in the control plane instead.`});return e()}}function F(t,e){const{controlPlaneTenantId:n,controlPlaneClientId:s}=e;return{...t,legacyClients:{...t.legacyClients,get:async a=>{var p;const o=await t.legacyClients.get(a);if(!o)return null;const r=s?await t.legacyClients.get(s):void 0,i=await t.connections.list(o.tenant.id),m=n?await t.connections.list(n):{connections:[]},d=i.connections.map(l=>{var g;const u=(g=m.connections)==null?void 0:g.find(w=>w.name===l.name);if(!(u!=null&&u.options))return l;const f=I.connectionSchema.parse({...u||{},...l});return f.options=I.connectionOptionsSchema.parse({...u.options||{},...l.options}),f}).filter(l=>l),c={...(r==null?void 0:r.tenant)||{},...o.tenant};return!o.tenant.audience&&((p=r==null?void 0:r.tenant)!=null&&p.audience)&&(c.audience=r.tenant.audience),{...o,web_origins:[...(r==null?void 0:r.web_origins)||[],...o.web_origins||[]],allowed_logout_urls:[...(r==null?void 0:r.allowed_logout_urls)||[],...o.allowed_logout_urls||[]],callbacks:[...(r==null?void 0:r.callbacks)||[],...o.callbacks||[]],connections:d,tenant:c}}},connections:{...t.connections,get:async(a,o)=>{const r=await t.connections.get(a,o);if(!r||!n)return r;const i=await t.connections.get(n,o);if(!i)return r;const m=I.connectionSchema.parse({...i,...r});return m.options=I.connectionOptionsSchema.parse({...i.options||{},...r.options}),m},list:async(a,o)=>{const r=await t.connections.list(a,o);if(!n||a===n)return r;const i=await t.connections.list(n),m=r.connections.map(d=>{var l;const c=(l=i.connections)==null?void 0:l.find(u=>u.name===d.name);if(!(c!=null&&c.options))return d;const p=I.connectionSchema.parse({...c,...d});return p.options=I.connectionOptionsSchema.parse({...c.options||{},...d.options}),p});return{...r,connections:m}}}}}function V(t,e){return F(t,e)}const de=F,ue=V;function W(t){return async(e,n)=>{if(!t.accessControl)return n();const s=e.var.tenant_id,a=e.var.organization_id;if(!s)throw new C(400,{message:"Tenant ID not found in request"});if(!G(a,s,t.accessControl.controlPlaneTenantId))throw new C(403,{message:`Access denied to tenant ${s}`});return n()}}function Q(t){return async(e,n)=>{if(!t.subdomainRouting)return n();const{baseDomain:s,reservedSubdomains:a=[],resolveSubdomain:o}=t.subdomainRouting,r=e.req.header("host")||"";let i=null;if(r.endsWith(s)){const d=r.slice(0,-(s.length+1));d&&!d.includes(".")&&(i=d)}if(i&&a.includes(i)&&(i=null),!i)return t.accessControl&&e.set("tenant_id",t.accessControl.controlPlaneTenantId),n();let m=null;if(o)m=await o(i);else if(t.subdomainRouting.useOrganizations!==!1&&t.accessControl)try{const d=await e.env.data.organizations.get(t.accessControl.controlPlaneTenantId,i);d&&(m=d.id)}catch{}if(!m)throw new C(404,{message:`Tenant not found for subdomain: ${i}`});return e.set("tenant_id",m),n()}}function J(t){return async(e,n)=>{if(!t.databaseIsolation)return n();const s=e.var.tenant_id;if(!s)throw new C(400,{message:"Tenant ID not found in request"});try{const a=await t.databaseIsolation.getAdapters(s);e.env.data=a}catch(a){throw console.error(`Failed to resolve database for tenant ${s}:`,a),new C(500,{message:"Failed to resolve tenant database"})}return n()}}function E(t){const e=Q(t),n=W(t),s=J(t);return async(a,o)=>(await e(a,async()=>{}),await n(a,async()=>{}),await s(a,async()=>{}),o())}function me(t){const e=$(t);return{name:"multi-tenancy",middleware:E(t),hooks:e,routes:[{path:"/management",handler:D(t,e)}],onRegister:async()=>{console.log("Multi-tenancy plugin registered"),t.accessControl&&console.log(` - Access control enabled (control plane: ${t.accessControl.controlPlaneTenantId})`),t.subdomainRouting&&console.log(` - Subdomain routing enabled (base domain: ${t.subdomainRouting.baseDomain})`),t.databaseIsolation&&console.log(" - Database isolation enabled")}}}function $(t){const e=t.accessControl?B(t.accessControl):{},n=t.databaseIsolation?K(t.databaseIsolation):{},s=L(t);return{...e,...n,tenants:s}}function X(t){const e=new ee.Hono,n=$(t);return e.route("/tenants",D(t,n)),e}function pe(t){return{hooks:$(t),middleware:E(t),app:X(t),config:t}}exports.createAccessControlHooks=B;exports.createAccessControlMiddleware=W;exports.createDatabaseHooks=K;exports.createDatabaseMiddleware=J;exports.createMultiTenancy=X;exports.createMultiTenancyHooks=$;exports.createMultiTenancyMiddleware=E;exports.createMultiTenancyPlugin=me;exports.createProtectSyncedMiddleware=le;exports.createProvisioningHooks=L;exports.createRuntimeFallbackAdapter=F;exports.createSettingsInheritanceAdapter=de;exports.createSubdomainMiddleware=Q;exports.createSyncHooks=re;exports.createTenantsOpenAPIRouter=D;exports.setupMultiTenancy=pe;exports.validateTenantAccess=G;exports.withRuntimeFallback=V;exports.withSettingsInheritance=ue;
|