@authhero/multi-tenancy 14.23.1 → 14.24.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.
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("hono"),t=require("authhero"),n=require("@hono/zod-openapi");function r(e){let{controlPlaneTenantId:t,requireOrganizationMatch:n=!0}=e;return{async onTenantAccessValidation(e,r){if(r===t)return!0;if(n){let t=e.var.org_name,n=e.var.organization_id,i=t||n;return i?i.toLowerCase()===r.toLowerCase():!1}return!0}}}function i(e,t,n,r){if(t===n)return!0;let i=r||e;return i?i.toLowerCase()===t.toLowerCase():!1}function a(e){return{async resolveDataAdapters(t){try{return await e.getAdapters(t)}catch(e){console.error(`Failed to resolve data adapters for tenant ${t}:`,e);return}}}}function o(e){return`urn:authhero:tenant:${e.toLowerCase()}`}function s(e){return{async beforeCreate(e,t){return!t.audience&&t.id?{...t,audience:o(t.id)}:t},async afterCreate(t,n){let{accessControl:r,databaseIsolation:i}=e;r&&t.ctx&&await c(t,n,r),i?.onProvision&&await i.onProvision(n.id)},async beforeDelete(t,n){let{accessControl:r,databaseIsolation:i}=e;if(r)try{let e=(await t.adapters.organizations.list(r.controlPlaneTenantId)).organizations.find(e=>e.name===n);e&&await t.adapters.organizations.remove(r.controlPlaneTenantId,e.id)}catch(e){console.warn(`Failed to remove organization for tenant ${n}:`,e)}if(i?.onDeprovision)try{await i.onDeprovision(n)}catch(e){console.warn(`Failed to deprovision database for tenant ${n}:`,e)}}}}async function c(e,t,n){let{controlPlaneTenantId:r,defaultPermissions:i,defaultRoles:a,issuer:o,adminRoleName:s=`Tenant Admin`,adminRoleDescription:c=`Full access to all tenant management operations`,addCreatorToOrganization:d=!0}=n,f=await e.adapters.organizations.create(r,{name:t.id,display_name:t.friendly_name||t.id}),p;if(o&&(p=await u(e,r,s,c)),d&&e.ctx){let t=e.ctx.var.user;if(t?.sub&&!await l(e,r,t.sub))try{await e.adapters.userOrganizations.create(r,{user_id:t.sub,organization_id:f.id}),p&&await e.adapters.userRoles.create(r,t.sub,p,f.id)}catch(e){console.warn(`Failed to add creator ${t.sub} to organization ${f.id}:`,e)}}a&&a.length>0&&console.log(`Would assign roles ${a.join(`, `)} to organization ${f.id}`),i&&i.length>0&&console.log(`Would grant permissions ${i.join(`, `)} to organization ${f.id}`)}async function l(e,t,n){let r=await e.adapters.userRoles.list(t,n,void 0,``);for(let n of r)if((await e.adapters.rolePermissions.list(t,n.id,{per_page:1e3})).some(e=>e.permission_name===`admin:organizations`))return!0;return!1}async function u(e,n,r,i){let a=(await e.adapters.roles.list(n,{})).roles.find(e=>e.name===r);if(a)return a.id;let o=await e.adapters.roles.create(n,{name:r,description:i}),s=t.MANAGEMENT_API_AUDIENCE,c=t.MANAGEMENT_API_SCOPES.map(e=>({role_id:o.id,resource_server_identifier:s,permission_name:e.value}));return await e.adapters.rolePermissions.assign(n,o.id,c),o.id}function d(e,t,n=()=>!0){let{controlPlaneTenantId:r,getChildTenantIds:i,getAdapters:a}=e,o=new Map;async function s(e,n,r){return(await t(e).list(n,{q:`name:${r}`,per_page:1}))[0]??null}async function c(e){let n=await i(),o=t(await a(r));await Promise.all(n.map(async n=>{try{let r=await a(n),i=t(r),c={...o.transform(e),is_system:!0},l=await s(r,n,e.name),u=l?i.getId(l):void 0;if(l&&u){let e=i.preserveOnUpdate?i.preserveOnUpdate(l,c):c;await i.update(n,u,e)}else await i.create(n,c)}catch(t){console.error(`Failed to sync ${o.listKey} "${e.name}" to tenant "${n}":`,t)}}))}async function l(e){let n=await i();await Promise.all(n.map(async n=>{try{let r=await a(n),i=t(r),o=await s(r,n,e),c=o?i.getId(o):void 0;o&&c&&await i.remove(n,c)}catch(t){console.error(`Failed to delete entity "${e}" from tenant "${n}":`,t)}}))}return{afterCreate:async(e,t)=>{e.tenantId===r&&n(t)&&await c(t)},afterUpdate:async(e,t,i)=>{e.tenantId===r&&n(i)&&await c(i)},beforeDelete:async(e,i)=>{if(e.tenantId!==r)return;let a=await t(e.adapters).get(e.tenantId,i);a&&n(a)&&o.set(i,a)},afterDelete:async(e,t)=>{if(e.tenantId!==r)return;let n=o.get(t);n&&(o.delete(t),await l(n.name))}}}function f(e,n,r=()=>!0){let{controlPlaneTenantId:i,getControlPlaneAdapters:a,getAdapters:o}=e;return{async afterCreate(e,s){if(s.id!==i)try{let e=await a(),c=await o(s.id),l=n(e),u=n(c),d=await(0,t.fetchAll)(e=>l.listPaginated(i,e),l.listKey,{cursorField:`id`,pageSize:100});await Promise.all(d.filter(e=>r(e)).map(async e=>{try{let t=l.transform(e);await u.create(s.id,{...t,is_system:!0})}catch(e){console.error(`Failed to sync entity to new tenant "${s.id}":`,e)}}))}catch(e){console.error(`Failed to sync entities to new tenant "${s.id}":`,e)}}}}var p=e=>({list:async(t,n)=>(await e.resourceServers.list(t,n)).resource_servers,listPaginated:(t,n)=>e.resourceServers.list(t,n),get:(t,n)=>e.resourceServers.get(t,n),create:(t,n)=>e.resourceServers.create(t,n),update:(t,n,r)=>e.resourceServers.update(t,n,r),remove:(t,n)=>e.resourceServers.remove(t,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})}),m=e=>({list:async(t,n)=>(await e.roles.list(t,n)).roles,listPaginated:(t,n)=>e.roles.list(t,n),get:(t,n)=>e.roles.get(t,n),create:(t,n)=>e.roles.create(t,n),update:(t,n,r)=>e.roles.update(t,n,r),remove:(t,n)=>e.roles.remove(t,n),listKey:`roles`,getId:e=>e.id,transform:e=>({id:e.id,name:e.name,description:e.description})});function h(e){return e.metadata?.sync!==!1}function g(e){let{sync:n={},filters:r={}}=e,i=n.resourceServers??!0,a=n.roles??!0,o=e=>h(e)?r.resourceServers?r.resourceServers(e):!0:!1,s=e=>h(e)?r.roles?r.roles(e):!0:!1,c=i?d(e,p,o):void 0,l=a?d(e,m,s):void 0,u=i?f(e,p,o):void 0,g=a?f(e,m,s):void 0,_=a?{async afterCreate(n,i){if(i.id!==e.controlPlaneTenantId){await g?.afterCreate?.(n,i);try{let n=await e.getControlPlaneAdapters(),a=await e.getAdapters(i.id),o=await(0,t.fetchAll)(t=>n.roles.list(e.controlPlaneTenantId,t),`roles`,{cursorField:`id`,pageSize:100}),s=new Map;for(let e of o.filter(e=>r.roles?.(e)??!0)){let t=await v(a,i.id,e.name);t&&s.set(e.name,t.id)}for(let t of o.filter(e=>r.roles?.(e)??!0)){let r=s.get(t.name);if(r)try{let o=await n.rolePermissions.list(e.controlPlaneTenantId,t.id,{});o.length>0&&await a.rolePermissions.assign(i.id,r,o.map(e=>({role_id:r,resource_server_identifier:e.resource_server_identifier,permission_name:e.permission_name})))}catch(e){console.error(`Failed to sync permissions for role "${t.name}" to tenant "${i.id}":`,e)}}}catch(e){console.error(`Failed to sync role permissions to tenant "${i.id}":`,e)}}}}:void 0;async function v(e,t,n){return(await e.roles.list(t,{q:`name:${n}`,per_page:1})).roles[0]??null}return{entityHooks:{resourceServers:c,roles:l},tenantHooks:{async afterCreate(e,t){let n=[u?.afterCreate,_?.afterCreate??g?.afterCreate],r=[];for(let i of n)if(i)try{await i(e,t)}catch(e){r.push(e instanceof Error?e:Error(String(e)))}if(r.length===1)throw r[0];if(r.length>1)throw AggregateError(r,r.map(e=>e.message).join(`; `))}}}}var _=class extends Error{res;status;constructor(e=500,t){super(t?.message,{cause:t?.cause}),this.res=t?.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 v(e,r){let i=new n.OpenAPIHono;return i.openapi((0,n.createRoute)({tags:[`tenants`],method:`get`,path:`/`,request:{query:t.auth0QuerySchema},security:[{Bearer:[]}],responses:{200:{content:{"application/json":{schema:n.z.object({tenants:n.z.array(t.tenantSchema),start:n.z.number().optional(),limit:n.z.number().optional(),length:n.z.number().optional()})}},description:`List of tenants`}}}),async n=>{let{page:r,per_page:i,include_totals:a,q:o}=n.req.valid(`query`),s=n.var.user,c=s?.permissions||[];if(!(s?.org_id??n.var.organization_id)&&c.includes(`admin:organizations`)){let e=await n.env.data.tenants.list({page:r,per_page:i,include_totals:a,q:o});return a?n.json({tenants:e.tenants,start:e.totals?.start??0,limit:e.totals?.limit??i,length:e.tenants.length}):n.json({tenants:e.tenants})}let l=e.accessControl?.controlPlaneTenantId??n.env.data.multiTenancyConfig?.controlPlaneTenantId;if(l&&!s?.sub)throw new _(403,{message:`Access denied: token has no subject`});if(l&&s?.sub){let e=(await(0,t.fetchAll)(e=>n.env.data.userOrganizations.listUserOrganizations(l,s.sub,e),`organizations`)).map(e=>e.name);if(e.length===0)return a?n.json({tenants:[],start:0,limit:i??50,length:0}):n.json({tenants:[]});let c=e.length,u=r??0,d=i??50,f=u*d,p=e.slice(f,f+d);if(p.length===0)return a?n.json({tenants:[],start:f,limit:d,length:c}):n.json({tenants:[]});let m=p.map(e=>`id:${e}`).join(` OR `),h=o?`(${m}) AND (${o})`:m,g=await n.env.data.tenants.list({q:h,per_page:d,include_totals:!1});return a?n.json({tenants:g.tenants,start:f,limit:d,length:c}):n.json({tenants:g.tenants})}let u=await n.env.data.tenants.list({page:r,per_page:i,include_totals:a,q:o});return a?n.json({tenants:u.tenants,start:u.totals?.start??0,limit:u.totals?.limit??i,length:u.tenants.length}):n.json({tenants:u.tenants})}),i.openapi((0,n.createRoute)({tags:[`tenants`],method:`post`,path:`/`,request:{body:{content:{"application/json":{schema:t.tenantInsertSchema}}}},security:[{Bearer:[]}],responses:{201:{content:{"application/json":{schema:t.tenantSchema}},description:`Tenant created`},400:{description:`Validation error`},409:{description:`Tenant with this ID already exists`}}}),async e=>{if(!e.var.user?.sub)throw new _(401,{message:`Authentication required to create tenants`});let t=e.req.valid(`json`),n={adapters:e.env.data,ctx:e};r.tenants?.beforeCreate&&(t=await r.tenants.beforeCreate(n,t));let i=await e.env.data.tenants.create(t);return r.tenants?.afterCreate&&await r.tenants.afterCreate(n,i),e.json(i,201)}),i.openapi((0,n.createRoute)({tags:[`tenants`],method:`delete`,path:`/{id}`,request:{params:n.z.object({id:n.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 n=>{let{id:i}=n.req.valid(`param`),a=e.accessControl?.controlPlaneTenantId??n.env.data.multiTenancyConfig?.controlPlaneTenantId;if(a){let e=n.var.user;if(!e?.sub)throw new _(401,{message:`Authentication required`});if(i===a)throw new _(403,{message:`Cannot delete the control plane`});let r=n.var.org_name,o=i.toLowerCase(),s=!!r&&r.toLowerCase()===o;if(s||=(await(0,t.fetchAll)(t=>n.env.data.userOrganizations.listUserOrganizations(a,e.sub,t),`organizations`)).some(e=>e.name?.toLowerCase()===o),!s)throw new _(403,{message:`Access denied to this tenant`})}if(!await n.env.data.tenants.get(i))throw new _(404,{message:`Tenant not found`});let o={adapters:n.env.data,ctx:n};return r.tenants?.beforeDelete&&await r.tenants.beforeDelete(o,i),await n.env.data.tenants.remove(i),r.tenants?.afterDelete&&await r.tenants.afterDelete(o,i),n.body(null,204)}),i.openapi((0,n.createRoute)({tags:[`tenants`,`settings`],method:`get`,path:`/settings`,request:{headers:n.z.object({"tenant-id":n.z.string().optional()})},security:[{Bearer:[`read:tenants`]}],responses:{200:{content:{"application/json":{schema:t.tenantSchema}},description:`Current tenant settings`}}}),async e=>{let t=await e.env.data.tenants.get(e.var.tenant_id);if(!t)throw new _(404,{message:`Tenant not found`});return e.json(t)}),i.openapi((0,n.createRoute)({tags:[`tenants`,`settings`],method:`patch`,path:`/settings`,request:{headers:n.z.object({"tenant-id":n.z.string().optional()}),body:{content:{"application/json":{schema:n.z.object(t.tenantInsertSchema.shape).partial()}}}},security:[{Bearer:[`update:tenants`]}],responses:{200:{content:{"application/json":{schema:t.tenantSchema}},description:`Updated tenant settings`}}}),async e=>{let{id:n,...r}=e.req.valid(`json`),i=await e.env.data.tenants.get(e.var.tenant_id);if(!i)throw new _(404,{message:`Tenant not found`});let a=(0,t.deepMergePatch)(i,r);await e.env.data.tenants.update(e.var.tenant_id,a);let o=await e.env.data.tenants.get(e.var.tenant_id);if(!o)throw new _(500,{message:`Failed to retrieve updated tenant`});return e.json(o)}),i}function y(e){for(let{pattern:t,type:n}of[{pattern:/\/api\/v2\/resource-servers\/([^/]+)$/,type:`resource_server`},{pattern:/\/api\/v2\/roles\/([^/]+)$/,type:`role`},{pattern:/\/api\/v2\/connections\/([^/]+)$/,type:`connection`}]){let r=e.match(t);if(r&&r[1])return{type:n,id:r[1]}}return null}async function b(e,t,n){try{switch(n.type){case`resource_server`:return(await e.resourceServers.get(t,n.id))?.is_system===!0;case`role`:return(await e.roles.get(t,n.id))?.is_system===!0;case`connection`:return(await e.connections.get(t,n.id))?.is_system===!0;default:return!1}}catch{return!1}}function x(e){return{resource_server:`resource server`,role:`role`,connection:`connection`}[e]}function S(){return async(e,t)=>{if(![`PATCH`,`PUT`,`DELETE`].includes(e.req.method))return t();let n=y(e.req.path);if(!n)return t();let r=e.var.tenant_id||e.req.header(`x-tenant-id`)||e.req.header(`tenant-id`);if(!r)return t();if(await b(e.env.data,r,n))throw new _(403,{message:`This ${x(n.type)} is a system resource and cannot be modified. Make changes in the control plane instead.`});return t()}}function C(e,n){let r=n.find(t=>t.strategy===e.strategy);if(!r?.options)return e;let i=t.connectionSchema.passthrough().parse({...r,...e});return i.options=t.connectionOptionsSchema.passthrough().parse({...r.options||{},...e.options}),i}function w(e,t){let n=[...t||[],...e||[]];return[...new Set(n)]}function T(e,t){if(!t?.length)return e||[];if(!e?.length)return t;let n=new Map;for(let e of t)n.set(e.value,e);for(let t of e)n.set(t.value,t);return Array.from(n.values())}function E(e,t){return t?{...e,scopes:T(e.scopes,t.scopes)}:e}function D(e,t){return t?{...e,callbacks:w(e.callbacks,t.callbacks),web_origins:w(e.web_origins,t.web_origins),allowed_logout_urls:w(e.allowed_logout_urls,t.allowed_logout_urls),allowed_origins:w(e.allowed_origins,t.allowed_origins)}:e}function O(e){let{controlPlaneTenantId:t,controlPlaneClientId:n,resolveControlPlane:r}=e;if(r)return async e=>r({tenant_id:e});if(!t)return async()=>void 0;let i={tenantId:t,clientId:n};return async()=>i}function k(e,t){return{...e.resourceServers,get:async(n,r)=>{let i=await e.resourceServers.get(n,r);if(!i)return i;let a=await t(n);return!a||n===a.tenantId||!i.is_system?i:E(i,await e.resourceServers.get(a.tenantId,r))},list:async(n,r)=>{let i=await e.resourceServers.list(n,r),a=await t(n);if(!a||n===a.tenantId)return i;let o=a.tenantId,s=i.resource_servers.filter(e=>!!(e.is_system&&e.id)).map(e=>e.id);if(s.length===0)return i;let c=new Map;await Promise.all(s.map(async t=>{let n=await e.resourceServers.get(o,t);n&&c.set(t,n)}));let l=i.resource_servers.map(e=>e.is_system&&e.id?E(e,c.get(e.id)??null):e);return{...i,resource_servers:l}}}}function A(e,t){let n=O({controlPlaneTenantId:t.controlPlaneTenantId,resolveControlPlane:t.resolveControlPlane});return{...e,resourceServers:k(e,n)}}function j(e,t){let{controlPlaneTenantId:n,controlPlaneClientId:r,resolveControlPlane:i}=t,a=O({controlPlaneTenantId:n,controlPlaneClientId:r,resolveControlPlane:i});return{...e,multiTenancyConfig:{controlPlaneTenantId:n,controlPlaneClientId:r,resolveControlPlane:i},connections:{...e.connections,get:async(t,n)=>{let r=await e.connections.get(t,n);if(!r)return r;let i=await a(t);return!i||t===i.tenantId?r:C(r,(await e.connections.list(i.tenantId)).connections||[])},list:async(t,n)=>{let r=await e.connections.list(t,n),i=await a(t);if(!i||t===i.tenantId)return r;let o=await e.connections.list(i.tenantId),s=r.connections.map(e=>C(e,o.connections||[]));return{...r,connections:s}}},clientConnections:{...e.clientConnections,listByClient:async(t,n)=>{let r=await e.clientConnections.listByClient(t,n);r.length===0&&(r=(await e.connections.list(t)).connections||[]);let i=await a(t);if(!i||t===i.tenantId)return r;let o=await e.connections.list(i.tenantId);return r.map(e=>C(e,o.connections||[]))}},emailProviders:{...e.emailProviders,get:async t=>{let n=await e.emailProviders.get(t);if(n)return n;let r=await a(t);return!r||t===r.tenantId?null:e.emailProviders.get(r.tenantId)}},resourceServers:k(e,a),hooks:N(e,a)}}function M(e){if(!e||typeof e!=`object`)return!1;let t=e.metadata;return!t||typeof t!=`object`?!1:t.inheritable===!0}function N(e,t){return{...e.hooks,list:async(n,r)=>{let i=await e.hooks.list(n,r),a=await t(n);if(!a||n===a.tenantId)return i;let o=((await e.hooks.list(a.tenantId,r)).hooks||[]).filter(M);if(o.length===0)return i;let s=new Set((i.hooks||[]).map(e=>e.hook_id)),c=o.filter(e=>!s.has(e.hook_id));return{...i,hooks:[...i.hooks||[],...c],length:typeof i.length==`number`?i.length+c.length:i.length}},get:async(n,r)=>{let i=await e.hooks.get(n,r);if(i)return i;let a=await t(n);if(!a||n===a.tenantId)return i;let o=await e.hooks.get(a.tenantId,r);return o&&M(o)?o:null}}}function P(e,t){return j(e,t)}function F(e){return async(t,n)=>{let r=t.var.user;return r?.tenant_id===e&&r.org_name&&t.set(`tenant_id`,r.org_name),n()}}function I(e){return async(n,r)=>{if(!e.accessControl)return r();let{controlPlaneTenantId:a}=e.accessControl,o=n.var.org_name,s=n.var.organization_id,c=o||s,l=n.var.tenant_id,u=n.var.user,d=(u?.aud?Array.isArray(u.aud)?u.aud:[u.aud]:[]).includes(t.MANAGEMENT_API_AUDIENCE);if(!l&&c&&d&&(n.set(`tenant_id`,c),l=c),!l)throw new _(400,{message:`Tenant ID not found in request`});if(!i(s,l,a,o))throw new _(403,{message:`Access denied to tenant ${l}`});return r()}}function L(e){return async(t,n)=>{if(!e.subdomainRouting)return n();let{baseDomain:r,reservedSubdomains:i=[],resolveSubdomain:a}=e.subdomainRouting,o=t.req.header(`x-forwarded-host`)||t.req.header(`host`)||``,s=null;if(o.endsWith(r)){let e=o.slice(0,-(r.length+1));e&&!e.includes(`.`)&&(s=e)}if(s&&i.includes(s)&&(s=null),!s)return e.accessControl&&t.set(`tenant_id`,e.accessControl.controlPlaneTenantId),n();let c=null;if(a)c=await a(s);else if(e.subdomainRouting.useOrganizations!==!1&&e.accessControl)try{let n=await t.env.data.organizations.get(e.accessControl.controlPlaneTenantId,s);n&&(c=n.id)}catch{}if(!c)throw new _(404,{message:`Tenant not found for subdomain: ${s}`});return t.set(`tenant_id`,c),n()}}function R(e){return async(t,n)=>{if(!e.databaseIsolation)return n();let r=t.var.tenant_id;if(!r)throw new _(400,{message:`Tenant ID not found in request`});try{let n=await e.databaseIsolation.getAdapters(r);t.env.data=n}catch(e){throw console.error(`Failed to resolve database for tenant ${r}:`,e),new _(500,{message:`Failed to resolve tenant database`})}return n()}}function z(e){let t=L(e),n=I(e),r=R(e);return async(e,i)=>(await t(e,async()=>{}),await n(e,async()=>{}),await r(e,async()=>{}),i())}function B(e){let{dataAdapter:n,controlPlane:r,controlPlane:{tenantId:i=`control_plane`,clientId:a}={},resolveControlPlane:o,sync:c={resourceServers:!0,roles:!0},defaultPermissions:l=[`tenant:admin`],requireOrganizationMatch:u=!1,managementApiExtensions:d=[],entityHooks:f,getChildTenantIds:p,getAdapters:m,...h}=e;if(o&&!r)throw Error("initMultiTenant: `resolveControlPlane` requires `controlPlane` to be set. The static `controlPlane.tenantId` is used for access control, sync direction, and tenant management routing; the resolver only overrides per-tenant runtime inheritance lookups on top of it.");let _=n,y=n;r&&(_=P(n,{controlPlaneTenantId:i,controlPlaneClientId:a,resolveControlPlane:o}),y={...A(n,{controlPlaneTenantId:i,resolveControlPlane:o}),multiTenancyConfig:{controlPlaneTenantId:i,controlPlaneClientId:a,resolveControlPlane:o}});let b=c!==!1,x=b?{resourceServers:c.resourceServers??!0,roles:c.roles??!0}:{resourceServers:!1,roles:!1},{entityHooks:C,tenantHooks:w}=g({controlPlaneTenantId:i,getChildTenantIds:p??(async()=>(await(0,t.fetchAll)(e=>_.tenants.list(e),`tenants`,{cursorField:`id`,pageSize:100})).filter(e=>e.id!==i).map(e=>e.id)),getAdapters:m??(async()=>_),getControlPlaneAdapters:async()=>_,sync:x}),T={resourceServers:[C.resourceServers,...f?.resourceServers??[]],roles:[C.roles,...f?.roles??[]],connections:f?.connections??[],tenants:f?.tenants??[],rolePermissions:f?.rolePermissions??[]},E=s({accessControl:{controlPlaneTenantId:i,requireOrganizationMatch:u,defaultPermissions:l}}),D=v({accessControl:{controlPlaneTenantId:i,requireOrganizationMatch:u,defaultPermissions:l}},{tenants:{async beforeCreate(e,t){return E.beforeCreate&&(t=await E.beforeCreate(e,t)),w.beforeCreate&&(t=await w.beforeCreate(e,t)),t},async afterCreate(e,t){await E.afterCreate?.(e,t),await w.afterCreate?.(e,t)},async beforeDelete(e,t){await E.beforeDelete?.(e,t),await w.beforeDelete?.(e,t)}}}),{app:O}=(0,t.init)({dataAdapter:_,managementDataAdapter:y,...h,entityHooks:T,managementApiExtensions:[...d,{path:`/tenants`,router:D}]});return O.use(`/api/v2/*`,F(i)),b&&O.use(`/api/v2/*`,S()),{app:O,controlPlaneTenantId:i}}function V(e){let t=H(e);return{name:`multi-tenancy`,middleware:z(e),hooks:t,routes:[{path:`/management`,handler:v(e,t)}],onRegister:async()=>{console.log(`Multi-tenancy plugin registered`),e.accessControl&&console.log(` - Access control enabled (control plane: ${e.accessControl.controlPlaneTenantId})`),e.subdomainRouting&&console.log(` - Subdomain routing enabled (base domain: ${e.subdomainRouting.baseDomain})`),e.databaseIsolation&&console.log(` - Database isolation enabled`)}}}function H(e){let t=e.accessControl?r(e.accessControl):{},n=e.databaseIsolation?a(e.databaseIsolation):{},i=s(e);return{...t,...n,tenants:i}}function U(t){let n=new e.Hono,r=H(t);return n.route(`/tenants`,v(t,r)),n}function W(e){return{hooks:H(e),middleware:z(e),app:U(e),config:e,wrapAdapters:(t,n)=>P(t,{controlPlaneTenantId:e.accessControl?.controlPlaneTenantId,controlPlaneClientId:n?.controlPlaneClientId})}}exports.createAccessControlHooks=r,exports.createAccessControlMiddleware=I,exports.createControlPlaneTenantMiddleware=F,exports.createDatabaseHooks=a,exports.createDatabaseMiddleware=R,exports.createMultiTenancy=U,exports.createMultiTenancyHooks=H,exports.createMultiTenancyMiddleware=z,exports.createMultiTenancyPlugin=V,exports.createProtectSyncedMiddleware=S,exports.createProvisioningHooks=s,exports.createRuntimeFallbackAdapter=j,exports.createSubdomainMiddleware=L,exports.createSyncHooks=g,exports.createTenantsOpenAPIRouter=v,exports.initMultiTenant=B,exports.mergeClientWithFallback=D,exports.setupMultiTenancy=W,exports.validateTenantAccess=i,exports.withRuntimeFallback=P,exports.withSystemResourceServerInheritance=A;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("hono"),t=require("authhero"),n=require("@hono/zod-openapi");function r(e){let{controlPlaneTenantId:t,requireOrganizationMatch:n=!0}=e;return{async onTenantAccessValidation(e,r){if(r===t)return!0;if(n){let t=e.var.org_name,n=e.var.organization_id,i=t||n;return i?i.toLowerCase()===r.toLowerCase():!1}return!0}}}function i(e,t,n,r){if(t===n)return!0;let i=r||e;return i?i.toLowerCase()===t.toLowerCase():!1}function a(e){return{async resolveDataAdapters(t){try{return await e.getAdapters(t)}catch(e){console.error(`Failed to resolve data adapters for tenant ${t}:`,e);return}}}}function o(e){return`urn:authhero:tenant:${e.toLowerCase()}`}function s(e){return{async beforeCreate(e,t){return!t.audience&&t.id?{...t,audience:o(t.id)}:t},async afterCreate(t,n){let{accessControl:r,databaseIsolation:i}=e;r&&t.ctx&&await c(t,n,r),i?.onProvision&&await i.onProvision(n.id)},async beforeDelete(t,n){let{accessControl:r,databaseIsolation:i}=e;if(r)try{let e=(await t.adapters.organizations.list(r.controlPlaneTenantId)).organizations.find(e=>e.name===n);e&&await t.adapters.organizations.remove(r.controlPlaneTenantId,e.id)}catch(e){console.warn(`Failed to remove organization for tenant ${n}:`,e)}if(i?.onDeprovision)try{await i.onDeprovision(n)}catch(e){console.warn(`Failed to deprovision database for tenant ${n}:`,e)}}}}async function c(e,t,n){let{controlPlaneTenantId:r,defaultPermissions:i,defaultRoles:a,issuer:o,adminRoleName:s=`Tenant Admin`,adminRoleDescription:c=`Full access to all tenant management operations`,addCreatorToOrganization:d=!0}=n,f=await e.adapters.organizations.create(r,{name:t.id,display_name:t.friendly_name||t.id}),p;if(o&&(p=await u(e,r,s,c)),d&&e.ctx){let t=e.ctx.var.user;if(t?.sub&&!await l(e,r,t.sub))try{await e.adapters.userOrganizations.create(r,{user_id:t.sub,organization_id:f.id}),p&&await e.adapters.userRoles.create(r,t.sub,p,f.id)}catch(e){console.warn(`Failed to add creator ${t.sub} to organization ${f.id}:`,e)}}a&&a.length>0&&console.log(`Would assign roles ${a.join(`, `)} to organization ${f.id}`),i&&i.length>0&&console.log(`Would grant permissions ${i.join(`, `)} to organization ${f.id}`)}async function l(e,t,n){let r=await e.adapters.userRoles.list(t,n,void 0,``);for(let n of r)if((await e.adapters.rolePermissions.list(t,n.id,{per_page:1e3})).some(e=>e.permission_name===`admin:organizations`))return!0;return!1}async function u(e,n,r,i){let a=(await e.adapters.roles.list(n,{})).roles.find(e=>e.name===r);if(a)return a.id;let o=await e.adapters.roles.create(n,{name:r,description:i}),s=t.MANAGEMENT_API_AUDIENCE,c=t.MANAGEMENT_API_SCOPES.map(e=>({role_id:o.id,resource_server_identifier:s,permission_name:e.value}));return await e.adapters.rolePermissions.assign(n,o.id,c),o.id}function d(e,t,n=()=>!0){let{controlPlaneTenantId:r,getChildTenantIds:i,getAdapters:a}=e,o=new Map;async function s(e,n,r){return(await t(e).list(n,{q:`name:${r}`,per_page:1}))[0]??null}async function c(e){let n=await i(),o=t(await a(r));await Promise.all(n.map(async n=>{try{let r=await a(n),i=t(r),c={...o.transform(e),is_system:!0},l=await s(r,n,e.name),u=l?i.getId(l):void 0;if(l&&u){let e=i.preserveOnUpdate?i.preserveOnUpdate(l,c):c;await i.update(n,u,e)}else await i.create(n,c)}catch(t){console.error(`Failed to sync ${o.listKey} "${e.name}" to tenant "${n}":`,t)}}))}async function l(e){let n=await i();await Promise.all(n.map(async n=>{try{let r=await a(n),i=t(r),o=await s(r,n,e),c=o?i.getId(o):void 0;o&&c&&await i.remove(n,c)}catch(t){console.error(`Failed to delete entity "${e}" from tenant "${n}":`,t)}}))}return{afterCreate:async(e,t)=>{e.tenantId===r&&n(t)&&await c(t)},afterUpdate:async(e,t,i)=>{e.tenantId===r&&n(i)&&await c(i)},beforeDelete:async(e,i)=>{if(e.tenantId!==r)return;let a=await t(e.adapters).get(e.tenantId,i);a&&n(a)&&o.set(i,a)},afterDelete:async(e,t)=>{if(e.tenantId!==r)return;let n=o.get(t);n&&(o.delete(t),await l(n.name))}}}function f(e,n,r=()=>!0){let{controlPlaneTenantId:i,getControlPlaneAdapters:a,getAdapters:o}=e;return{async afterCreate(e,s){if(s.id!==i)try{let e=await a(),c=await o(s.id),l=n(e),u=n(c),d=await(0,t.fetchAll)(e=>l.listPaginated(i,e),l.listKey,{cursorField:`id`,pageSize:100});await Promise.all(d.filter(e=>r(e)).map(async e=>{try{let t=l.transform(e);await u.create(s.id,{...t,is_system:!0})}catch(e){console.error(`Failed to sync entity to new tenant "${s.id}":`,e)}}))}catch(e){console.error(`Failed to sync entities to new tenant "${s.id}":`,e)}}}}var p=e=>({list:async(t,n)=>(await e.resourceServers.list(t,n)).resource_servers,listPaginated:(t,n)=>e.resourceServers.list(t,n),get:(t,n)=>e.resourceServers.get(t,n),create:(t,n)=>e.resourceServers.create(t,n),update:(t,n,r)=>e.resourceServers.update(t,n,r),remove:(t,n)=>e.resourceServers.remove(t,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})}),m=e=>({list:async(t,n)=>(await e.roles.list(t,n)).roles,listPaginated:(t,n)=>e.roles.list(t,n),get:(t,n)=>e.roles.get(t,n),create:(t,n)=>e.roles.create(t,n),update:(t,n,r)=>e.roles.update(t,n,r),remove:(t,n)=>e.roles.remove(t,n),listKey:`roles`,getId:e=>e.id,transform:e=>({id:e.id,name:e.name,description:e.description})});function h(e){return e.metadata?.sync!==!1}function g(e){let{sync:n={},filters:r={}}=e,i=n.resourceServers??!0,a=n.roles??!0,o=e=>h(e)?r.resourceServers?r.resourceServers(e):!0:!1,s=e=>h(e)?r.roles?r.roles(e):!0:!1,c=i?d(e,p,o):void 0,l=a?d(e,m,s):void 0,u=i?f(e,p,o):void 0,g=a?f(e,m,s):void 0,_=a?{async afterCreate(n,i){if(i.id!==e.controlPlaneTenantId){await g?.afterCreate?.(n,i);try{let n=await e.getControlPlaneAdapters(),a=await e.getAdapters(i.id),o=await(0,t.fetchAll)(t=>n.roles.list(e.controlPlaneTenantId,t),`roles`,{cursorField:`id`,pageSize:100}),s=new Map;for(let e of o.filter(e=>r.roles?.(e)??!0)){let t=await v(a,i.id,e.name);t&&s.set(e.name,t.id)}for(let t of o.filter(e=>r.roles?.(e)??!0)){let r=s.get(t.name);if(r)try{let o=await n.rolePermissions.list(e.controlPlaneTenantId,t.id,{});o.length>0&&await a.rolePermissions.assign(i.id,r,o.map(e=>({role_id:r,resource_server_identifier:e.resource_server_identifier,permission_name:e.permission_name})))}catch(e){console.error(`Failed to sync permissions for role "${t.name}" to tenant "${i.id}":`,e)}}}catch(e){console.error(`Failed to sync role permissions to tenant "${i.id}":`,e)}}}}:void 0;async function v(e,t,n){return(await e.roles.list(t,{q:`name:${n}`,per_page:1})).roles[0]??null}return{entityHooks:{resourceServers:c,roles:l},tenantHooks:{async afterCreate(e,t){let n=[u?.afterCreate,_?.afterCreate??g?.afterCreate],r=[];for(let i of n)if(i)try{await i(e,t)}catch(e){r.push(e instanceof Error?e:Error(String(e)))}if(r.length===1)throw r[0];if(r.length>1)throw AggregateError(r,r.map(e=>e.message).join(`; `))}}}}var _=class extends Error{res;status;constructor(e=500,t){super(t?.message,{cause:t?.cause}),this.res=t?.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 v(e,r){let i=new n.OpenAPIHono;return i.openapi((0,n.createRoute)({tags:[`tenants`],method:`get`,path:`/`,request:{query:t.auth0QuerySchema},security:[{Bearer:[]}],responses:{200:{content:{"application/json":{schema:n.z.object({tenants:n.z.array(t.tenantSchema),start:n.z.number().optional(),limit:n.z.number().optional(),length:n.z.number().optional()})}},description:`List of tenants`}}}),async n=>{let{page:r,per_page:i,include_totals:a,q:o}=n.req.valid(`query`),s=n.var.user,c=s?.permissions||[];if(!(s?.org_id??n.var.organization_id)&&c.includes(`admin:organizations`)){let e=await n.env.data.tenants.list({page:r,per_page:i,include_totals:a,q:o});return a?n.json({tenants:e.tenants,start:e.totals?.start??0,limit:e.totals?.limit??i,length:e.tenants.length}):n.json({tenants:e.tenants})}let l=e.accessControl?.controlPlaneTenantId??n.env.data.multiTenancyConfig?.controlPlaneTenantId;if(l&&!s?.sub)throw new _(403,{message:`Access denied: token has no subject`});if(l&&s?.sub){let e=(await(0,t.fetchAll)(e=>n.env.data.userOrganizations.listUserOrganizations(l,s.sub,e),`organizations`)).map(e=>e.name);if(e.length===0)return a?n.json({tenants:[],start:0,limit:i??50,length:0}):n.json({tenants:[]});let c=e.length,u=r??0,d=i??50,f=u*d,p=e.slice(f,f+d);if(p.length===0)return a?n.json({tenants:[],start:f,limit:d,length:c}):n.json({tenants:[]});let m=p.map(e=>`id:${e}`).join(` OR `),h=o?`(${m}) AND (${o})`:m,g=await n.env.data.tenants.list({q:h,per_page:d,include_totals:!1});return a?n.json({tenants:g.tenants,start:f,limit:d,length:c}):n.json({tenants:g.tenants})}let u=await n.env.data.tenants.list({page:r,per_page:i,include_totals:a,q:o});return a?n.json({tenants:u.tenants,start:u.totals?.start??0,limit:u.totals?.limit??i,length:u.tenants.length}):n.json({tenants:u.tenants})}),i.openapi((0,n.createRoute)({tags:[`tenants`],method:`post`,path:`/`,request:{body:{content:{"application/json":{schema:t.tenantInsertSchema}}}},security:[{Bearer:[]}],responses:{201:{content:{"application/json":{schema:t.tenantSchema}},description:`Tenant created`},400:{description:`Validation error`},409:{description:`Tenant with this ID already exists`}}}),async e=>{if(!e.var.user?.sub)throw new _(401,{message:`Authentication required to create tenants`});let t=e.req.valid(`json`),n={adapters:e.env.data,ctx:e};r.tenants?.beforeCreate&&(t=await r.tenants.beforeCreate(n,t));let i=await e.env.data.tenants.create(t);return r.tenants?.afterCreate&&await r.tenants.afterCreate(n,i),e.json(i,201)}),i.openapi((0,n.createRoute)({tags:[`tenants`],method:`delete`,path:`/{id}`,request:{params:n.z.object({id:n.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 n=>{let{id:i}=n.req.valid(`param`),a=e.accessControl?.controlPlaneTenantId??n.env.data.multiTenancyConfig?.controlPlaneTenantId;if(a){let e=n.var.user;if(!e?.sub)throw new _(401,{message:`Authentication required`});if(i===a)throw new _(403,{message:`Cannot delete the control plane`});let r=n.var.org_name,o=i.toLowerCase(),s=!!r&&r.toLowerCase()===o;if(s||=(await(0,t.fetchAll)(t=>n.env.data.userOrganizations.listUserOrganizations(a,e.sub,t),`organizations`)).some(e=>e.name?.toLowerCase()===o),!s)throw new _(403,{message:`Access denied to this tenant`})}if(!await n.env.data.tenants.get(i))throw new _(404,{message:`Tenant not found`});let o={adapters:n.env.data,ctx:n};return r.tenants?.beforeDelete&&await r.tenants.beforeDelete(o,i),await n.env.data.tenants.remove(i),r.tenants?.afterDelete&&await r.tenants.afterDelete(o,i),n.body(null,204)}),i.openapi((0,n.createRoute)({tags:[`tenants`,`settings`],method:`get`,path:`/settings`,request:{headers:n.z.object({"tenant-id":n.z.string().optional()})},security:[{Bearer:[`read:tenants`]}],responses:{200:{content:{"application/json":{schema:t.tenantSchema}},description:`Current tenant settings`}}}),async e=>{let t=await e.env.data.tenants.get(e.var.tenant_id);if(!t)throw new _(404,{message:`Tenant not found`});return e.json(t)}),i.openapi((0,n.createRoute)({tags:[`tenants`,`settings`],method:`patch`,path:`/settings`,request:{headers:n.z.object({"tenant-id":n.z.string().optional()}),body:{content:{"application/json":{schema:n.z.object(t.tenantInsertSchema.shape).partial()}}}},security:[{Bearer:[`update:tenants`]}],responses:{200:{content:{"application/json":{schema:t.tenantSchema}},description:`Updated tenant settings`}}}),async e=>{let{id:n,...r}=e.req.valid(`json`),i=await e.env.data.tenants.get(e.var.tenant_id);if(!i)throw new _(404,{message:`Tenant not found`});let a=(0,t.deepMergePatch)(i,r);await e.env.data.tenants.update(e.var.tenant_id,a);let o=await e.env.data.tenants.get(e.var.tenant_id);if(!o)throw new _(500,{message:`Failed to retrieve updated tenant`});return e.json(o)}),i}function y(e){for(let{pattern:t,type:n}of[{pattern:/\/api\/v2\/resource-servers\/([^/]+)$/,type:`resource_server`},{pattern:/\/api\/v2\/roles\/([^/]+)$/,type:`role`},{pattern:/\/api\/v2\/connections\/([^/]+)$/,type:`connection`}]){let r=e.match(t);if(r&&r[1])return{type:n,id:r[1]}}return null}async function b(e,t,n){try{switch(n.type){case`resource_server`:return(await e.resourceServers.get(t,n.id))?.is_system===!0;case`role`:return(await e.roles.get(t,n.id))?.is_system===!0;case`connection`:return(await e.connections.get(t,n.id))?.is_system===!0;default:return!1}}catch{return!1}}function x(e){return{resource_server:`resource server`,role:`role`,connection:`connection`}[e]}function S(){return async(e,t)=>{if(![`PATCH`,`PUT`,`DELETE`].includes(e.req.method))return t();let n=y(e.req.path);if(!n)return t();let r=e.var.tenant_id||e.req.header(`x-tenant-id`)||e.req.header(`tenant-id`);if(!r)return t();if(await b(e.env.data,r,n))throw new _(403,{message:`This ${x(n.type)} is a system resource and cannot be modified. Make changes in the control plane instead.`});return t()}}function C(e,n){let r=n.find(t=>t.strategy===e.strategy);if(!r?.options)return e;let i=t.connectionSchema.passthrough().parse({...r,...e});return i.options=t.connectionOptionsSchema.passthrough().parse({...r.options||{},...e.options}),i}function w(e,t){let n=[...t||[],...e||[]];return[...new Set(n)]}function T(e,t){if(!t?.length)return e||[];if(!e?.length)return t;let n=new Map;for(let e of t)n.set(e.value,e);for(let t of e)n.set(t.value,t);return Array.from(n.values())}function E(e,t){return t?{...e,scopes:T(e.scopes,t.scopes)}:e}function D(e,t){return t?{...e,callbacks:w(e.callbacks,t.callbacks),web_origins:w(e.web_origins,t.web_origins),allowed_logout_urls:w(e.allowed_logout_urls,t.allowed_logout_urls),allowed_origins:w(e.allowed_origins,t.allowed_origins)}:e}function O(e){let{controlPlaneTenantId:t,controlPlaneClientId:n,resolveControlPlane:r}=e;if(r)return async e=>r({tenant_id:e});if(!t)return async()=>void 0;let i={tenantId:t,clientId:n};return async()=>i}function k(e,t){return{...e.resourceServers,get:async(n,r)=>{let i=await e.resourceServers.get(n,r);if(!i)return i;let a=await t(n);return!a||n===a.tenantId||!i.is_system?i:E(i,await e.resourceServers.get(a.tenantId,r))},list:async(n,r)=>{let i=await e.resourceServers.list(n,r),a=await t(n);if(!a||n===a.tenantId)return i;let o=a.tenantId,s=i.resource_servers.filter(e=>!!(e.is_system&&e.id)).map(e=>e.id);if(s.length===0)return i;let c=new Map;await Promise.all(s.map(async t=>{let n=await e.resourceServers.get(o,t);n&&c.set(t,n)}));let l=i.resource_servers.map(e=>e.is_system&&e.id?E(e,c.get(e.id)??null):e);return{...i,resource_servers:l}}}}function A(e,t){let n=O({controlPlaneTenantId:t.controlPlaneTenantId,resolveControlPlane:t.resolveControlPlane});return{...e,resourceServers:k(e,n)}}function j(e,t){let{controlPlaneTenantId:n,controlPlaneClientId:r,resolveControlPlane:i}=t,a=O({controlPlaneTenantId:n,controlPlaneClientId:r,resolveControlPlane:i});return{...e,multiTenancyConfig:{controlPlaneTenantId:n,controlPlaneClientId:r,resolveControlPlane:i},connections:{...e.connections,get:async(t,n)=>{let r=await e.connections.get(t,n);if(!r)return r;let i=await a(t);return!i||t===i.tenantId?r:C(r,(await e.connections.list(i.tenantId)).connections||[])},list:async(t,n)=>{let r=await e.connections.list(t,n),i=await a(t);if(!i||t===i.tenantId)return r;let o=await e.connections.list(i.tenantId),s=r.connections.map(e=>C(e,o.connections||[]));return{...r,connections:s}}},clientConnections:{...e.clientConnections,listByClient:async(t,n)=>{let r=await e.clientConnections.listByClient(t,n);r.length===0&&(r=(await e.connections.list(t)).connections||[]);let i=await a(t);if(!i||t===i.tenantId)return r;let o=await e.connections.list(i.tenantId);return r.map(e=>C(e,o.connections||[]))}},emailProviders:{...e.emailProviders,get:async t=>{let n=await e.emailProviders.get(t);if(n)return n;let r=await a(t);return!r||t===r.tenantId?null:e.emailProviders.get(r.tenantId)}},resourceServers:k(e,a),hooks:N(e,a)}}function M(e){if(!e||typeof e!=`object`)return!1;let t=e.metadata;return!t||typeof t!=`object`?!1:t.inheritable===!0}function N(e,t){return{...e.hooks,list:async(n,r)=>{let i=await e.hooks.list(n,r),a=await t(n);if(!a||n===a.tenantId)return i;let o=((await e.hooks.list(a.tenantId,r)).hooks||[]).filter(M);if(o.length===0)return i;let s=new Set((i.hooks||[]).map(e=>e.hook_id)),c=o.filter(e=>!s.has(e.hook_id));return{...i,hooks:[...i.hooks||[],...c],length:typeof i.length==`number`?i.length+c.length:i.length}},get:async(n,r)=>{let i=await e.hooks.get(n,r);if(i)return i;let a=await t(n);if(!a||n===a.tenantId)return i;let o=await e.hooks.get(a.tenantId,r);return o&&M(o)?o:null}}}function P(e,t){return j(e,t)}function F(e){return async(t,n)=>{let r=t.var.user;return r?.tenant_id===e&&r.org_name&&t.set(`tenant_id`,r.org_name),n()}}function I(e){return async(n,r)=>{if(!e.accessControl)return r();let{controlPlaneTenantId:a}=e.accessControl,o=n.var.org_name,s=n.var.organization_id,c=o||s,l=n.var.tenant_id,u=n.var.user,d=(u?.aud?Array.isArray(u.aud)?u.aud:[u.aud]:[]).includes(t.MANAGEMENT_API_AUDIENCE);if(!l&&c&&d&&(n.set(`tenant_id`,c),l=c),!l)throw new _(400,{message:`Tenant ID not found in request`});if(!i(s,l,a,o))throw new _(403,{message:`Access denied to tenant ${l}`});return r()}}function L(e){return async(t,n)=>{if(!e.subdomainRouting)return n();let{baseDomain:r,reservedSubdomains:i=[],resolveSubdomain:a}=e.subdomainRouting,o=t.req.header(`x-forwarded-host`)||t.req.header(`host`)||``,s=null;if(o.endsWith(r)){let e=o.slice(0,-(r.length+1));e&&!e.includes(`.`)&&(s=e)}if(s&&i.includes(s)&&(s=null),!s)return e.accessControl&&t.set(`tenant_id`,e.accessControl.controlPlaneTenantId),n();let c=null;if(a)c=await a(s);else if(e.subdomainRouting.useOrganizations!==!1&&e.accessControl)try{let n=await t.env.data.organizations.get(e.accessControl.controlPlaneTenantId,s);n&&(c=n.id)}catch{}if(!c)throw new _(404,{message:`Tenant not found for subdomain: ${s}`});return t.set(`tenant_id`,c),n()}}function R(e){return async(t,n)=>{if(!e.databaseIsolation)return n();let r=t.var.tenant_id;if(!r)throw new _(400,{message:`Tenant ID not found in request`});try{let n=await e.databaseIsolation.getAdapters(r);t.env.data=n}catch(e){throw console.error(`Failed to resolve database for tenant ${r}:`,e),new _(500,{message:`Failed to resolve tenant database`})}return n()}}function z(e){let t=L(e),n=I(e),r=R(e);return async(e,i)=>(await t(e,async()=>{}),await n(e,async()=>{}),await r(e,async()=>{}),i())}function B(e){let{dataAdapter:n,controlPlane:r,controlPlane:{tenantId:i=`control_plane`,clientId:a}={},resolveControlPlane:o,sync:c={resourceServers:!0,roles:!0},defaultPermissions:l=[`tenant:admin`],requireOrganizationMatch:u=!1,managementApiExtensions:d=[],entityHooks:f,getChildTenantIds:p,getAdapters:m,...h}=e;if(o&&!r)throw Error("initMultiTenant: `resolveControlPlane` requires `controlPlane` to be set. The static `controlPlane.tenantId` is used for access control, sync direction, and tenant management routing; the resolver only overrides per-tenant runtime inheritance lookups on top of it.");let _=n,y=n;r&&(_=P(n,{controlPlaneTenantId:i,controlPlaneClientId:a,resolveControlPlane:o}),y={...A(n,{controlPlaneTenantId:i,resolveControlPlane:o}),multiTenancyConfig:{controlPlaneTenantId:i,controlPlaneClientId:a,resolveControlPlane:o}});let b=c!==!1,x=b?{resourceServers:c.resourceServers??!0,roles:c.roles??!0}:{resourceServers:!1,roles:!1},{entityHooks:C,tenantHooks:w}=g({controlPlaneTenantId:i,getChildTenantIds:p??(async()=>(await(0,t.fetchAll)(e=>_.tenants.list(e),`tenants`,{cursorField:`id`,pageSize:100})).filter(e=>e.id!==i).map(e=>e.id)),getAdapters:m??(async()=>_),getControlPlaneAdapters:async()=>_,sync:x}),T={resourceServers:[C.resourceServers,...f?.resourceServers??[]],roles:[C.roles,...f?.roles??[]],connections:f?.connections??[],tenants:f?.tenants??[],rolePermissions:f?.rolePermissions??[]},E=s({accessControl:{controlPlaneTenantId:i,requireOrganizationMatch:u,defaultPermissions:l}}),D=v({accessControl:{controlPlaneTenantId:i,requireOrganizationMatch:u,defaultPermissions:l}},{tenants:{async beforeCreate(e,t){return E.beforeCreate&&(t=await E.beforeCreate(e,t)),w.beforeCreate&&(t=await w.beforeCreate(e,t)),t},async afterCreate(e,t){await E.afterCreate?.(e,t),await w.afterCreate?.(e,t)},async beforeDelete(e,t){await E.beforeDelete?.(e,t),await w.beforeDelete?.(e,t)}}}),{app:O}=(0,t.init)({dataAdapter:_,managementDataAdapter:y,...h,entityHooks:T,managementApiExtensions:[...d,{path:`/tenants`,router:D}]});return O.use(`/api/v2/*`,F(i)),b&&O.use(`/api/v2/*`,S()),{app:O,controlPlaneTenantId:i}}function V(){return{upserted:0,errors:[]}}function H(e){let t=e.metadata;return!!(t&&t.inheritable===!0)}async function U(e,t,n,r){try{await r()}catch(r){let i=`${t}: ${r instanceof Error?r.message:String(r)}`;if(!n)throw Error(i,{cause:r});e.errors.push(i)}}async function W(e,n){let{controlPlaneTenantId:r,getControlPlaneAdapters:i,getAdapters:a,entities:o={},continueOnError:s=!1}=e,c={connections:o.connections??!0,resourceServers:o.resourceServers??!0,hooks:o.hooks??!0,emailProvider:o.emailProvider??!0,branding:o.branding??!0,promptSettings:o.promptSettings??!0},l=await i(),u=await a(n),d={tenantId:n,connections:V(),resourceServers:V(),hooks:V(),emailProvider:V(),branding:V(),promptSettings:V()};if(c.connections){let e=await(0,t.fetchAll)(e=>l.connections.list(r,e),`connections`,{cursorField:`id`,pageSize:100});for(let n of e){let e=n.id;e&&await U(d.connections,`connection ${e}`,s,async()=>{let i=t.connectionInsertSchema.parse(n);await u.connections.get(r,e)?await u.connections.update(r,e,i):await u.connections.create(r,i),d.connections.upserted+=1})}}if(c.resourceServers){let e=await(0,t.fetchAll)(e=>l.resourceServers.list(r,e),`resource_servers`,{cursorField:`id`,pageSize:100});for(let n of e)!n.is_system||!n.id||await U(d.resourceServers,`resource_server ${n.id}`,s,async()=>{let e=t.resourceServerInsertSchema.parse(n);await u.resourceServers.get(r,n.id)?await u.resourceServers.update(r,n.id,e):await u.resourceServers.create(r,e),d.resourceServers.upserted+=1})}if(c.hooks){let e=await(0,t.fetchAll)(e=>l.hooks.list(r,e),`hooks`,{cursorField:`hook_id`,pageSize:100});for(let n of e)!H(n)||!n.hook_id||await U(d.hooks,`hook ${n.hook_id}`,s,async()=>{let e=t.hookInsertSchema.parse(n);await u.hooks.get(r,n.hook_id)?await u.hooks.update(r,n.hook_id,e):await u.hooks.create(r,e),d.hooks.upserted+=1})}return c.emailProvider&&await U(d.emailProvider,`email_provider`,s,async()=>{let e=await l.emailProviders.get(r);e&&(await u.emailProviders.get(r)?await u.emailProviders.update(r,e):await u.emailProviders.create(r,e),d.emailProvider.upserted+=1)}),c.branding&&await U(d.branding,`branding`,s,async()=>{let e=await l.branding.get(r);e&&(await u.branding.set(r,e),d.branding.upserted+=1)}),c.promptSettings&&await U(d.promptSettings,`prompt_settings`,s,async()=>{let e=await l.promptSettings.get(r);e&&(await u.promptSettings.set(r,e),d.promptSettings.upserted+=1)}),d}function G(e){return{syncDefaults:t=>W(e,t),syncDefaultsToTenants:async t=>{let n=[];for(let r of t)n.push(await W(e,r));return n}}}function K(e){let t=q(e);return{name:`multi-tenancy`,middleware:z(e),hooks:t,routes:[{path:`/management`,handler:v(e,t)}],onRegister:async()=>{console.log(`Multi-tenancy plugin registered`),e.accessControl&&console.log(` - Access control enabled (control plane: ${e.accessControl.controlPlaneTenantId})`),e.subdomainRouting&&console.log(` - Subdomain routing enabled (base domain: ${e.subdomainRouting.baseDomain})`),e.databaseIsolation&&console.log(` - Database isolation enabled`)}}}function q(e){let t=e.accessControl?r(e.accessControl):{},n=e.databaseIsolation?a(e.databaseIsolation):{},i=s(e);return{...t,...n,tenants:i}}function J(t){let n=new e.Hono,r=q(t);return n.route(`/tenants`,v(t,r)),n}function Y(e){return{hooks:q(e),middleware:z(e),app:J(e),config:e,wrapAdapters:(t,n)=>P(t,{controlPlaneTenantId:e.accessControl?.controlPlaneTenantId,controlPlaneClientId:n?.controlPlaneClientId})}}exports.createAccessControlHooks=r,exports.createAccessControlMiddleware=I,exports.createControlPlaneTenantMiddleware=F,exports.createDatabaseHooks=a,exports.createDatabaseMiddleware=R,exports.createDirectRolloutAdapter=G,exports.createMultiTenancy=J,exports.createMultiTenancyHooks=q,exports.createMultiTenancyMiddleware=z,exports.createMultiTenancyPlugin=K,exports.createProtectSyncedMiddleware=S,exports.createProvisioningHooks=s,exports.createRuntimeFallbackAdapter=j,exports.createSubdomainMiddleware=L,exports.createSyncHooks=g,exports.createTenantsOpenAPIRouter=v,exports.initMultiTenant=B,exports.mergeClientWithFallback=D,exports.projectControlPlaneDefaults=W,exports.setupMultiTenancy=Y,exports.validateTenantAccess=i,exports.withRuntimeFallback=P,exports.withSystemResourceServerInheritance=A;
@@ -1,8 +1,8 @@
1
1
  import { Hono as e } from "hono";
2
- import { MANAGEMENT_API_AUDIENCE as t, MANAGEMENT_API_SCOPES as n, auth0QuerySchema as r, connectionOptionsSchema as i, connectionSchema as a, deepMergePatch as o, fetchAll as s, init as c, tenantInsertSchema as l, tenantSchema as u } from "authhero";
3
- import { OpenAPIHono as d, createRoute as f, z as p } from "@hono/zod-openapi";
2
+ import { MANAGEMENT_API_AUDIENCE as t, MANAGEMENT_API_SCOPES as n, auth0QuerySchema as r, connectionInsertSchema as i, connectionOptionsSchema as a, connectionSchema as o, deepMergePatch as s, fetchAll as c, hookInsertSchema as l, init as u, resourceServerInsertSchema as d, tenantInsertSchema as f, tenantSchema as p } from "authhero";
3
+ import { OpenAPIHono as m, createRoute as h, z as g } from "@hono/zod-openapi";
4
4
  //#region src/hooks/access-control.ts
5
- function m(e) {
5
+ function _(e) {
6
6
  let { controlPlaneTenantId: t, requireOrganizationMatch: n = !0 } = e;
7
7
  return { async onTenantAccessValidation(e, r) {
8
8
  if (r === t) return !0;
@@ -13,14 +13,14 @@ function m(e) {
13
13
  return !0;
14
14
  } };
15
15
  }
16
- function h(e, t, n, r) {
16
+ function v(e, t, n, r) {
17
17
  if (t === n) return !0;
18
18
  let i = r || e;
19
19
  return i ? i.toLowerCase() === t.toLowerCase() : !1;
20
20
  }
21
21
  //#endregion
22
22
  //#region src/hooks/database.ts
23
- function g(e) {
23
+ function y(e) {
24
24
  return { async resolveDataAdapters(t) {
25
25
  try {
26
26
  return await e.getAdapters(t);
@@ -32,20 +32,20 @@ function g(e) {
32
32
  }
33
33
  //#endregion
34
34
  //#region src/hooks/provisioning.ts
35
- function _(e) {
35
+ function b(e) {
36
36
  return `urn:authhero:tenant:${e.toLowerCase()}`;
37
37
  }
38
- function v(e) {
38
+ function x(e) {
39
39
  return {
40
40
  async beforeCreate(e, t) {
41
41
  return !t.audience && t.id ? {
42
42
  ...t,
43
- audience: _(t.id)
43
+ audience: b(t.id)
44
44
  } : t;
45
45
  },
46
46
  async afterCreate(t, n) {
47
47
  let { accessControl: r, databaseIsolation: i } = e;
48
- r && t.ctx && await y(t, n, r), i?.onProvision && await i.onProvision(n.id);
48
+ r && t.ctx && await S(t, n, r), i?.onProvision && await i.onProvision(n.id);
49
49
  },
50
50
  async beforeDelete(t, n) {
51
51
  let { accessControl: r, databaseIsolation: i } = e;
@@ -63,14 +63,14 @@ function v(e) {
63
63
  }
64
64
  };
65
65
  }
66
- async function y(e, t, n) {
66
+ async function S(e, t, n) {
67
67
  let { controlPlaneTenantId: r, defaultPermissions: i, defaultRoles: a, issuer: o, adminRoleName: s = "Tenant Admin", adminRoleDescription: c = "Full access to all tenant management operations", addCreatorToOrganization: l = !0 } = n, u = await e.adapters.organizations.create(r, {
68
68
  name: t.id,
69
69
  display_name: t.friendly_name || t.id
70
70
  }), d;
71
- if (o && (d = await x(e, r, s, c)), l && e.ctx) {
71
+ if (o && (d = await w(e, r, s, c)), l && e.ctx) {
72
72
  let t = e.ctx.var.user;
73
- if (t?.sub && !await b(e, r, t.sub)) try {
73
+ if (t?.sub && !await C(e, r, t.sub)) try {
74
74
  await e.adapters.userOrganizations.create(r, {
75
75
  user_id: t.sub,
76
76
  organization_id: u.id
@@ -81,12 +81,12 @@ async function y(e, t, n) {
81
81
  }
82
82
  a && a.length > 0 && console.log(`Would assign roles ${a.join(", ")} to organization ${u.id}`), i && i.length > 0 && console.log(`Would grant permissions ${i.join(", ")} to organization ${u.id}`);
83
83
  }
84
- async function b(e, t, n) {
84
+ async function C(e, t, n) {
85
85
  let r = await e.adapters.userRoles.list(t, n, void 0, "");
86
86
  for (let n of r) if ((await e.adapters.rolePermissions.list(t, n.id, { per_page: 1e3 })).some((e) => e.permission_name === "admin:organizations")) return !0;
87
87
  return !1;
88
88
  }
89
- async function x(e, r, i, a) {
89
+ async function w(e, r, i, a) {
90
90
  let o = (await e.adapters.roles.list(r, {})).roles.find((e) => e.name === i);
91
91
  if (o) return o.id;
92
92
  let s = await e.adapters.roles.create(r, {
@@ -101,7 +101,7 @@ async function x(e, r, i, a) {
101
101
  }
102
102
  //#endregion
103
103
  //#region src/hooks/sync.ts
104
- function S(e, t, n = () => !0) {
104
+ function T(e, t, n = () => !0) {
105
105
  let { controlPlaneTenantId: r, getChildTenantIds: i, getAdapters: a } = e, o = /* @__PURE__ */ new Map();
106
106
  async function s(e, n, r) {
107
107
  return (await t(e).list(n, {
@@ -156,11 +156,11 @@ function S(e, t, n = () => !0) {
156
156
  }
157
157
  };
158
158
  }
159
- function C(e, t, n = () => !0) {
159
+ function E(e, t, n = () => !0) {
160
160
  let { controlPlaneTenantId: r, getControlPlaneAdapters: i, getAdapters: a } = e;
161
161
  return { async afterCreate(e, o) {
162
162
  if (o.id !== r) try {
163
- let e = await i(), c = await a(o.id), l = t(e), u = t(c), d = await s((e) => l.listPaginated(r, e), l.listKey, {
163
+ let e = await i(), s = await a(o.id), l = t(e), u = t(s), d = await c((e) => l.listPaginated(r, e), l.listKey, {
164
164
  cursorField: "id",
165
165
  pageSize: 100
166
166
  });
@@ -180,7 +180,7 @@ function C(e, t, n = () => !0) {
180
180
  }
181
181
  } };
182
182
  }
183
- var w = (e) => ({
183
+ var D = (e) => ({
184
184
  list: async (t, n) => (await e.resourceServers.list(t, n)).resource_servers,
185
185
  listPaginated: (t, n) => e.resourceServers.list(t, n),
186
186
  get: (t, n) => e.resourceServers.get(t, n),
@@ -198,7 +198,7 @@ var w = (e) => ({
198
198
  token_lifetime: e.token_lifetime,
199
199
  token_lifetime_for_web: e.token_lifetime_for_web
200
200
  })
201
- }), T = (e) => ({
201
+ }), O = (e) => ({
202
202
  list: async (t, n) => (await e.roles.list(t, n)).roles,
203
203
  listPaginated: (t, n) => e.roles.list(t, n),
204
204
  get: (t, n) => e.roles.get(t, n),
@@ -213,15 +213,15 @@ var w = (e) => ({
213
213
  description: e.description
214
214
  })
215
215
  });
216
- function E(e) {
216
+ function k(e) {
217
217
  return e.metadata?.sync !== !1;
218
218
  }
219
- function D(e) {
220
- let { sync: t = {}, filters: n = {} } = e, r = t.resourceServers ?? !0, i = t.roles ?? !0, a = (e) => E(e) ? n.resourceServers ? n.resourceServers(e) : !0 : !1, o = (e) => E(e) ? n.roles ? n.roles(e) : !0 : !1, c = r ? S(e, w, a) : void 0, l = i ? S(e, T, o) : void 0, u = r ? C(e, w, a) : void 0, d = i ? C(e, T, o) : void 0, f = i ? { async afterCreate(t, r) {
219
+ function A(e) {
220
+ let { sync: t = {}, filters: n = {} } = e, r = t.resourceServers ?? !0, i = t.roles ?? !0, a = (e) => k(e) ? n.resourceServers ? n.resourceServers(e) : !0 : !1, o = (e) => k(e) ? n.roles ? n.roles(e) : !0 : !1, s = r ? T(e, D, a) : void 0, l = i ? T(e, O, o) : void 0, u = r ? E(e, D, a) : void 0, d = i ? E(e, O, o) : void 0, f = i ? { async afterCreate(t, r) {
221
221
  if (r.id !== e.controlPlaneTenantId) {
222
222
  await d?.afterCreate?.(t, r);
223
223
  try {
224
- let t = await e.getControlPlaneAdapters(), i = await e.getAdapters(r.id), a = await s((n) => t.roles.list(e.controlPlaneTenantId, n), "roles", {
224
+ let t = await e.getControlPlaneAdapters(), i = await e.getAdapters(r.id), a = await c((n) => t.roles.list(e.controlPlaneTenantId, n), "roles", {
225
225
  cursorField: "id",
226
226
  pageSize: 100
227
227
  }), o = /* @__PURE__ */ new Map();
@@ -255,7 +255,7 @@ function D(e) {
255
255
  }
256
256
  return {
257
257
  entityHooks: {
258
- resourceServers: c,
258
+ resourceServers: s,
259
259
  roles: l
260
260
  },
261
261
  tenantHooks: { async afterCreate(e, t) {
@@ -272,7 +272,7 @@ function D(e) {
272
272
  }
273
273
  //#endregion
274
274
  //#region ../../node_modules/.pnpm/hono@4.12.25/node_modules/hono/dist/http-exception.js
275
- var O = class extends Error {
275
+ var j = class extends Error {
276
276
  res;
277
277
  status;
278
278
  constructor(e = 500, t) {
@@ -287,26 +287,26 @@ var O = class extends Error {
287
287
  };
288
288
  //#endregion
289
289
  //#region src/routes/tenants.ts
290
- function k(e, t) {
291
- let n = new d();
292
- return n.openapi(f({
290
+ function M(e, t) {
291
+ let n = new m();
292
+ return n.openapi(h({
293
293
  tags: ["tenants"],
294
294
  method: "get",
295
295
  path: "/",
296
296
  request: { query: r },
297
297
  security: [{ Bearer: [] }],
298
298
  responses: { 200: {
299
- content: { "application/json": { schema: p.object({
300
- tenants: p.array(u),
301
- start: p.number().optional(),
302
- limit: p.number().optional(),
303
- length: p.number().optional()
299
+ content: { "application/json": { schema: g.object({
300
+ tenants: g.array(p),
301
+ start: g.number().optional(),
302
+ limit: g.number().optional(),
303
+ length: g.number().optional()
304
304
  }) } },
305
305
  description: "List of tenants"
306
306
  } }
307
307
  }), async (t) => {
308
- let { page: n, per_page: r, include_totals: i, q: a } = t.req.valid("query"), o = t.var.user, c = o?.permissions || [];
309
- if (!(o?.org_id ?? t.var.organization_id) && c.includes("admin:organizations")) {
308
+ let { page: n, per_page: r, include_totals: i, q: a } = t.req.valid("query"), o = t.var.user, s = o?.permissions || [];
309
+ if (!(o?.org_id ?? t.var.organization_id) && s.includes("admin:organizations")) {
310
310
  let e = await t.env.data.tenants.list({
311
311
  page: n,
312
312
  per_page: r,
@@ -321,21 +321,21 @@ function k(e, t) {
321
321
  }) : t.json({ tenants: e.tenants });
322
322
  }
323
323
  let l = e.accessControl?.controlPlaneTenantId ?? t.env.data.multiTenancyConfig?.controlPlaneTenantId;
324
- if (l && !o?.sub) throw new O(403, { message: "Access denied: token has no subject" });
324
+ if (l && !o?.sub) throw new j(403, { message: "Access denied: token has no subject" });
325
325
  if (l && o?.sub) {
326
- let e = (await s((e) => t.env.data.userOrganizations.listUserOrganizations(l, o.sub, e), "organizations")).map((e) => e.name);
326
+ let e = (await c((e) => t.env.data.userOrganizations.listUserOrganizations(l, o.sub, e), "organizations")).map((e) => e.name);
327
327
  if (e.length === 0) return i ? t.json({
328
328
  tenants: [],
329
329
  start: 0,
330
330
  limit: r ?? 50,
331
331
  length: 0
332
332
  }) : t.json({ tenants: [] });
333
- let c = e.length, u = n ?? 0, d = r ?? 50, f = u * d, p = e.slice(f, f + d);
333
+ let s = e.length, u = n ?? 0, d = r ?? 50, f = u * d, p = e.slice(f, f + d);
334
334
  if (p.length === 0) return i ? t.json({
335
335
  tenants: [],
336
336
  start: f,
337
337
  limit: d,
338
- length: c
338
+ length: s
339
339
  }) : t.json({ tenants: [] });
340
340
  let m = p.map((e) => `id:${e}`).join(" OR "), h = a ? `(${m}) AND (${a})` : m, g = await t.env.data.tenants.list({
341
341
  q: h,
@@ -346,7 +346,7 @@ function k(e, t) {
346
346
  tenants: g.tenants,
347
347
  start: f,
348
348
  limit: d,
349
- length: c
349
+ length: s
350
350
  }) : t.json({ tenants: g.tenants });
351
351
  }
352
352
  let u = await t.env.data.tenants.list({
@@ -361,22 +361,22 @@ function k(e, t) {
361
361
  limit: u.totals?.limit ?? r,
362
362
  length: u.tenants.length
363
363
  }) : t.json({ tenants: u.tenants });
364
- }), n.openapi(f({
364
+ }), n.openapi(h({
365
365
  tags: ["tenants"],
366
366
  method: "post",
367
367
  path: "/",
368
- request: { body: { content: { "application/json": { schema: l } } } },
368
+ request: { body: { content: { "application/json": { schema: f } } } },
369
369
  security: [{ Bearer: [] }],
370
370
  responses: {
371
371
  201: {
372
- content: { "application/json": { schema: u } },
372
+ content: { "application/json": { schema: p } },
373
373
  description: "Tenant created"
374
374
  },
375
375
  400: { description: "Validation error" },
376
376
  409: { description: "Tenant with this ID already exists" }
377
377
  }
378
378
  }), async (e) => {
379
- if (!e.var.user?.sub) throw new O(401, { message: "Authentication required to create tenants" });
379
+ if (!e.var.user?.sub) throw new j(401, { message: "Authentication required to create tenants" });
380
380
  let n = e.req.valid("json"), r = {
381
381
  adapters: e.env.data,
382
382
  ctx: e
@@ -384,11 +384,11 @@ function k(e, t) {
384
384
  t.tenants?.beforeCreate && (n = await t.tenants.beforeCreate(r, n));
385
385
  let i = await e.env.data.tenants.create(n);
386
386
  return t.tenants?.afterCreate && await t.tenants.afterCreate(r, i), e.json(i, 201);
387
- }), n.openapi(f({
387
+ }), n.openapi(h({
388
388
  tags: ["tenants"],
389
389
  method: "delete",
390
390
  path: "/{id}",
391
- request: { params: p.object({ id: p.string() }) },
391
+ request: { params: g.object({ id: g.string() }) },
392
392
  security: [{ Bearer: ["delete:tenants"] }],
393
393
  responses: {
394
394
  204: { description: "Tenant deleted" },
@@ -399,57 +399,57 @@ function k(e, t) {
399
399
  let { id: r } = n.req.valid("param"), i = e.accessControl?.controlPlaneTenantId ?? n.env.data.multiTenancyConfig?.controlPlaneTenantId;
400
400
  if (i) {
401
401
  let e = n.var.user;
402
- if (!e?.sub) throw new O(401, { message: "Authentication required" });
403
- if (r === i) throw new O(403, { message: "Cannot delete the control plane" });
402
+ if (!e?.sub) throw new j(401, { message: "Authentication required" });
403
+ if (r === i) throw new j(403, { message: "Cannot delete the control plane" });
404
404
  let t = n.var.org_name, a = r.toLowerCase(), o = !!t && t.toLowerCase() === a;
405
- if (o ||= (await s((t) => n.env.data.userOrganizations.listUserOrganizations(i, e.sub, t), "organizations")).some((e) => e.name?.toLowerCase() === a), !o) throw new O(403, { message: "Access denied to this tenant" });
405
+ if (o ||= (await c((t) => n.env.data.userOrganizations.listUserOrganizations(i, e.sub, t), "organizations")).some((e) => e.name?.toLowerCase() === a), !o) throw new j(403, { message: "Access denied to this tenant" });
406
406
  }
407
- if (!await n.env.data.tenants.get(r)) throw new O(404, { message: "Tenant not found" });
407
+ if (!await n.env.data.tenants.get(r)) throw new j(404, { message: "Tenant not found" });
408
408
  let a = {
409
409
  adapters: n.env.data,
410
410
  ctx: n
411
411
  };
412
412
  return t.tenants?.beforeDelete && await t.tenants.beforeDelete(a, r), await n.env.data.tenants.remove(r), t.tenants?.afterDelete && await t.tenants.afterDelete(a, r), n.body(null, 204);
413
- }), n.openapi(f({
413
+ }), n.openapi(h({
414
414
  tags: ["tenants", "settings"],
415
415
  method: "get",
416
416
  path: "/settings",
417
- request: { headers: p.object({ "tenant-id": p.string().optional() }) },
417
+ request: { headers: g.object({ "tenant-id": g.string().optional() }) },
418
418
  security: [{ Bearer: ["read:tenants"] }],
419
419
  responses: { 200: {
420
- content: { "application/json": { schema: u } },
420
+ content: { "application/json": { schema: p } },
421
421
  description: "Current tenant settings"
422
422
  } }
423
423
  }), async (e) => {
424
424
  let t = await e.env.data.tenants.get(e.var.tenant_id);
425
- if (!t) throw new O(404, { message: "Tenant not found" });
425
+ if (!t) throw new j(404, { message: "Tenant not found" });
426
426
  return e.json(t);
427
- }), n.openapi(f({
427
+ }), n.openapi(h({
428
428
  tags: ["tenants", "settings"],
429
429
  method: "patch",
430
430
  path: "/settings",
431
431
  request: {
432
- headers: p.object({ "tenant-id": p.string().optional() }),
433
- body: { content: { "application/json": { schema: p.object(l.shape).partial() } } }
432
+ headers: g.object({ "tenant-id": g.string().optional() }),
433
+ body: { content: { "application/json": { schema: g.object(f.shape).partial() } } }
434
434
  },
435
435
  security: [{ Bearer: ["update:tenants"] }],
436
436
  responses: { 200: {
437
- content: { "application/json": { schema: u } },
437
+ content: { "application/json": { schema: p } },
438
438
  description: "Updated tenant settings"
439
439
  } }
440
440
  }), async (e) => {
441
441
  let { id: t, ...n } = e.req.valid("json"), r = await e.env.data.tenants.get(e.var.tenant_id);
442
- if (!r) throw new O(404, { message: "Tenant not found" });
443
- let i = o(r, n);
442
+ if (!r) throw new j(404, { message: "Tenant not found" });
443
+ let i = s(r, n);
444
444
  await e.env.data.tenants.update(e.var.tenant_id, i);
445
445
  let a = await e.env.data.tenants.get(e.var.tenant_id);
446
- if (!a) throw new O(500, { message: "Failed to retrieve updated tenant" });
446
+ if (!a) throw new j(500, { message: "Failed to retrieve updated tenant" });
447
447
  return e.json(a);
448
448
  }), n;
449
449
  }
450
450
  //#endregion
451
451
  //#region src/middleware/protect-synced.ts
452
- function A(e) {
452
+ function ee(e) {
453
453
  for (let { pattern: t, type: n } of [
454
454
  {
455
455
  pattern: /\/api\/v2\/resource-servers\/([^/]+)$/,
@@ -472,7 +472,7 @@ function A(e) {
472
472
  }
473
473
  return null;
474
474
  }
475
- async function j(e, t, n) {
475
+ async function te(e, t, n) {
476
476
  try {
477
477
  switch (n.type) {
478
478
  case "resource_server": return (await e.resourceServers.get(t, n.id))?.is_system === !0;
@@ -484,7 +484,7 @@ async function j(e, t, n) {
484
484
  return !1;
485
485
  }
486
486
  }
487
- function M(e) {
487
+ function ne(e) {
488
488
  return {
489
489
  resource_server: "resource server",
490
490
  role: "role",
@@ -498,11 +498,11 @@ function N() {
498
498
  "PUT",
499
499
  "DELETE"
500
500
  ].includes(e.req.method)) return t();
501
- let n = A(e.req.path);
501
+ let n = ee(e.req.path);
502
502
  if (!n) return t();
503
503
  let r = e.var.tenant_id || e.req.header("x-tenant-id") || e.req.header("tenant-id");
504
504
  if (!r) return t();
505
- if (await j(e.env.data, r, n)) throw new O(403, { message: `This ${M(n.type)} is a system resource and cannot be modified. Make changes in the control plane instead.` });
505
+ if (await te(e.env.data, r, n)) throw new j(403, { message: `This ${ne(n.type)} is a system resource and cannot be modified. Make changes in the control plane instead.` });
506
506
  return t();
507
507
  };
508
508
  }
@@ -511,11 +511,11 @@ function N() {
511
511
  function P(e, t) {
512
512
  let n = t.find((t) => t.strategy === e.strategy);
513
513
  if (!n?.options) return e;
514
- let r = a.passthrough().parse({
514
+ let r = o.passthrough().parse({
515
515
  ...n,
516
516
  ...e
517
517
  });
518
- return r.options = i.passthrough().parse({
518
+ return r.options = a.passthrough().parse({
519
519
  ...n.options || {},
520
520
  ...e.options
521
521
  }), r;
@@ -538,7 +538,7 @@ function L(e, t) {
538
538
  scopes: I(e.scopes, t.scopes)
539
539
  } : e;
540
540
  }
541
- function R(e, t) {
541
+ function re(e, t) {
542
542
  return t ? {
543
543
  ...e,
544
544
  callbacks: F(e.callbacks, t.callbacks),
@@ -547,7 +547,7 @@ function R(e, t) {
547
547
  allowed_origins: F(e.allowed_origins, t.allowed_origins)
548
548
  } : e;
549
549
  }
550
- function z(e) {
550
+ function R(e) {
551
551
  let { controlPlaneTenantId: t, controlPlaneClientId: n, resolveControlPlane: r } = e;
552
552
  if (r) return async (e) => r({ tenant_id: e });
553
553
  if (!t) return async () => void 0;
@@ -557,7 +557,7 @@ function z(e) {
557
557
  };
558
558
  return async () => i;
559
559
  }
560
- function B(e, t) {
560
+ function z(e, t) {
561
561
  return {
562
562
  ...e.resourceServers,
563
563
  get: async (n, r) => {
@@ -584,18 +584,18 @@ function B(e, t) {
584
584
  }
585
585
  };
586
586
  }
587
- function V(e, t) {
588
- let n = z({
587
+ function B(e, t) {
588
+ let n = R({
589
589
  controlPlaneTenantId: t.controlPlaneTenantId,
590
590
  resolveControlPlane: t.resolveControlPlane
591
591
  });
592
592
  return {
593
593
  ...e,
594
- resourceServers: B(e, n)
594
+ resourceServers: z(e, n)
595
595
  };
596
596
  }
597
- function H(e, t) {
598
- let { controlPlaneTenantId: n, controlPlaneClientId: r, resolveControlPlane: i } = t, a = z({
597
+ function V(e, t) {
598
+ let { controlPlaneTenantId: n, controlPlaneClientId: r, resolveControlPlane: i } = t, a = R({
599
599
  controlPlaneTenantId: n,
600
600
  controlPlaneClientId: r,
601
601
  resolveControlPlane: i
@@ -645,22 +645,22 @@ function H(e, t) {
645
645
  return !r || t === r.tenantId ? null : e.emailProviders.get(r.tenantId);
646
646
  }
647
647
  },
648
- resourceServers: B(e, a),
649
- hooks: W(e, a)
648
+ resourceServers: z(e, a),
649
+ hooks: ie(e, a)
650
650
  };
651
651
  }
652
- function U(e) {
652
+ function H(e) {
653
653
  if (!e || typeof e != "object") return !1;
654
654
  let t = e.metadata;
655
655
  return !t || typeof t != "object" ? !1 : t.inheritable === !0;
656
656
  }
657
- function W(e, t) {
657
+ function ie(e, t) {
658
658
  return {
659
659
  ...e.hooks,
660
660
  list: async (n, r) => {
661
661
  let i = await e.hooks.list(n, r), a = await t(n);
662
662
  if (!a || n === a.tenantId) return i;
663
- let o = ((await e.hooks.list(a.tenantId, r)).hooks || []).filter(U);
663
+ let o = ((await e.hooks.list(a.tenantId, r)).hooks || []).filter(H);
664
664
  if (o.length === 0) return i;
665
665
  let s = new Set((i.hooks || []).map((e) => e.hook_id)), c = o.filter((e) => !s.has(e.hook_id));
666
666
  return {
@@ -675,31 +675,31 @@ function W(e, t) {
675
675
  let a = await t(n);
676
676
  if (!a || n === a.tenantId) return i;
677
677
  let o = await e.hooks.get(a.tenantId, r);
678
- return o && U(o) ? o : null;
678
+ return o && H(o) ? o : null;
679
679
  }
680
680
  };
681
681
  }
682
- function G(e, t) {
683
- return H(e, t);
682
+ function U(e, t) {
683
+ return V(e, t);
684
684
  }
685
685
  //#endregion
686
686
  //#region src/middleware/index.ts
687
- function K(e) {
687
+ function W(e) {
688
688
  return async (t, n) => {
689
689
  let r = t.var.user;
690
690
  return r?.tenant_id === e && r.org_name && t.set("tenant_id", r.org_name), n();
691
691
  };
692
692
  }
693
- function q(e) {
693
+ function G(e) {
694
694
  return async (n, r) => {
695
695
  if (!e.accessControl) return r();
696
696
  let { controlPlaneTenantId: i } = e.accessControl, a = n.var.org_name, o = n.var.organization_id, s = a || o, c = n.var.tenant_id, l = n.var.user, u = (l?.aud ? Array.isArray(l.aud) ? l.aud : [l.aud] : []).includes(t);
697
- if (!c && s && u && (n.set("tenant_id", s), c = s), !c) throw new O(400, { message: "Tenant ID not found in request" });
698
- if (!h(o, c, i, a)) throw new O(403, { message: `Access denied to tenant ${c}` });
697
+ if (!c && s && u && (n.set("tenant_id", s), c = s), !c) throw new j(400, { message: "Tenant ID not found in request" });
698
+ if (!v(o, c, i, a)) throw new j(403, { message: `Access denied to tenant ${c}` });
699
699
  return r();
700
700
  };
701
701
  }
702
- function J(e) {
702
+ function K(e) {
703
703
  return async (t, n) => {
704
704
  if (!e.subdomainRouting) return n();
705
705
  let { baseDomain: r, reservedSubdomains: i = [], resolveSubdomain: a } = e.subdomainRouting, o = t.req.header("x-forwarded-host") || t.req.header("host") || "", s = null;
@@ -714,43 +714,43 @@ function J(e) {
714
714
  let n = await t.env.data.organizations.get(e.accessControl.controlPlaneTenantId, s);
715
715
  n && (c = n.id);
716
716
  } catch {}
717
- if (!c) throw new O(404, { message: `Tenant not found for subdomain: ${s}` });
717
+ if (!c) throw new j(404, { message: `Tenant not found for subdomain: ${s}` });
718
718
  return t.set("tenant_id", c), n();
719
719
  };
720
720
  }
721
- function Y(e) {
721
+ function q(e) {
722
722
  return async (t, n) => {
723
723
  if (!e.databaseIsolation) return n();
724
724
  let r = t.var.tenant_id;
725
- if (!r) throw new O(400, { message: "Tenant ID not found in request" });
725
+ if (!r) throw new j(400, { message: "Tenant ID not found in request" });
726
726
  try {
727
727
  let n = await e.databaseIsolation.getAdapters(r);
728
728
  t.env.data = n;
729
729
  } catch (e) {
730
- throw console.error(`Failed to resolve database for tenant ${r}:`, e), new O(500, { message: "Failed to resolve tenant database" });
730
+ throw console.error(`Failed to resolve database for tenant ${r}:`, e), new j(500, { message: "Failed to resolve tenant database" });
731
731
  }
732
732
  return n();
733
733
  };
734
734
  }
735
- function X(e) {
736
- let t = J(e), n = q(e), r = Y(e);
735
+ function J(e) {
736
+ let t = K(e), n = G(e), r = q(e);
737
737
  return async (e, i) => (await t(e, async () => {}), await n(e, async () => {}), await r(e, async () => {}), i());
738
738
  }
739
739
  //#endregion
740
740
  //#region src/init.ts
741
- function Z(e) {
741
+ function ae(e) {
742
742
  let { dataAdapter: t, controlPlane: n, controlPlane: { tenantId: r = "control_plane", clientId: i } = {}, resolveControlPlane: a, sync: o = {
743
743
  resourceServers: !0,
744
744
  roles: !0
745
- }, defaultPermissions: l = ["tenant:admin"], requireOrganizationMatch: u = !1, managementApiExtensions: d = [], entityHooks: f, getChildTenantIds: p, getAdapters: m, ...h } = e;
745
+ }, defaultPermissions: s = ["tenant:admin"], requireOrganizationMatch: l = !1, managementApiExtensions: d = [], entityHooks: f, getChildTenantIds: p, getAdapters: m, ...h } = e;
746
746
  if (a && !n) throw Error("initMultiTenant: `resolveControlPlane` requires `controlPlane` to be set. The static `controlPlane.tenantId` is used for access control, sync direction, and tenant management routing; the resolver only overrides per-tenant runtime inheritance lookups on top of it.");
747
747
  let g = t, _ = t;
748
- n && (g = G(t, {
748
+ n && (g = U(t, {
749
749
  controlPlaneTenantId: r,
750
750
  controlPlaneClientId: i,
751
751
  resolveControlPlane: a
752
752
  }), _ = {
753
- ...V(t, {
753
+ ...B(t, {
754
754
  controlPlaneTenantId: r,
755
755
  resolveControlPlane: a
756
756
  }),
@@ -760,35 +760,35 @@ function Z(e) {
760
760
  resolveControlPlane: a
761
761
  }
762
762
  });
763
- let y = o !== !1, b = y ? {
763
+ let v = o !== !1, y = v ? {
764
764
  resourceServers: o.resourceServers ?? !0,
765
765
  roles: o.roles ?? !0
766
766
  } : {
767
767
  resourceServers: !1,
768
768
  roles: !1
769
- }, { entityHooks: x, tenantHooks: S } = D({
769
+ }, { entityHooks: b, tenantHooks: S } = A({
770
770
  controlPlaneTenantId: r,
771
- getChildTenantIds: p ?? (async () => (await s((e) => g.tenants.list(e), "tenants", {
771
+ getChildTenantIds: p ?? (async () => (await c((e) => g.tenants.list(e), "tenants", {
772
772
  cursorField: "id",
773
773
  pageSize: 100
774
774
  })).filter((e) => e.id !== r).map((e) => e.id)),
775
775
  getAdapters: m ?? (async () => g),
776
776
  getControlPlaneAdapters: async () => g,
777
- sync: b
777
+ sync: y
778
778
  }), C = {
779
- resourceServers: [x.resourceServers, ...f?.resourceServers ?? []],
780
- roles: [x.roles, ...f?.roles ?? []],
779
+ resourceServers: [b.resourceServers, ...f?.resourceServers ?? []],
780
+ roles: [b.roles, ...f?.roles ?? []],
781
781
  connections: f?.connections ?? [],
782
782
  tenants: f?.tenants ?? [],
783
783
  rolePermissions: f?.rolePermissions ?? []
784
- }, w = v({ accessControl: {
784
+ }, w = x({ accessControl: {
785
785
  controlPlaneTenantId: r,
786
- requireOrganizationMatch: u,
787
- defaultPermissions: l
788
- } }), T = k({ accessControl: {
786
+ requireOrganizationMatch: l,
787
+ defaultPermissions: s
788
+ } }), T = M({ accessControl: {
789
789
  controlPlaneTenantId: r,
790
- requireOrganizationMatch: u,
791
- defaultPermissions: l
790
+ requireOrganizationMatch: l,
791
+ defaultPermissions: s
792
792
  } }, { tenants: {
793
793
  async beforeCreate(e, t) {
794
794
  return w.beforeCreate && (t = await w.beforeCreate(e, t)), S.beforeCreate && (t = await S.beforeCreate(e, t)), t;
@@ -799,7 +799,7 @@ function Z(e) {
799
799
  async beforeDelete(e, t) {
800
800
  await w.beforeDelete?.(e, t), await S.beforeDelete?.(e, t);
801
801
  }
802
- } }), { app: E } = c({
802
+ } }), { app: E } = u({
803
803
  dataAdapter: g,
804
804
  managementDataAdapter: _,
805
805
  ...h,
@@ -809,22 +809,116 @@ function Z(e) {
809
809
  router: T
810
810
  }]
811
811
  });
812
- return E.use("/api/v2/*", K(r)), y && E.use("/api/v2/*", N()), {
812
+ return E.use("/api/v2/*", W(r)), v && E.use("/api/v2/*", N()), {
813
813
  app: E,
814
814
  controlPlaneTenantId: r
815
815
  };
816
816
  }
817
817
  //#endregion
818
+ //#region src/rollout/defaults-projection.ts
819
+ function Y() {
820
+ return {
821
+ upserted: 0,
822
+ errors: []
823
+ };
824
+ }
825
+ function oe(e) {
826
+ let t = e.metadata;
827
+ return !!(t && t.inheritable === !0);
828
+ }
829
+ async function X(e, t, n, r) {
830
+ try {
831
+ await r();
832
+ } catch (r) {
833
+ let i = `${t}: ${r instanceof Error ? r.message : String(r)}`;
834
+ if (!n) throw Error(i, { cause: r });
835
+ e.errors.push(i);
836
+ }
837
+ }
838
+ async function Z(e, t) {
839
+ let { controlPlaneTenantId: n, getControlPlaneAdapters: r, getAdapters: a, entities: o = {}, continueOnError: s = !1 } = e, u = {
840
+ connections: o.connections ?? !0,
841
+ resourceServers: o.resourceServers ?? !0,
842
+ hooks: o.hooks ?? !0,
843
+ emailProvider: o.emailProvider ?? !0,
844
+ branding: o.branding ?? !0,
845
+ promptSettings: o.promptSettings ?? !0
846
+ }, f = await r(), p = await a(t), m = {
847
+ tenantId: t,
848
+ connections: Y(),
849
+ resourceServers: Y(),
850
+ hooks: Y(),
851
+ emailProvider: Y(),
852
+ branding: Y(),
853
+ promptSettings: Y()
854
+ };
855
+ if (u.connections) {
856
+ let e = await c((e) => f.connections.list(n, e), "connections", {
857
+ cursorField: "id",
858
+ pageSize: 100
859
+ });
860
+ for (let t of e) {
861
+ let e = t.id;
862
+ e && await X(m.connections, `connection ${e}`, s, async () => {
863
+ let r = i.parse(t);
864
+ await p.connections.get(n, e) ? await p.connections.update(n, e, r) : await p.connections.create(n, r), m.connections.upserted += 1;
865
+ });
866
+ }
867
+ }
868
+ if (u.resourceServers) {
869
+ let e = await c((e) => f.resourceServers.list(n, e), "resource_servers", {
870
+ cursorField: "id",
871
+ pageSize: 100
872
+ });
873
+ for (let t of e) !t.is_system || !t.id || await X(m.resourceServers, `resource_server ${t.id}`, s, async () => {
874
+ let e = d.parse(t);
875
+ await p.resourceServers.get(n, t.id) ? await p.resourceServers.update(n, t.id, e) : await p.resourceServers.create(n, e), m.resourceServers.upserted += 1;
876
+ });
877
+ }
878
+ if (u.hooks) {
879
+ let e = await c((e) => f.hooks.list(n, e), "hooks", {
880
+ cursorField: "hook_id",
881
+ pageSize: 100
882
+ });
883
+ for (let t of e) !oe(t) || !t.hook_id || await X(m.hooks, `hook ${t.hook_id}`, s, async () => {
884
+ let e = l.parse(t);
885
+ await p.hooks.get(n, t.hook_id) ? await p.hooks.update(n, t.hook_id, e) : await p.hooks.create(n, e), m.hooks.upserted += 1;
886
+ });
887
+ }
888
+ return u.emailProvider && await X(m.emailProvider, "email_provider", s, async () => {
889
+ let e = await f.emailProviders.get(n);
890
+ e && (await p.emailProviders.get(n) ? await p.emailProviders.update(n, e) : await p.emailProviders.create(n, e), m.emailProvider.upserted += 1);
891
+ }), u.branding && await X(m.branding, "branding", s, async () => {
892
+ let e = await f.branding.get(n);
893
+ e && (await p.branding.set(n, e), m.branding.upserted += 1);
894
+ }), u.promptSettings && await X(m.promptSettings, "prompt_settings", s, async () => {
895
+ let e = await f.promptSettings.get(n);
896
+ e && (await p.promptSettings.set(n, e), m.promptSettings.upserted += 1);
897
+ }), m;
898
+ }
899
+ //#endregion
900
+ //#region src/rollout/index.ts
901
+ function se(e) {
902
+ return {
903
+ syncDefaults: (t) => Z(e, t),
904
+ syncDefaultsToTenants: async (t) => {
905
+ let n = [];
906
+ for (let r of t) n.push(await Z(e, r));
907
+ return n;
908
+ }
909
+ };
910
+ }
911
+ //#endregion
818
912
  //#region src/plugin.ts
819
- function ee(e) {
913
+ function ce(e) {
820
914
  let t = Q(e);
821
915
  return {
822
916
  name: "multi-tenancy",
823
- middleware: X(e),
917
+ middleware: J(e),
824
918
  hooks: t,
825
919
  routes: [{
826
920
  path: "/management",
827
- handler: k(e, t)
921
+ handler: M(e, t)
828
922
  }],
829
923
  onRegister: async () => {
830
924
  console.log("Multi-tenancy plugin registered"), e.accessControl && console.log(` - Access control enabled (control plane: ${e.accessControl.controlPlaneTenantId})`), e.subdomainRouting && console.log(` - Subdomain routing enabled (base domain: ${e.subdomainRouting.baseDomain})`), e.databaseIsolation && console.log(" - Database isolation enabled");
@@ -834,7 +928,7 @@ function ee(e) {
834
928
  //#endregion
835
929
  //#region src/index.ts
836
930
  function Q(e) {
837
- let t = e.accessControl ? m(e.accessControl) : {}, n = e.databaseIsolation ? g(e.databaseIsolation) : {}, r = v(e);
931
+ let t = e.accessControl ? _(e.accessControl) : {}, n = e.databaseIsolation ? y(e.databaseIsolation) : {}, r = x(e);
838
932
  return {
839
933
  ...t,
840
934
  ...n,
@@ -843,19 +937,19 @@ function Q(e) {
843
937
  }
844
938
  function $(t) {
845
939
  let n = new e(), r = Q(t);
846
- return n.route("/tenants", k(t, r)), n;
940
+ return n.route("/tenants", M(t, r)), n;
847
941
  }
848
- function te(e) {
942
+ function le(e) {
849
943
  return {
850
944
  hooks: Q(e),
851
- middleware: X(e),
945
+ middleware: J(e),
852
946
  app: $(e),
853
947
  config: e,
854
- wrapAdapters: (t, n) => G(t, {
948
+ wrapAdapters: (t, n) => U(t, {
855
949
  controlPlaneTenantId: e.accessControl?.controlPlaneTenantId,
856
950
  controlPlaneClientId: n?.controlPlaneClientId
857
951
  })
858
952
  };
859
953
  }
860
954
  //#endregion
861
- export { m as createAccessControlHooks, q as createAccessControlMiddleware, K as createControlPlaneTenantMiddleware, g as createDatabaseHooks, Y as createDatabaseMiddleware, $ as createMultiTenancy, Q as createMultiTenancyHooks, X as createMultiTenancyMiddleware, ee as createMultiTenancyPlugin, N as createProtectSyncedMiddleware, v as createProvisioningHooks, H as createRuntimeFallbackAdapter, J as createSubdomainMiddleware, D as createSyncHooks, k as createTenantsOpenAPIRouter, Z as initMultiTenant, R as mergeClientWithFallback, te as setupMultiTenancy, h as validateTenantAccess, G as withRuntimeFallback, V as withSystemResourceServerInheritance };
955
+ export { _ as createAccessControlHooks, G as createAccessControlMiddleware, W as createControlPlaneTenantMiddleware, y as createDatabaseHooks, q as createDatabaseMiddleware, se as createDirectRolloutAdapter, $ as createMultiTenancy, Q as createMultiTenancyHooks, J as createMultiTenancyMiddleware, ce as createMultiTenancyPlugin, N as createProtectSyncedMiddleware, x as createProvisioningHooks, V as createRuntimeFallbackAdapter, K as createSubdomainMiddleware, A as createSyncHooks, M as createTenantsOpenAPIRouter, ae as initMultiTenant, re as mergeClientWithFallback, Z as projectControlPlaneDefaults, le as setupMultiTenancy, v as validateTenantAccess, U as withRuntimeFallback, B as withSystemResourceServerInheritance };
@@ -3,6 +3,8 @@ import { MultiTenancyConfig, MultiTenancyHooks, MultiTenancyBindings, MultiTenan
3
3
  export * from "./types";
4
4
  export { initMultiTenant } from "./init";
5
5
  export type { MultiTenantConfig, MultiTenantResult, ControlPlaneConfig, } from "./init";
6
+ export { createDirectRolloutAdapter, projectControlPlaneDefaults, } from "./rollout";
7
+ export type { ControlPlaneRolloutAdapter, DefaultsProjectionConfig, DefaultsProjectionEntities, DefaultsProjectionResult, EntityProjectionOutcome, } from "./rollout";
6
8
  export { createSyncHooks } from "./hooks/sync";
7
9
  export type { EntitySyncConfig, SyncHooksResult } from "./hooks/sync";
8
10
  export { createTenantsOpenAPIRouter } from "./routes";
@@ -1 +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;AAajB,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AACzC,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,QAAQ,CAAC;AAGhB,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,EACnB,mCAAmC,EACnC,uBAAuB,GACxB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEhF,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,kBAAkB;;;;;;;kBAnD9C,oBAAoB;mBACnB,qBAAqB;;;IAwDhC;;;;;;;OAOG;6BAES,OAAO,UAAU,EAAE,YAAY,qBACtB;QAAE,oBAAoB,CAAC,EAAE,MAAM,CAAA;KAAE;EAOzD"}
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;AAajB,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AACzC,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,QAAQ,CAAC;AAKhB,OAAO,EACL,0BAA0B,EAC1B,2BAA2B,GAC5B,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,0BAA0B,EAC1B,wBAAwB,EACxB,0BAA0B,EAC1B,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,WAAW,CAAC;AAGnB,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,EACnB,mCAAmC,EACnC,uBAAuB,GACxB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEhF,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,kBAAkB;;;;;;;kBAnD9C,oBAAoB;mBACnB,qBAAqB;;;IAwDhC;;;;;;;OAOG;6BAES,OAAO,UAAU,EAAE,YAAY,qBACtB;QAAE,oBAAoB,CAAC,EAAE,MAAM,CAAA;KAAE;EAOzD"}
@@ -0,0 +1,74 @@
1
+ import { DataAdapters } from "authhero";
2
+ /**
3
+ * Which control plane entities to project into a tenant's own database. Every
4
+ * entity defaults to `true`.
5
+ *
6
+ * The projected rows are the same set the runtime fallback
7
+ * (`withRuntimeFallback`) reads from the control plane tenant: connections,
8
+ * `is_system` resource servers, `inheritable` hooks and the email provider.
9
+ * `branding` and `promptSettings` are projected too so a WFP tenant can render
10
+ * the control plane's defaults; consuming them on read still depends on the
11
+ * tenant resolving those singletons against the control plane tenant id.
12
+ */
13
+ export interface DefaultsProjectionEntities {
14
+ connections?: boolean;
15
+ resourceServers?: boolean;
16
+ hooks?: boolean;
17
+ emailProvider?: boolean;
18
+ branding?: boolean;
19
+ promptSettings?: boolean;
20
+ }
21
+ export interface DefaultsProjectionConfig {
22
+ /**
23
+ * The control plane tenant id. Projected rows are written into the target
24
+ * tenant's database under THIS id, so the existing runtime fallback resolves
25
+ * them exactly as it does in a shared database.
26
+ */
27
+ controlPlaneTenantId: string;
28
+ /**
29
+ * Adapters for reading the control plane tenant's rows. Secrets are returned
30
+ * decrypted (this should be the encrypted adapter), and are re-encrypted at
31
+ * rest by the target adapter on write.
32
+ */
33
+ getControlPlaneAdapters: () => Promise<DataAdapters>;
34
+ /**
35
+ * Adapters for the target tenant's own database. For a WFP tenant this is the
36
+ * adapter over the tenant's D1, ideally wrapped with a key ring that tags
37
+ * control-plane-tenant rows with a control-plane-only key id so the tenant
38
+ * operator cannot read the inherited secrets.
39
+ */
40
+ getAdapters: (tenantId: string) => Promise<DataAdapters>;
41
+ /** Which entities to project. All default to true. */
42
+ entities?: DefaultsProjectionEntities;
43
+ /**
44
+ * When false (default) the first failing entity throws, so a pilot rollout
45
+ * fails loudly. When true, every entity is attempted and errors are collected
46
+ * into the result instead.
47
+ */
48
+ continueOnError?: boolean;
49
+ }
50
+ export interface EntityProjectionOutcome {
51
+ /** Rows created or updated. */
52
+ upserted: number;
53
+ /** Non-fatal errors, populated only when `continueOnError` is true. */
54
+ errors: string[];
55
+ }
56
+ export interface DefaultsProjectionResult {
57
+ tenantId: string;
58
+ connections: EntityProjectionOutcome;
59
+ resourceServers: EntityProjectionOutcome;
60
+ hooks: EntityProjectionOutcome;
61
+ emailProvider: EntityProjectionOutcome;
62
+ branding: EntityProjectionOutcome;
63
+ promptSettings: EntityProjectionOutcome;
64
+ }
65
+ /**
66
+ * Projects the control plane tenant's inheritable defaults into a single target
67
+ * tenant's database, writing the rows under the control plane tenant id so the
68
+ * existing runtime fallback resolves them with no read-path change.
69
+ *
70
+ * Idempotent: every row is upserted by its stable id, so re-running the
71
+ * projection (a re-sync, or a later rollout) converges rather than duplicating.
72
+ */
73
+ export declare function projectControlPlaneDefaults(config: DefaultsProjectionConfig, targetTenantId: string): Promise<DefaultsProjectionResult>;
74
+ //# sourceMappingURL=defaults-projection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults-projection.d.ts","sourceRoot":"","sources":["../../../src/rollout/defaults-projection.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAQb,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;GAUG;AACH,MAAM,WAAW,0BAA0B;IACzC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAE7B;;;;OAIG;IACH,uBAAuB,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;IAErD;;;;;OAKG;IACH,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzD,sDAAsD;IACtD,QAAQ,CAAC,EAAE,0BAA0B,CAAC;IAEtC;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACtC,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,uBAAuB,CAAC;IACrC,eAAe,EAAE,uBAAuB,CAAC;IACzC,KAAK,EAAE,uBAAuB,CAAC;IAC/B,aAAa,EAAE,uBAAuB,CAAC;IACvC,QAAQ,EAAE,uBAAuB,CAAC;IAClC,cAAc,EAAE,uBAAuB,CAAC;CACzC;AAgCD;;;;;;;GAOG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,wBAAwB,EAChC,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,wBAAwB,CAAC,CAyJnC"}
@@ -0,0 +1,30 @@
1
+ import { DefaultsProjectionConfig, DefaultsProjectionResult } from "./defaults-projection";
2
+ export type { DefaultsProjectionConfig, DefaultsProjectionEntities, DefaultsProjectionResult, EntityProjectionOutcome, } from "./defaults-projection";
3
+ export { projectControlPlaneDefaults } from "./defaults-projection";
4
+ /**
5
+ * A control plane rollout applies the control plane's state to one or more
6
+ * tenant databases. `syncDefaults` is the only operation today; schema
7
+ * migrations and tenant worker code deploys are the same shape (enumerate
8
+ * tenants, apply per-tenant, retry, resume) and will become sibling methods.
9
+ *
10
+ * The interface is the seam for execution strategy. The direct implementation
11
+ * runs inline — right for a single pilot tenant. A future Cloudflare Workflows
12
+ * implementation will satisfy the same interface with durable, retryable,
13
+ * resumable fan-out, with no change to callers.
14
+ */
15
+ export interface ControlPlaneRolloutAdapter {
16
+ /** Project the control plane defaults into a single tenant's database. */
17
+ syncDefaults(targetTenantId: string): Promise<DefaultsProjectionResult>;
18
+ /**
19
+ * Project the defaults into several tenants. The direct implementation runs
20
+ * them sequentially; a Workflows implementation fans out durably.
21
+ */
22
+ syncDefaultsToTenants(targetTenantIds: string[]): Promise<DefaultsProjectionResult[]>;
23
+ }
24
+ /**
25
+ * Creates a rollout adapter that executes projections inline in the current
26
+ * process. Use this to validate the model with a single tenant before moving to
27
+ * a durable Cloudflare Workflows implementation.
28
+ */
29
+ export declare function createDirectRolloutAdapter(config: DefaultsProjectionConfig): ControlPlaneRolloutAdapter;
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/rollout/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EAEzB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,wBAAwB,EACxB,0BAA0B,EAC1B,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAEpE;;;;;;;;;;GAUG;AACH,MAAM,WAAW,0BAA0B;IACzC,0EAA0E;IAC1E,YAAY,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAExE;;;OAGG;IACH,qBAAqB,CACnB,eAAe,EAAE,MAAM,EAAE,GACxB,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAAC;CACxC;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,wBAAwB,GAC/B,0BAA0B,CAa5B"}
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "type": "git",
12
12
  "url": "https://github.com/markusahlstrand/authhero"
13
13
  },
14
- "version": "14.23.1",
14
+ "version": "14.24.0",
15
15
  "description": "Multi-tenancy support for AuthHero with organization-based access control and per-tenant database isolation",
16
16
  "files": [
17
17
  "dist"
@@ -36,9 +36,9 @@
36
36
  "typescript": "^5.6.0",
37
37
  "vite": "^8.0.14",
38
38
  "vitest": "^4.1.7",
39
+ "@authhero/kysely-adapter": "11.8.9",
39
40
  "@authhero/adapter-interfaces": "3.1.1",
40
- "@authhero/kysely-adapter": "11.8.8",
41
- "authhero": "7.2.2"
41
+ "authhero": "8.3.0"
42
42
  },
43
43
  "dependencies": {
44
44
  "zod": "^4.4.3"