@authhero/multi-tenancy 14.23.1 → 14.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/multi-tenancy.cjs +1 -1
- package/dist/multi-tenancy.mjs +251 -152
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/rollout/defaults-projection.d.ts +74 -0
- package/dist/types/rollout/defaults-projection.d.ts.map +1 -0
- package/dist/types/rollout/index.d.ts +30 -0
- package/dist/types/rollout/index.d.ts.map +1 -0
- package/dist/types/routes/tenants.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/multi-tenancy.cjs
CHANGED
|
@@ -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"),r=require("hono/http-exception");function i(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 a(e,t,n,r){if(t===n)return!0;let i=r||e;return i?i.toLowerCase()===t.toLowerCase():!1}function o(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 s(e){return`urn:authhero:tenant:${e.toLowerCase()}`}function c(e){return{async beforeCreate(e,t){return!t.audience&&t.id?{...t,audience:s(t.id)}:t},async afterCreate(t,n){let{accessControl:r,databaseIsolation:i}=e;r&&t.ctx&&await l(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 l(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:l=!0}=n,f=await e.adapters.organizations.create(r,{name:t.id,display_name:t.friendly_name||t.id}),p;if(o&&(p=await d(e,r,s,c)),l&&e.ctx){let t=e.ctx.var.user;if(t?.sub&&!await u(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 u(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 d(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 f(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 p(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 m=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})}),h=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 g(e){return e.metadata?.sync!==!1}function _(e){let{sync:n={},filters:r={}}=e,i=n.resourceServers??!0,a=n.roles??!0,o=e=>g(e)?r.resourceServers?r.resourceServers(e):!0:!1,s=e=>g(e)?r.roles?r.roles(e):!0:!1,c=i?f(e,m,o):void 0,l=a?f(e,h,s):void 0,u=i?p(e,m,o):void 0,d=a?p(e,h,s):void 0,_=a?{async afterCreate(n,i){if(i.id!==e.controlPlaneTenantId){await d?.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??d?.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 v=n.z.object({sub:n.z.string(),tenant_id:n.z.string().optional(),org_id:n.z.string().optional(),scope:n.z.string().optional(),permissions:n.z.array(n.z.string()).optional()}).passthrough();function y(e){let t=v.safeParse(e);return t.success?t.data:void 0}function b(e){let t=e.permissions??[],n=e.scope?e.scope.split(` `).filter(Boolean):[],r=new Set([...t,...n]);return r.has(`delete:tenants`)||r.has(`admin:organizations`)}function x(e,i){let a=new n.OpenAPIHono;return a.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:i,per_page:a,include_totals:o,q:s}=n.req.valid(`query`),c=n.var.user,l=c?.permissions||[];if(!(c?.org_id??n.var.organization_id)&&l.includes(`admin:organizations`)){let e=await n.env.data.tenants.list({page:i,per_page:a,include_totals:o,q:s});return o?n.json({tenants:e.tenants,start:e.totals?.start??0,limit:e.totals?.limit??a,length:e.tenants.length}):n.json({tenants:e.tenants})}let u=e.accessControl?.controlPlaneTenantId??n.env.data.multiTenancyConfig?.controlPlaneTenantId;if(u&&!c?.sub)throw new r.HTTPException(403,{message:`Access denied: token has no subject`});if(u&&c?.sub){let e=(await(0,t.fetchAll)(e=>n.env.data.userOrganizations.listUserOrganizations(u,c.sub,e),`organizations`)).map(e=>e.name);if(e.length===0)return o?n.json({tenants:[],start:0,limit:a??50,length:0}):n.json({tenants:[]});let r=e.length,l=i??0,d=a??50,f=l*d,p=e.slice(f,f+d);if(p.length===0)return o?n.json({tenants:[],start:f,limit:d,length:r}):n.json({tenants:[]});let m=p.map(e=>`id:${e}`).join(` OR `),h=s?`(${m}) AND (${s})`:m,g=await n.env.data.tenants.list({q:h,per_page:d,include_totals:!1});return o?n.json({tenants:g.tenants,start:f,limit:d,length:r}):n.json({tenants:g.tenants})}let d=await n.env.data.tenants.list({page:i,per_page:a,include_totals:o,q:s});return o?n.json({tenants:d.tenants,start:d.totals?.start??0,limit:d.totals?.limit??a,length:d.tenants.length}):n.json({tenants:d.tenants})}),a.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 r.HTTPException(401,{message:`Authentication required to create tenants`});let t=e.req.valid(`json`),n={adapters:e.env.data,ctx:e};i.tenants?.beforeCreate&&(t=await i.tenants.beforeCreate(n,t));let a=await e.env.data.tenants.create(t);return i.tenants?.afterCreate&&await i.tenants.afterCreate(n,a),e.json(a,201)}),a.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:a}=n.req.valid(`param`),o=e.accessControl?.controlPlaneTenantId??n.env.data.multiTenancyConfig?.controlPlaneTenantId;if(o){let e=y(n.var.user);if(!e?.sub)throw new r.HTTPException(401,{message:`Authentication required`});if(a===o)throw new r.HTTPException(403,{message:`Cannot delete the control plane`});let i=n.var.org_name,s=a.toLowerCase(),c=!!i&&i.toLowerCase()===s;if(!c){let t=!!(e.org_id??n.var.organization_id??i),r=!e.tenant_id||e.tenant_id===o;!t&&r&&b(e)&&(c=!0)}if(c||=(await(0,t.fetchAll)(t=>n.env.data.userOrganizations.listUserOrganizations(o,e.sub,t),`organizations`)).some(e=>e.name?.toLowerCase()===s),!c)throw new r.HTTPException(403,{message:`Access denied to this tenant`})}if(!await n.env.data.tenants.get(a))throw new r.HTTPException(404,{message:`Tenant not found`});let s={adapters:n.env.data,ctx:n};return i.tenants?.beforeDelete&&await i.tenants.beforeDelete(s,a),await n.env.data.tenants.remove(a),i.tenants?.afterDelete&&await i.tenants.afterDelete(s,a),n.body(null,204)}),a.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 r.HTTPException(404,{message:`Tenant not found`});return e.json(t)}),a.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,...i}=e.req.valid(`json`),a=await e.env.data.tenants.get(e.var.tenant_id);if(!a)throw new r.HTTPException(404,{message:`Tenant not found`});let o=(0,t.deepMergePatch)(a,i);await e.env.data.tenants.update(e.var.tenant_id,o);let s=await e.env.data.tenants.get(e.var.tenant_id);if(!s)throw new r.HTTPException(500,{message:`Failed to retrieve updated tenant`});return e.json(s)}),a}function S(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 C(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 w(e){return{resource_server:`resource server`,role:`role`,connection:`connection`}[e]}function T(){return async(e,t)=>{if(![`PATCH`,`PUT`,`DELETE`].includes(e.req.method))return t();let n=S(e.req.path);if(!n)return t();let i=e.var.tenant_id||e.req.header(`x-tenant-id`)||e.req.header(`tenant-id`);if(!i)return t();if(await C(e.env.data,i,n))throw new r.HTTPException(403,{message:`This ${w(n.type)} is a system resource and cannot be modified. Make changes in the control plane instead.`});return t()}}function E(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 D(e,t){let n=[...t||[],...e||[]];return[...new Set(n)]}function O(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 k(e,t){return t?{...e,scopes:O(e.scopes,t.scopes)}:e}function A(e,t){return t?{...e,callbacks:D(e.callbacks,t.callbacks),web_origins:D(e.web_origins,t.web_origins),allowed_logout_urls:D(e.allowed_logout_urls,t.allowed_logout_urls),allowed_origins:D(e.allowed_origins,t.allowed_origins)}:e}function j(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 M(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:k(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?k(e,c.get(e.id)??null):e);return{...i,resource_servers:l}}}}function N(e,t){let n=j({controlPlaneTenantId:t.controlPlaneTenantId,resolveControlPlane:t.resolveControlPlane});return{...e,resourceServers:M(e,n)}}function P(e,t){let{controlPlaneTenantId:n,controlPlaneClientId:r,resolveControlPlane:i}=t,a=j({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:E(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=>E(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=>E(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:M(e,a),hooks:I(e,a)}}function F(e){if(!e||typeof e!=`object`)return!1;let t=e.metadata;return!t||typeof t!=`object`?!1:t.inheritable===!0}function I(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(F);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&&F(o)?o:null}}}function L(e,t){return P(e,t)}function R(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 z(e){return async(n,i)=>{if(!e.accessControl)return i();let{controlPlaneTenantId:o}=e.accessControl,s=n.var.org_name,c=n.var.organization_id,l=s||c,u=n.var.tenant_id,d=n.var.user,f=(d?.aud?Array.isArray(d.aud)?d.aud:[d.aud]:[]).includes(t.MANAGEMENT_API_AUDIENCE);if(!u&&l&&f&&(n.set(`tenant_id`,l),u=l),!u)throw new r.HTTPException(400,{message:`Tenant ID not found in request`});if(!a(c,u,o,s))throw new r.HTTPException(403,{message:`Access denied to tenant ${u}`});return i()}}function B(e){return async(t,n)=>{if(!e.subdomainRouting)return n();let{baseDomain:i,reservedSubdomains:a=[],resolveSubdomain:o}=e.subdomainRouting,s=t.req.header(`x-forwarded-host`)||t.req.header(`host`)||``,c=null;if(s.endsWith(i)){let e=s.slice(0,-(i.length+1));e&&!e.includes(`.`)&&(c=e)}if(c&&a.includes(c)&&(c=null),!c)return e.accessControl&&t.set(`tenant_id`,e.accessControl.controlPlaneTenantId),n();let l=null;if(o)l=await o(c);else if(e.subdomainRouting.useOrganizations!==!1&&e.accessControl)try{let n=await t.env.data.organizations.get(e.accessControl.controlPlaneTenantId,c);n&&(l=n.id)}catch{}if(!l)throw new r.HTTPException(404,{message:`Tenant not found for subdomain: ${c}`});return t.set(`tenant_id`,l),n()}}function V(e){return async(t,n)=>{if(!e.databaseIsolation)return n();let i=t.var.tenant_id;if(!i)throw new r.HTTPException(400,{message:`Tenant ID not found in request`});try{let n=await e.databaseIsolation.getAdapters(i);t.env.data=n}catch(e){throw console.error(`Failed to resolve database for tenant ${i}:`,e),new r.HTTPException(500,{message:`Failed to resolve tenant database`})}return n()}}function H(e){let t=B(e),n=z(e),r=V(e);return async(e,i)=>(await t(e,async()=>{}),await n(e,async()=>{}),await r(e,async()=>{}),i())}function U(e){let{dataAdapter:n,controlPlane:r,controlPlane:{tenantId:i=`control_plane`,clientId:a}={},resolveControlPlane:o,sync:s={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 g=n,v=n;r&&(g=L(n,{controlPlaneTenantId:i,controlPlaneClientId:a,resolveControlPlane:o}),v={...N(n,{controlPlaneTenantId:i,resolveControlPlane:o}),multiTenancyConfig:{controlPlaneTenantId:i,controlPlaneClientId:a,resolveControlPlane:o}});let y=s!==!1,b=y?{resourceServers:s.resourceServers??!0,roles:s.roles??!0}:{resourceServers:!1,roles:!1},{entityHooks:S,tenantHooks:C}=_({controlPlaneTenantId:i,getChildTenantIds:p??(async()=>(await(0,t.fetchAll)(e=>g.tenants.list(e),`tenants`,{cursorField:`id`,pageSize:100})).filter(e=>e.id!==i).map(e=>e.id)),getAdapters:m??(async()=>g),getControlPlaneAdapters:async()=>g,sync:b}),w={resourceServers:[S.resourceServers,...f?.resourceServers??[]],roles:[S.roles,...f?.roles??[]],connections:f?.connections??[],tenants:f?.tenants??[],rolePermissions:f?.rolePermissions??[]},E=c({accessControl:{controlPlaneTenantId:i,requireOrganizationMatch:u,defaultPermissions:l}}),D=x({accessControl:{controlPlaneTenantId:i,requireOrganizationMatch:u,defaultPermissions:l}},{tenants:{async beforeCreate(e,t){return E.beforeCreate&&(t=await E.beforeCreate(e,t)),C.beforeCreate&&(t=await C.beforeCreate(e,t)),t},async afterCreate(e,t){await E.afterCreate?.(e,t),await C.afterCreate?.(e,t)},async beforeDelete(e,t){await E.beforeDelete?.(e,t),await C.beforeDelete?.(e,t)}}}),{app:O}=(0,t.init)({dataAdapter:g,managementDataAdapter:v,...h,entityHooks:w,managementApiExtensions:[...d,{path:`/tenants`,router:D}]});return O.use(`/api/v2/*`,R(i)),y&&O.use(`/api/v2/*`,T()),{app:O,controlPlaneTenantId:i}}function W(){return{upserted:0,errors:[]}}function G(e){let t=e.metadata;return!!(t&&t.inheritable===!0)}async function K(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 q(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:W(),resourceServers:W(),hooks:W(),emailProvider:W(),branding:W(),promptSettings:W()};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 K(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 K(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)!G(n)||!n.hook_id||await K(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 K(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 K(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 K(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 J(e){return{syncDefaults:t=>q(e,t),syncDefaultsToTenants:async t=>{let n=[];for(let r of t)n.push(await q(e,r));return n}}}function Y(e){let t=X(e);return{name:`multi-tenancy`,middleware:H(e),hooks:t,routes:[{path:`/management`,handler:x(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 X(e){let t=e.accessControl?i(e.accessControl):{},n=e.databaseIsolation?o(e.databaseIsolation):{},r=c(e);return{...t,...n,tenants:r}}function Z(t){let n=new e.Hono,r=X(t);return n.route(`/tenants`,x(t,r)),n}function Q(e){return{hooks:X(e),middleware:H(e),app:Z(e),config:e,wrapAdapters:(t,n)=>L(t,{controlPlaneTenantId:e.accessControl?.controlPlaneTenantId,controlPlaneClientId:n?.controlPlaneClientId})}}exports.createAccessControlHooks=i,exports.createAccessControlMiddleware=z,exports.createControlPlaneTenantMiddleware=R,exports.createDatabaseHooks=o,exports.createDatabaseMiddleware=V,exports.createDirectRolloutAdapter=J,exports.createMultiTenancy=Z,exports.createMultiTenancyHooks=X,exports.createMultiTenancyMiddleware=H,exports.createMultiTenancyPlugin=Y,exports.createProtectSyncedMiddleware=T,exports.createProvisioningHooks=c,exports.createRuntimeFallbackAdapter=P,exports.createSubdomainMiddleware=B,exports.createSyncHooks=_,exports.createTenantsOpenAPIRouter=x,exports.initMultiTenant=U,exports.mergeClientWithFallback=A,exports.projectControlPlaneDefaults=q,exports.setupMultiTenancy=Q,exports.validateTenantAccess=a,exports.withRuntimeFallback=L,exports.withSystemResourceServerInheritance=N;
|
package/dist/multi-tenancy.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Hono as e } from "hono";
|
|
2
|
-
import { MANAGEMENT_API_AUDIENCE as t, MANAGEMENT_API_SCOPES as n, auth0QuerySchema as r,
|
|
3
|
-
import { OpenAPIHono as
|
|
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
|
+
import { HTTPException as _ } from "hono/http-exception";
|
|
4
5
|
//#region src/hooks/access-control.ts
|
|
5
|
-
function
|
|
6
|
+
function v(e) {
|
|
6
7
|
let { controlPlaneTenantId: t, requireOrganizationMatch: n = !0 } = e;
|
|
7
8
|
return { async onTenantAccessValidation(e, r) {
|
|
8
9
|
if (r === t) return !0;
|
|
@@ -13,14 +14,14 @@ function m(e) {
|
|
|
13
14
|
return !0;
|
|
14
15
|
} };
|
|
15
16
|
}
|
|
16
|
-
function
|
|
17
|
+
function y(e, t, n, r) {
|
|
17
18
|
if (t === n) return !0;
|
|
18
19
|
let i = r || e;
|
|
19
20
|
return i ? i.toLowerCase() === t.toLowerCase() : !1;
|
|
20
21
|
}
|
|
21
22
|
//#endregion
|
|
22
23
|
//#region src/hooks/database.ts
|
|
23
|
-
function
|
|
24
|
+
function b(e) {
|
|
24
25
|
return { async resolveDataAdapters(t) {
|
|
25
26
|
try {
|
|
26
27
|
return await e.getAdapters(t);
|
|
@@ -32,20 +33,20 @@ function g(e) {
|
|
|
32
33
|
}
|
|
33
34
|
//#endregion
|
|
34
35
|
//#region src/hooks/provisioning.ts
|
|
35
|
-
function
|
|
36
|
+
function x(e) {
|
|
36
37
|
return `urn:authhero:tenant:${e.toLowerCase()}`;
|
|
37
38
|
}
|
|
38
|
-
function
|
|
39
|
+
function S(e) {
|
|
39
40
|
return {
|
|
40
41
|
async beforeCreate(e, t) {
|
|
41
42
|
return !t.audience && t.id ? {
|
|
42
43
|
...t,
|
|
43
|
-
audience:
|
|
44
|
+
audience: x(t.id)
|
|
44
45
|
} : t;
|
|
45
46
|
},
|
|
46
47
|
async afterCreate(t, n) {
|
|
47
48
|
let { accessControl: r, databaseIsolation: i } = e;
|
|
48
|
-
r && t.ctx && await
|
|
49
|
+
r && t.ctx && await C(t, n, r), i?.onProvision && await i.onProvision(n.id);
|
|
49
50
|
},
|
|
50
51
|
async beforeDelete(t, n) {
|
|
51
52
|
let { accessControl: r, databaseIsolation: i } = e;
|
|
@@ -63,14 +64,14 @@ function v(e) {
|
|
|
63
64
|
}
|
|
64
65
|
};
|
|
65
66
|
}
|
|
66
|
-
async function
|
|
67
|
+
async function C(e, t, n) {
|
|
67
68
|
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
69
|
name: t.id,
|
|
69
70
|
display_name: t.friendly_name || t.id
|
|
70
71
|
}), d;
|
|
71
|
-
if (o && (d = await
|
|
72
|
+
if (o && (d = await T(e, r, s, c)), l && e.ctx) {
|
|
72
73
|
let t = e.ctx.var.user;
|
|
73
|
-
if (t?.sub && !await
|
|
74
|
+
if (t?.sub && !await w(e, r, t.sub)) try {
|
|
74
75
|
await e.adapters.userOrganizations.create(r, {
|
|
75
76
|
user_id: t.sub,
|
|
76
77
|
organization_id: u.id
|
|
@@ -81,12 +82,12 @@ async function y(e, t, n) {
|
|
|
81
82
|
}
|
|
82
83
|
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
84
|
}
|
|
84
|
-
async function
|
|
85
|
+
async function w(e, t, n) {
|
|
85
86
|
let r = await e.adapters.userRoles.list(t, n, void 0, "");
|
|
86
87
|
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
88
|
return !1;
|
|
88
89
|
}
|
|
89
|
-
async function
|
|
90
|
+
async function T(e, r, i, a) {
|
|
90
91
|
let o = (await e.adapters.roles.list(r, {})).roles.find((e) => e.name === i);
|
|
91
92
|
if (o) return o.id;
|
|
92
93
|
let s = await e.adapters.roles.create(r, {
|
|
@@ -101,7 +102,7 @@ async function x(e, r, i, a) {
|
|
|
101
102
|
}
|
|
102
103
|
//#endregion
|
|
103
104
|
//#region src/hooks/sync.ts
|
|
104
|
-
function
|
|
105
|
+
function E(e, t, n = () => !0) {
|
|
105
106
|
let { controlPlaneTenantId: r, getChildTenantIds: i, getAdapters: a } = e, o = /* @__PURE__ */ new Map();
|
|
106
107
|
async function s(e, n, r) {
|
|
107
108
|
return (await t(e).list(n, {
|
|
@@ -156,11 +157,11 @@ function S(e, t, n = () => !0) {
|
|
|
156
157
|
}
|
|
157
158
|
};
|
|
158
159
|
}
|
|
159
|
-
function
|
|
160
|
+
function D(e, t, n = () => !0) {
|
|
160
161
|
let { controlPlaneTenantId: r, getControlPlaneAdapters: i, getAdapters: a } = e;
|
|
161
162
|
return { async afterCreate(e, o) {
|
|
162
163
|
if (o.id !== r) try {
|
|
163
|
-
let e = await i(),
|
|
164
|
+
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
165
|
cursorField: "id",
|
|
165
166
|
pageSize: 100
|
|
166
167
|
});
|
|
@@ -180,7 +181,7 @@ function C(e, t, n = () => !0) {
|
|
|
180
181
|
}
|
|
181
182
|
} };
|
|
182
183
|
}
|
|
183
|
-
var
|
|
184
|
+
var O = (e) => ({
|
|
184
185
|
list: async (t, n) => (await e.resourceServers.list(t, n)).resource_servers,
|
|
185
186
|
listPaginated: (t, n) => e.resourceServers.list(t, n),
|
|
186
187
|
get: (t, n) => e.resourceServers.get(t, n),
|
|
@@ -198,7 +199,7 @@ var w = (e) => ({
|
|
|
198
199
|
token_lifetime: e.token_lifetime,
|
|
199
200
|
token_lifetime_for_web: e.token_lifetime_for_web
|
|
200
201
|
})
|
|
201
|
-
}),
|
|
202
|
+
}), k = (e) => ({
|
|
202
203
|
list: async (t, n) => (await e.roles.list(t, n)).roles,
|
|
203
204
|
listPaginated: (t, n) => e.roles.list(t, n),
|
|
204
205
|
get: (t, n) => e.roles.get(t, n),
|
|
@@ -213,15 +214,15 @@ var w = (e) => ({
|
|
|
213
214
|
description: e.description
|
|
214
215
|
})
|
|
215
216
|
});
|
|
216
|
-
function
|
|
217
|
+
function A(e) {
|
|
217
218
|
return e.metadata?.sync !== !1;
|
|
218
219
|
}
|
|
219
|
-
function
|
|
220
|
-
let { sync: t = {}, filters: n = {} } = e, r = t.resourceServers ?? !0, i = t.roles ?? !0, a = (e) =>
|
|
220
|
+
function j(e) {
|
|
221
|
+
let { sync: t = {}, filters: n = {} } = e, r = t.resourceServers ?? !0, i = t.roles ?? !0, a = (e) => A(e) ? n.resourceServers ? n.resourceServers(e) : !0 : !1, o = (e) => A(e) ? n.roles ? n.roles(e) : !0 : !1, s = r ? E(e, O, a) : void 0, l = i ? E(e, k, o) : void 0, u = r ? D(e, O, a) : void 0, d = i ? D(e, k, o) : void 0, f = i ? { async afterCreate(t, r) {
|
|
221
222
|
if (r.id !== e.controlPlaneTenantId) {
|
|
222
223
|
await d?.afterCreate?.(t, r);
|
|
223
224
|
try {
|
|
224
|
-
let t = await e.getControlPlaneAdapters(), i = await e.getAdapters(r.id), a = await
|
|
225
|
+
let t = await e.getControlPlaneAdapters(), i = await e.getAdapters(r.id), a = await c((n) => t.roles.list(e.controlPlaneTenantId, n), "roles", {
|
|
225
226
|
cursorField: "id",
|
|
226
227
|
pageSize: 100
|
|
227
228
|
}), o = /* @__PURE__ */ new Map();
|
|
@@ -255,7 +256,7 @@ function D(e) {
|
|
|
255
256
|
}
|
|
256
257
|
return {
|
|
257
258
|
entityHooks: {
|
|
258
|
-
resourceServers:
|
|
259
|
+
resourceServers: s,
|
|
259
260
|
roles: l
|
|
260
261
|
},
|
|
261
262
|
tenantHooks: { async afterCreate(e, t) {
|
|
@@ -271,42 +272,42 @@ function D(e) {
|
|
|
271
272
|
};
|
|
272
273
|
}
|
|
273
274
|
//#endregion
|
|
274
|
-
//#region ../../node_modules/.pnpm/hono@4.12.25/node_modules/hono/dist/http-exception.js
|
|
275
|
-
var O = class extends Error {
|
|
276
|
-
res;
|
|
277
|
-
status;
|
|
278
|
-
constructor(e = 500, t) {
|
|
279
|
-
super(t?.message, { cause: t?.cause }), this.res = t?.res, this.status = e;
|
|
280
|
-
}
|
|
281
|
-
getResponse() {
|
|
282
|
-
return this.res ? new Response(this.res.body, {
|
|
283
|
-
status: this.status,
|
|
284
|
-
headers: this.res.headers
|
|
285
|
-
}) : new Response(this.message, { status: this.status });
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
//#endregion
|
|
289
275
|
//#region src/routes/tenants.ts
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
276
|
+
var ee = g.object({
|
|
277
|
+
sub: g.string(),
|
|
278
|
+
tenant_id: g.string().optional(),
|
|
279
|
+
org_id: g.string().optional(),
|
|
280
|
+
scope: g.string().optional(),
|
|
281
|
+
permissions: g.array(g.string()).optional()
|
|
282
|
+
}).passthrough();
|
|
283
|
+
function te(e) {
|
|
284
|
+
let t = ee.safeParse(e);
|
|
285
|
+
return t.success ? t.data : void 0;
|
|
286
|
+
}
|
|
287
|
+
function ne(e) {
|
|
288
|
+
let t = e.permissions ?? [], n = e.scope ? e.scope.split(" ").filter(Boolean) : [], r = new Set([...t, ...n]);
|
|
289
|
+
return r.has("delete:tenants") || r.has("admin:organizations");
|
|
290
|
+
}
|
|
291
|
+
function M(e, t) {
|
|
292
|
+
let n = new m();
|
|
293
|
+
return n.openapi(h({
|
|
293
294
|
tags: ["tenants"],
|
|
294
295
|
method: "get",
|
|
295
296
|
path: "/",
|
|
296
297
|
request: { query: r },
|
|
297
298
|
security: [{ Bearer: [] }],
|
|
298
299
|
responses: { 200: {
|
|
299
|
-
content: { "application/json": { schema:
|
|
300
|
-
tenants:
|
|
301
|
-
start:
|
|
302
|
-
limit:
|
|
303
|
-
length:
|
|
300
|
+
content: { "application/json": { schema: g.object({
|
|
301
|
+
tenants: g.array(p),
|
|
302
|
+
start: g.number().optional(),
|
|
303
|
+
limit: g.number().optional(),
|
|
304
|
+
length: g.number().optional()
|
|
304
305
|
}) } },
|
|
305
306
|
description: "List of tenants"
|
|
306
307
|
} }
|
|
307
308
|
}), async (t) => {
|
|
308
|
-
let { page: n, per_page: r, include_totals: i, q: a } = t.req.valid("query"), o = t.var.user,
|
|
309
|
-
if (!(o?.org_id ?? t.var.organization_id) &&
|
|
309
|
+
let { page: n, per_page: r, include_totals: i, q: a } = t.req.valid("query"), o = t.var.user, s = o?.permissions || [];
|
|
310
|
+
if (!(o?.org_id ?? t.var.organization_id) && s.includes("admin:organizations")) {
|
|
310
311
|
let e = await t.env.data.tenants.list({
|
|
311
312
|
page: n,
|
|
312
313
|
per_page: r,
|
|
@@ -321,21 +322,21 @@ function k(e, t) {
|
|
|
321
322
|
}) : t.json({ tenants: e.tenants });
|
|
322
323
|
}
|
|
323
324
|
let l = e.accessControl?.controlPlaneTenantId ?? t.env.data.multiTenancyConfig?.controlPlaneTenantId;
|
|
324
|
-
if (l && !o?.sub) throw new
|
|
325
|
+
if (l && !o?.sub) throw new _(403, { message: "Access denied: token has no subject" });
|
|
325
326
|
if (l && o?.sub) {
|
|
326
|
-
let e = (await
|
|
327
|
+
let e = (await c((e) => t.env.data.userOrganizations.listUserOrganizations(l, o.sub, e), "organizations")).map((e) => e.name);
|
|
327
328
|
if (e.length === 0) return i ? t.json({
|
|
328
329
|
tenants: [],
|
|
329
330
|
start: 0,
|
|
330
331
|
limit: r ?? 50,
|
|
331
332
|
length: 0
|
|
332
333
|
}) : t.json({ tenants: [] });
|
|
333
|
-
let
|
|
334
|
+
let s = e.length, u = n ?? 0, d = r ?? 50, f = u * d, p = e.slice(f, f + d);
|
|
334
335
|
if (p.length === 0) return i ? t.json({
|
|
335
336
|
tenants: [],
|
|
336
337
|
start: f,
|
|
337
338
|
limit: d,
|
|
338
|
-
length:
|
|
339
|
+
length: s
|
|
339
340
|
}) : t.json({ tenants: [] });
|
|
340
341
|
let m = p.map((e) => `id:${e}`).join(" OR "), h = a ? `(${m}) AND (${a})` : m, g = await t.env.data.tenants.list({
|
|
341
342
|
q: h,
|
|
@@ -346,7 +347,7 @@ function k(e, t) {
|
|
|
346
347
|
tenants: g.tenants,
|
|
347
348
|
start: f,
|
|
348
349
|
limit: d,
|
|
349
|
-
length:
|
|
350
|
+
length: s
|
|
350
351
|
}) : t.json({ tenants: g.tenants });
|
|
351
352
|
}
|
|
352
353
|
let u = await t.env.data.tenants.list({
|
|
@@ -361,22 +362,22 @@ function k(e, t) {
|
|
|
361
362
|
limit: u.totals?.limit ?? r,
|
|
362
363
|
length: u.tenants.length
|
|
363
364
|
}) : t.json({ tenants: u.tenants });
|
|
364
|
-
}), n.openapi(
|
|
365
|
+
}), n.openapi(h({
|
|
365
366
|
tags: ["tenants"],
|
|
366
367
|
method: "post",
|
|
367
368
|
path: "/",
|
|
368
|
-
request: { body: { content: { "application/json": { schema:
|
|
369
|
+
request: { body: { content: { "application/json": { schema: f } } } },
|
|
369
370
|
security: [{ Bearer: [] }],
|
|
370
371
|
responses: {
|
|
371
372
|
201: {
|
|
372
|
-
content: { "application/json": { schema:
|
|
373
|
+
content: { "application/json": { schema: p } },
|
|
373
374
|
description: "Tenant created"
|
|
374
375
|
},
|
|
375
376
|
400: { description: "Validation error" },
|
|
376
377
|
409: { description: "Tenant with this ID already exists" }
|
|
377
378
|
}
|
|
378
379
|
}), async (e) => {
|
|
379
|
-
if (!e.var.user?.sub) throw new
|
|
380
|
+
if (!e.var.user?.sub) throw new _(401, { message: "Authentication required to create tenants" });
|
|
380
381
|
let n = e.req.valid("json"), r = {
|
|
381
382
|
adapters: e.env.data,
|
|
382
383
|
ctx: e
|
|
@@ -384,11 +385,11 @@ function k(e, t) {
|
|
|
384
385
|
t.tenants?.beforeCreate && (n = await t.tenants.beforeCreate(r, n));
|
|
385
386
|
let i = await e.env.data.tenants.create(n);
|
|
386
387
|
return t.tenants?.afterCreate && await t.tenants.afterCreate(r, i), e.json(i, 201);
|
|
387
|
-
}), n.openapi(
|
|
388
|
+
}), n.openapi(h({
|
|
388
389
|
tags: ["tenants"],
|
|
389
390
|
method: "delete",
|
|
390
391
|
path: "/{id}",
|
|
391
|
-
request: { params:
|
|
392
|
+
request: { params: g.object({ id: g.string() }) },
|
|
392
393
|
security: [{ Bearer: ["delete:tenants"] }],
|
|
393
394
|
responses: {
|
|
394
395
|
204: { description: "Tenant deleted" },
|
|
@@ -398,58 +399,62 @@ function k(e, t) {
|
|
|
398
399
|
}), async (n) => {
|
|
399
400
|
let { id: r } = n.req.valid("param"), i = e.accessControl?.controlPlaneTenantId ?? n.env.data.multiTenancyConfig?.controlPlaneTenantId;
|
|
400
401
|
if (i) {
|
|
401
|
-
let e = n.var.user;
|
|
402
|
-
if (!e?.sub) throw new
|
|
403
|
-
if (r === i) throw new
|
|
402
|
+
let e = te(n.var.user);
|
|
403
|
+
if (!e?.sub) throw new _(401, { message: "Authentication required" });
|
|
404
|
+
if (r === i) throw new _(403, { message: "Cannot delete the control plane" });
|
|
404
405
|
let t = n.var.org_name, a = r.toLowerCase(), o = !!t && t.toLowerCase() === a;
|
|
405
|
-
if (
|
|
406
|
+
if (!o) {
|
|
407
|
+
let r = !!(e.org_id ?? n.var.organization_id ?? t), a = !e.tenant_id || e.tenant_id === i;
|
|
408
|
+
!r && a && ne(e) && (o = !0);
|
|
409
|
+
}
|
|
410
|
+
if (o ||= (await c((t) => n.env.data.userOrganizations.listUserOrganizations(i, e.sub, t), "organizations")).some((e) => e.name?.toLowerCase() === a), !o) throw new _(403, { message: "Access denied to this tenant" });
|
|
406
411
|
}
|
|
407
|
-
if (!await n.env.data.tenants.get(r)) throw new
|
|
412
|
+
if (!await n.env.data.tenants.get(r)) throw new _(404, { message: "Tenant not found" });
|
|
408
413
|
let a = {
|
|
409
414
|
adapters: n.env.data,
|
|
410
415
|
ctx: n
|
|
411
416
|
};
|
|
412
417
|
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(
|
|
418
|
+
}), n.openapi(h({
|
|
414
419
|
tags: ["tenants", "settings"],
|
|
415
420
|
method: "get",
|
|
416
421
|
path: "/settings",
|
|
417
|
-
request: { headers:
|
|
422
|
+
request: { headers: g.object({ "tenant-id": g.string().optional() }) },
|
|
418
423
|
security: [{ Bearer: ["read:tenants"] }],
|
|
419
424
|
responses: { 200: {
|
|
420
|
-
content: { "application/json": { schema:
|
|
425
|
+
content: { "application/json": { schema: p } },
|
|
421
426
|
description: "Current tenant settings"
|
|
422
427
|
} }
|
|
423
428
|
}), async (e) => {
|
|
424
429
|
let t = await e.env.data.tenants.get(e.var.tenant_id);
|
|
425
|
-
if (!t) throw new
|
|
430
|
+
if (!t) throw new _(404, { message: "Tenant not found" });
|
|
426
431
|
return e.json(t);
|
|
427
|
-
}), n.openapi(
|
|
432
|
+
}), n.openapi(h({
|
|
428
433
|
tags: ["tenants", "settings"],
|
|
429
434
|
method: "patch",
|
|
430
435
|
path: "/settings",
|
|
431
436
|
request: {
|
|
432
|
-
headers:
|
|
433
|
-
body: { content: { "application/json": { schema:
|
|
437
|
+
headers: g.object({ "tenant-id": g.string().optional() }),
|
|
438
|
+
body: { content: { "application/json": { schema: g.object(f.shape).partial() } } }
|
|
434
439
|
},
|
|
435
440
|
security: [{ Bearer: ["update:tenants"] }],
|
|
436
441
|
responses: { 200: {
|
|
437
|
-
content: { "application/json": { schema:
|
|
442
|
+
content: { "application/json": { schema: p } },
|
|
438
443
|
description: "Updated tenant settings"
|
|
439
444
|
} }
|
|
440
445
|
}), async (e) => {
|
|
441
446
|
let { id: t, ...n } = e.req.valid("json"), r = await e.env.data.tenants.get(e.var.tenant_id);
|
|
442
|
-
if (!r) throw new
|
|
443
|
-
let i =
|
|
447
|
+
if (!r) throw new _(404, { message: "Tenant not found" });
|
|
448
|
+
let i = s(r, n);
|
|
444
449
|
await e.env.data.tenants.update(e.var.tenant_id, i);
|
|
445
450
|
let a = await e.env.data.tenants.get(e.var.tenant_id);
|
|
446
|
-
if (!a) throw new
|
|
451
|
+
if (!a) throw new _(500, { message: "Failed to retrieve updated tenant" });
|
|
447
452
|
return e.json(a);
|
|
448
453
|
}), n;
|
|
449
454
|
}
|
|
450
455
|
//#endregion
|
|
451
456
|
//#region src/middleware/protect-synced.ts
|
|
452
|
-
function
|
|
457
|
+
function N(e) {
|
|
453
458
|
for (let { pattern: t, type: n } of [
|
|
454
459
|
{
|
|
455
460
|
pattern: /\/api\/v2\/resource-servers\/([^/]+)$/,
|
|
@@ -472,7 +477,7 @@ function A(e) {
|
|
|
472
477
|
}
|
|
473
478
|
return null;
|
|
474
479
|
}
|
|
475
|
-
async function
|
|
480
|
+
async function re(e, t, n) {
|
|
476
481
|
try {
|
|
477
482
|
switch (n.type) {
|
|
478
483
|
case "resource_server": return (await e.resourceServers.get(t, n.id))?.is_system === !0;
|
|
@@ -484,47 +489,47 @@ async function j(e, t, n) {
|
|
|
484
489
|
return !1;
|
|
485
490
|
}
|
|
486
491
|
}
|
|
487
|
-
function
|
|
492
|
+
function ie(e) {
|
|
488
493
|
return {
|
|
489
494
|
resource_server: "resource server",
|
|
490
495
|
role: "role",
|
|
491
496
|
connection: "connection"
|
|
492
497
|
}[e];
|
|
493
498
|
}
|
|
494
|
-
function
|
|
499
|
+
function P() {
|
|
495
500
|
return async (e, t) => {
|
|
496
501
|
if (![
|
|
497
502
|
"PATCH",
|
|
498
503
|
"PUT",
|
|
499
504
|
"DELETE"
|
|
500
505
|
].includes(e.req.method)) return t();
|
|
501
|
-
let n =
|
|
506
|
+
let n = N(e.req.path);
|
|
502
507
|
if (!n) return t();
|
|
503
508
|
let r = e.var.tenant_id || e.req.header("x-tenant-id") || e.req.header("tenant-id");
|
|
504
509
|
if (!r) return t();
|
|
505
|
-
if (await
|
|
510
|
+
if (await re(e.env.data, r, n)) throw new _(403, { message: `This ${ie(n.type)} is a system resource and cannot be modified. Make changes in the control plane instead.` });
|
|
506
511
|
return t();
|
|
507
512
|
};
|
|
508
513
|
}
|
|
509
514
|
//#endregion
|
|
510
515
|
//#region src/middleware/settings-inheritance.ts
|
|
511
|
-
function
|
|
516
|
+
function F(e, t) {
|
|
512
517
|
let n = t.find((t) => t.strategy === e.strategy);
|
|
513
518
|
if (!n?.options) return e;
|
|
514
|
-
let r =
|
|
519
|
+
let r = o.passthrough().parse({
|
|
515
520
|
...n,
|
|
516
521
|
...e
|
|
517
522
|
});
|
|
518
|
-
return r.options =
|
|
523
|
+
return r.options = a.passthrough().parse({
|
|
519
524
|
...n.options || {},
|
|
520
525
|
...e.options
|
|
521
526
|
}), r;
|
|
522
527
|
}
|
|
523
|
-
function
|
|
528
|
+
function I(e, t) {
|
|
524
529
|
let n = [...t || [], ...e || []];
|
|
525
530
|
return [...new Set(n)];
|
|
526
531
|
}
|
|
527
|
-
function
|
|
532
|
+
function ae(e, t) {
|
|
528
533
|
if (!t?.length) return e || [];
|
|
529
534
|
if (!e?.length) return t;
|
|
530
535
|
let n = /* @__PURE__ */ new Map();
|
|
@@ -535,19 +540,19 @@ function I(e, t) {
|
|
|
535
540
|
function L(e, t) {
|
|
536
541
|
return t ? {
|
|
537
542
|
...e,
|
|
538
|
-
scopes:
|
|
543
|
+
scopes: ae(e.scopes, t.scopes)
|
|
539
544
|
} : e;
|
|
540
545
|
}
|
|
541
|
-
function
|
|
546
|
+
function oe(e, t) {
|
|
542
547
|
return t ? {
|
|
543
548
|
...e,
|
|
544
|
-
callbacks:
|
|
545
|
-
web_origins:
|
|
546
|
-
allowed_logout_urls:
|
|
547
|
-
allowed_origins:
|
|
549
|
+
callbacks: I(e.callbacks, t.callbacks),
|
|
550
|
+
web_origins: I(e.web_origins, t.web_origins),
|
|
551
|
+
allowed_logout_urls: I(e.allowed_logout_urls, t.allowed_logout_urls),
|
|
552
|
+
allowed_origins: I(e.allowed_origins, t.allowed_origins)
|
|
548
553
|
} : e;
|
|
549
554
|
}
|
|
550
|
-
function
|
|
555
|
+
function R(e) {
|
|
551
556
|
let { controlPlaneTenantId: t, controlPlaneClientId: n, resolveControlPlane: r } = e;
|
|
552
557
|
if (r) return async (e) => r({ tenant_id: e });
|
|
553
558
|
if (!t) return async () => void 0;
|
|
@@ -557,7 +562,7 @@ function z(e) {
|
|
|
557
562
|
};
|
|
558
563
|
return async () => i;
|
|
559
564
|
}
|
|
560
|
-
function
|
|
565
|
+
function z(e, t) {
|
|
561
566
|
return {
|
|
562
567
|
...e.resourceServers,
|
|
563
568
|
get: async (n, r) => {
|
|
@@ -584,18 +589,18 @@ function B(e, t) {
|
|
|
584
589
|
}
|
|
585
590
|
};
|
|
586
591
|
}
|
|
587
|
-
function
|
|
588
|
-
let n =
|
|
592
|
+
function B(e, t) {
|
|
593
|
+
let n = R({
|
|
589
594
|
controlPlaneTenantId: t.controlPlaneTenantId,
|
|
590
595
|
resolveControlPlane: t.resolveControlPlane
|
|
591
596
|
});
|
|
592
597
|
return {
|
|
593
598
|
...e,
|
|
594
|
-
resourceServers:
|
|
599
|
+
resourceServers: z(e, n)
|
|
595
600
|
};
|
|
596
601
|
}
|
|
597
|
-
function
|
|
598
|
-
let { controlPlaneTenantId: n, controlPlaneClientId: r, resolveControlPlane: i } = t, a =
|
|
602
|
+
function V(e, t) {
|
|
603
|
+
let { controlPlaneTenantId: n, controlPlaneClientId: r, resolveControlPlane: i } = t, a = R({
|
|
599
604
|
controlPlaneTenantId: n,
|
|
600
605
|
controlPlaneClientId: r,
|
|
601
606
|
resolveControlPlane: i
|
|
@@ -613,12 +618,12 @@ function H(e, t) {
|
|
|
613
618
|
let r = await e.connections.get(t, n);
|
|
614
619
|
if (!r) return r;
|
|
615
620
|
let i = await a(t);
|
|
616
|
-
return !i || t === i.tenantId ? r :
|
|
621
|
+
return !i || t === i.tenantId ? r : F(r, (await e.connections.list(i.tenantId)).connections || []);
|
|
617
622
|
},
|
|
618
623
|
list: async (t, n) => {
|
|
619
624
|
let r = await e.connections.list(t, n), i = await a(t);
|
|
620
625
|
if (!i || t === i.tenantId) return r;
|
|
621
|
-
let o = await e.connections.list(i.tenantId), s = r.connections.map((e) =>
|
|
626
|
+
let o = await e.connections.list(i.tenantId), s = r.connections.map((e) => F(e, o.connections || []));
|
|
622
627
|
return {
|
|
623
628
|
...r,
|
|
624
629
|
connections: s
|
|
@@ -633,7 +638,7 @@ function H(e, t) {
|
|
|
633
638
|
let i = await a(t);
|
|
634
639
|
if (!i || t === i.tenantId) return r;
|
|
635
640
|
let o = await e.connections.list(i.tenantId);
|
|
636
|
-
return r.map((e) =>
|
|
641
|
+
return r.map((e) => F(e, o.connections || []));
|
|
637
642
|
}
|
|
638
643
|
},
|
|
639
644
|
emailProviders: {
|
|
@@ -645,22 +650,22 @@ function H(e, t) {
|
|
|
645
650
|
return !r || t === r.tenantId ? null : e.emailProviders.get(r.tenantId);
|
|
646
651
|
}
|
|
647
652
|
},
|
|
648
|
-
resourceServers:
|
|
649
|
-
hooks:
|
|
653
|
+
resourceServers: z(e, a),
|
|
654
|
+
hooks: se(e, a)
|
|
650
655
|
};
|
|
651
656
|
}
|
|
652
|
-
function
|
|
657
|
+
function H(e) {
|
|
653
658
|
if (!e || typeof e != "object") return !1;
|
|
654
659
|
let t = e.metadata;
|
|
655
660
|
return !t || typeof t != "object" ? !1 : t.inheritable === !0;
|
|
656
661
|
}
|
|
657
|
-
function
|
|
662
|
+
function se(e, t) {
|
|
658
663
|
return {
|
|
659
664
|
...e.hooks,
|
|
660
665
|
list: async (n, r) => {
|
|
661
666
|
let i = await e.hooks.list(n, r), a = await t(n);
|
|
662
667
|
if (!a || n === a.tenantId) return i;
|
|
663
|
-
let o = ((await e.hooks.list(a.tenantId, r)).hooks || []).filter(
|
|
668
|
+
let o = ((await e.hooks.list(a.tenantId, r)).hooks || []).filter(H);
|
|
664
669
|
if (o.length === 0) return i;
|
|
665
670
|
let s = new Set((i.hooks || []).map((e) => e.hook_id)), c = o.filter((e) => !s.has(e.hook_id));
|
|
666
671
|
return {
|
|
@@ -675,31 +680,31 @@ function W(e, t) {
|
|
|
675
680
|
let a = await t(n);
|
|
676
681
|
if (!a || n === a.tenantId) return i;
|
|
677
682
|
let o = await e.hooks.get(a.tenantId, r);
|
|
678
|
-
return o &&
|
|
683
|
+
return o && H(o) ? o : null;
|
|
679
684
|
}
|
|
680
685
|
};
|
|
681
686
|
}
|
|
682
|
-
function
|
|
683
|
-
return
|
|
687
|
+
function U(e, t) {
|
|
688
|
+
return V(e, t);
|
|
684
689
|
}
|
|
685
690
|
//#endregion
|
|
686
691
|
//#region src/middleware/index.ts
|
|
687
|
-
function
|
|
692
|
+
function W(e) {
|
|
688
693
|
return async (t, n) => {
|
|
689
694
|
let r = t.var.user;
|
|
690
695
|
return r?.tenant_id === e && r.org_name && t.set("tenant_id", r.org_name), n();
|
|
691
696
|
};
|
|
692
697
|
}
|
|
693
|
-
function
|
|
698
|
+
function G(e) {
|
|
694
699
|
return async (n, r) => {
|
|
695
700
|
if (!e.accessControl) return r();
|
|
696
701
|
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
|
|
698
|
-
if (!
|
|
702
|
+
if (!c && s && u && (n.set("tenant_id", s), c = s), !c) throw new _(400, { message: "Tenant ID not found in request" });
|
|
703
|
+
if (!y(o, c, i, a)) throw new _(403, { message: `Access denied to tenant ${c}` });
|
|
699
704
|
return r();
|
|
700
705
|
};
|
|
701
706
|
}
|
|
702
|
-
function
|
|
707
|
+
function K(e) {
|
|
703
708
|
return async (t, n) => {
|
|
704
709
|
if (!e.subdomainRouting) return n();
|
|
705
710
|
let { baseDomain: r, reservedSubdomains: i = [], resolveSubdomain: a } = e.subdomainRouting, o = t.req.header("x-forwarded-host") || t.req.header("host") || "", s = null;
|
|
@@ -714,43 +719,43 @@ function J(e) {
|
|
|
714
719
|
let n = await t.env.data.organizations.get(e.accessControl.controlPlaneTenantId, s);
|
|
715
720
|
n && (c = n.id);
|
|
716
721
|
} catch {}
|
|
717
|
-
if (!c) throw new
|
|
722
|
+
if (!c) throw new _(404, { message: `Tenant not found for subdomain: ${s}` });
|
|
718
723
|
return t.set("tenant_id", c), n();
|
|
719
724
|
};
|
|
720
725
|
}
|
|
721
|
-
function
|
|
726
|
+
function q(e) {
|
|
722
727
|
return async (t, n) => {
|
|
723
728
|
if (!e.databaseIsolation) return n();
|
|
724
729
|
let r = t.var.tenant_id;
|
|
725
|
-
if (!r) throw new
|
|
730
|
+
if (!r) throw new _(400, { message: "Tenant ID not found in request" });
|
|
726
731
|
try {
|
|
727
732
|
let n = await e.databaseIsolation.getAdapters(r);
|
|
728
733
|
t.env.data = n;
|
|
729
734
|
} catch (e) {
|
|
730
|
-
throw console.error(`Failed to resolve database for tenant ${r}:`, e), new
|
|
735
|
+
throw console.error(`Failed to resolve database for tenant ${r}:`, e), new _(500, { message: "Failed to resolve tenant database" });
|
|
731
736
|
}
|
|
732
737
|
return n();
|
|
733
738
|
};
|
|
734
739
|
}
|
|
735
|
-
function
|
|
736
|
-
let t =
|
|
740
|
+
function J(e) {
|
|
741
|
+
let t = K(e), n = G(e), r = q(e);
|
|
737
742
|
return async (e, i) => (await t(e, async () => {}), await n(e, async () => {}), await r(e, async () => {}), i());
|
|
738
743
|
}
|
|
739
744
|
//#endregion
|
|
740
745
|
//#region src/init.ts
|
|
741
|
-
function
|
|
746
|
+
function ce(e) {
|
|
742
747
|
let { dataAdapter: t, controlPlane: n, controlPlane: { tenantId: r = "control_plane", clientId: i } = {}, resolveControlPlane: a, sync: o = {
|
|
743
748
|
resourceServers: !0,
|
|
744
749
|
roles: !0
|
|
745
|
-
}, defaultPermissions:
|
|
750
|
+
}, defaultPermissions: s = ["tenant:admin"], requireOrganizationMatch: l = !1, managementApiExtensions: d = [], entityHooks: f, getChildTenantIds: p, getAdapters: m, ...h } = e;
|
|
746
751
|
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
752
|
let g = t, _ = t;
|
|
748
|
-
n && (g =
|
|
753
|
+
n && (g = U(t, {
|
|
749
754
|
controlPlaneTenantId: r,
|
|
750
755
|
controlPlaneClientId: i,
|
|
751
756
|
resolveControlPlane: a
|
|
752
757
|
}), _ = {
|
|
753
|
-
...
|
|
758
|
+
...B(t, {
|
|
754
759
|
controlPlaneTenantId: r,
|
|
755
760
|
resolveControlPlane: a
|
|
756
761
|
}),
|
|
@@ -760,46 +765,46 @@ function Z(e) {
|
|
|
760
765
|
resolveControlPlane: a
|
|
761
766
|
}
|
|
762
767
|
});
|
|
763
|
-
let
|
|
768
|
+
let v = o !== !1, y = v ? {
|
|
764
769
|
resourceServers: o.resourceServers ?? !0,
|
|
765
770
|
roles: o.roles ?? !0
|
|
766
771
|
} : {
|
|
767
772
|
resourceServers: !1,
|
|
768
773
|
roles: !1
|
|
769
|
-
}, { entityHooks:
|
|
774
|
+
}, { entityHooks: b, tenantHooks: x } = j({
|
|
770
775
|
controlPlaneTenantId: r,
|
|
771
|
-
getChildTenantIds: p ?? (async () => (await
|
|
776
|
+
getChildTenantIds: p ?? (async () => (await c((e) => g.tenants.list(e), "tenants", {
|
|
772
777
|
cursorField: "id",
|
|
773
778
|
pageSize: 100
|
|
774
779
|
})).filter((e) => e.id !== r).map((e) => e.id)),
|
|
775
780
|
getAdapters: m ?? (async () => g),
|
|
776
781
|
getControlPlaneAdapters: async () => g,
|
|
777
|
-
sync:
|
|
782
|
+
sync: y
|
|
778
783
|
}), C = {
|
|
779
|
-
resourceServers: [
|
|
780
|
-
roles: [
|
|
784
|
+
resourceServers: [b.resourceServers, ...f?.resourceServers ?? []],
|
|
785
|
+
roles: [b.roles, ...f?.roles ?? []],
|
|
781
786
|
connections: f?.connections ?? [],
|
|
782
787
|
tenants: f?.tenants ?? [],
|
|
783
788
|
rolePermissions: f?.rolePermissions ?? []
|
|
784
|
-
}, w =
|
|
789
|
+
}, w = S({ accessControl: {
|
|
785
790
|
controlPlaneTenantId: r,
|
|
786
|
-
requireOrganizationMatch:
|
|
787
|
-
defaultPermissions:
|
|
788
|
-
} }), T =
|
|
791
|
+
requireOrganizationMatch: l,
|
|
792
|
+
defaultPermissions: s
|
|
793
|
+
} }), T = M({ accessControl: {
|
|
789
794
|
controlPlaneTenantId: r,
|
|
790
|
-
requireOrganizationMatch:
|
|
791
|
-
defaultPermissions:
|
|
795
|
+
requireOrganizationMatch: l,
|
|
796
|
+
defaultPermissions: s
|
|
792
797
|
} }, { tenants: {
|
|
793
798
|
async beforeCreate(e, t) {
|
|
794
|
-
return w.beforeCreate && (t = await w.beforeCreate(e, t)),
|
|
799
|
+
return w.beforeCreate && (t = await w.beforeCreate(e, t)), x.beforeCreate && (t = await x.beforeCreate(e, t)), t;
|
|
795
800
|
},
|
|
796
801
|
async afterCreate(e, t) {
|
|
797
|
-
await w.afterCreate?.(e, t), await
|
|
802
|
+
await w.afterCreate?.(e, t), await x.afterCreate?.(e, t);
|
|
798
803
|
},
|
|
799
804
|
async beforeDelete(e, t) {
|
|
800
|
-
await w.beforeDelete?.(e, t), await
|
|
805
|
+
await w.beforeDelete?.(e, t), await x.beforeDelete?.(e, t);
|
|
801
806
|
}
|
|
802
|
-
} }), { app: E } =
|
|
807
|
+
} }), { app: E } = u({
|
|
803
808
|
dataAdapter: g,
|
|
804
809
|
managementDataAdapter: _,
|
|
805
810
|
...h,
|
|
@@ -809,22 +814,116 @@ function Z(e) {
|
|
|
809
814
|
router: T
|
|
810
815
|
}]
|
|
811
816
|
});
|
|
812
|
-
return E.use("/api/v2/*",
|
|
817
|
+
return E.use("/api/v2/*", W(r)), v && E.use("/api/v2/*", P()), {
|
|
813
818
|
app: E,
|
|
814
819
|
controlPlaneTenantId: r
|
|
815
820
|
};
|
|
816
821
|
}
|
|
817
822
|
//#endregion
|
|
823
|
+
//#region src/rollout/defaults-projection.ts
|
|
824
|
+
function Y() {
|
|
825
|
+
return {
|
|
826
|
+
upserted: 0,
|
|
827
|
+
errors: []
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
function le(e) {
|
|
831
|
+
let t = e.metadata;
|
|
832
|
+
return !!(t && t.inheritable === !0);
|
|
833
|
+
}
|
|
834
|
+
async function X(e, t, n, r) {
|
|
835
|
+
try {
|
|
836
|
+
await r();
|
|
837
|
+
} catch (r) {
|
|
838
|
+
let i = `${t}: ${r instanceof Error ? r.message : String(r)}`;
|
|
839
|
+
if (!n) throw Error(i, { cause: r });
|
|
840
|
+
e.errors.push(i);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
async function Z(e, t) {
|
|
844
|
+
let { controlPlaneTenantId: n, getControlPlaneAdapters: r, getAdapters: a, entities: o = {}, continueOnError: s = !1 } = e, u = {
|
|
845
|
+
connections: o.connections ?? !0,
|
|
846
|
+
resourceServers: o.resourceServers ?? !0,
|
|
847
|
+
hooks: o.hooks ?? !0,
|
|
848
|
+
emailProvider: o.emailProvider ?? !0,
|
|
849
|
+
branding: o.branding ?? !0,
|
|
850
|
+
promptSettings: o.promptSettings ?? !0
|
|
851
|
+
}, f = await r(), p = await a(t), m = {
|
|
852
|
+
tenantId: t,
|
|
853
|
+
connections: Y(),
|
|
854
|
+
resourceServers: Y(),
|
|
855
|
+
hooks: Y(),
|
|
856
|
+
emailProvider: Y(),
|
|
857
|
+
branding: Y(),
|
|
858
|
+
promptSettings: Y()
|
|
859
|
+
};
|
|
860
|
+
if (u.connections) {
|
|
861
|
+
let e = await c((e) => f.connections.list(n, e), "connections", {
|
|
862
|
+
cursorField: "id",
|
|
863
|
+
pageSize: 100
|
|
864
|
+
});
|
|
865
|
+
for (let t of e) {
|
|
866
|
+
let e = t.id;
|
|
867
|
+
e && await X(m.connections, `connection ${e}`, s, async () => {
|
|
868
|
+
let r = i.parse(t);
|
|
869
|
+
await p.connections.get(n, e) ? await p.connections.update(n, e, r) : await p.connections.create(n, r), m.connections.upserted += 1;
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (u.resourceServers) {
|
|
874
|
+
let e = await c((e) => f.resourceServers.list(n, e), "resource_servers", {
|
|
875
|
+
cursorField: "id",
|
|
876
|
+
pageSize: 100
|
|
877
|
+
});
|
|
878
|
+
for (let t of e) !t.is_system || !t.id || await X(m.resourceServers, `resource_server ${t.id}`, s, async () => {
|
|
879
|
+
let e = d.parse(t);
|
|
880
|
+
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;
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
if (u.hooks) {
|
|
884
|
+
let e = await c((e) => f.hooks.list(n, e), "hooks", {
|
|
885
|
+
cursorField: "hook_id",
|
|
886
|
+
pageSize: 100
|
|
887
|
+
});
|
|
888
|
+
for (let t of e) !le(t) || !t.hook_id || await X(m.hooks, `hook ${t.hook_id}`, s, async () => {
|
|
889
|
+
let e = l.parse(t);
|
|
890
|
+
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;
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
return u.emailProvider && await X(m.emailProvider, "email_provider", s, async () => {
|
|
894
|
+
let e = await f.emailProviders.get(n);
|
|
895
|
+
e && (await p.emailProviders.get(n) ? await p.emailProviders.update(n, e) : await p.emailProviders.create(n, e), m.emailProvider.upserted += 1);
|
|
896
|
+
}), u.branding && await X(m.branding, "branding", s, async () => {
|
|
897
|
+
let e = await f.branding.get(n);
|
|
898
|
+
e && (await p.branding.set(n, e), m.branding.upserted += 1);
|
|
899
|
+
}), u.promptSettings && await X(m.promptSettings, "prompt_settings", s, async () => {
|
|
900
|
+
let e = await f.promptSettings.get(n);
|
|
901
|
+
e && (await p.promptSettings.set(n, e), m.promptSettings.upserted += 1);
|
|
902
|
+
}), m;
|
|
903
|
+
}
|
|
904
|
+
//#endregion
|
|
905
|
+
//#region src/rollout/index.ts
|
|
906
|
+
function ue(e) {
|
|
907
|
+
return {
|
|
908
|
+
syncDefaults: (t) => Z(e, t),
|
|
909
|
+
syncDefaultsToTenants: async (t) => {
|
|
910
|
+
let n = [];
|
|
911
|
+
for (let r of t) n.push(await Z(e, r));
|
|
912
|
+
return n;
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
//#endregion
|
|
818
917
|
//#region src/plugin.ts
|
|
819
|
-
function
|
|
918
|
+
function de(e) {
|
|
820
919
|
let t = Q(e);
|
|
821
920
|
return {
|
|
822
921
|
name: "multi-tenancy",
|
|
823
|
-
middleware:
|
|
922
|
+
middleware: J(e),
|
|
824
923
|
hooks: t,
|
|
825
924
|
routes: [{
|
|
826
925
|
path: "/management",
|
|
827
|
-
handler:
|
|
926
|
+
handler: M(e, t)
|
|
828
927
|
}],
|
|
829
928
|
onRegister: async () => {
|
|
830
929
|
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 +933,7 @@ function ee(e) {
|
|
|
834
933
|
//#endregion
|
|
835
934
|
//#region src/index.ts
|
|
836
935
|
function Q(e) {
|
|
837
|
-
let t = e.accessControl ?
|
|
936
|
+
let t = e.accessControl ? v(e.accessControl) : {}, n = e.databaseIsolation ? b(e.databaseIsolation) : {}, r = S(e);
|
|
838
937
|
return {
|
|
839
938
|
...t,
|
|
840
939
|
...n,
|
|
@@ -843,19 +942,19 @@ function Q(e) {
|
|
|
843
942
|
}
|
|
844
943
|
function $(t) {
|
|
845
944
|
let n = new e(), r = Q(t);
|
|
846
|
-
return n.route("/tenants",
|
|
945
|
+
return n.route("/tenants", M(t, r)), n;
|
|
847
946
|
}
|
|
848
|
-
function
|
|
947
|
+
function fe(e) {
|
|
849
948
|
return {
|
|
850
949
|
hooks: Q(e),
|
|
851
|
-
middleware:
|
|
950
|
+
middleware: J(e),
|
|
852
951
|
app: $(e),
|
|
853
952
|
config: e,
|
|
854
|
-
wrapAdapters: (t, n) =>
|
|
953
|
+
wrapAdapters: (t, n) => U(t, {
|
|
855
954
|
controlPlaneTenantId: e.accessControl?.controlPlaneTenantId,
|
|
856
955
|
controlPlaneClientId: n?.controlPlaneClientId
|
|
857
956
|
})
|
|
858
957
|
};
|
|
859
958
|
}
|
|
860
959
|
//#endregion
|
|
861
|
-
export {
|
|
960
|
+
export { v as createAccessControlHooks, G as createAccessControlMiddleware, W as createControlPlaneTenantMiddleware, b as createDatabaseHooks, q as createDatabaseMiddleware, ue as createDirectRolloutAdapter, $ as createMultiTenancy, Q as createMultiTenancyHooks, J as createMultiTenancyMiddleware, de as createMultiTenancyPlugin, P as createProtectSyncedMiddleware, S as createProvisioningHooks, V as createRuntimeFallbackAdapter, K as createSubdomainMiddleware, j as createSyncHooks, M as createTenantsOpenAPIRouter, ce as initMultiTenant, oe as mergeClientWithFallback, Z as projectControlPlaneDefaults, fe as setupMultiTenancy, y as validateTenantAccess, U as withRuntimeFallback, B as withSystemResourceServerInheritance };
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenants.d.ts","sourceRoot":"","sources":["../../../src/routes/tenants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAUhE,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,EAElB,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"tenants.d.ts","sourceRoot":"","sources":["../../../src/routes/tenants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAUhE,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,EAElB,MAAM,UAAU,CAAC;AAwClB;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,iBAAiB;cAGZ,oBAAoB;eACnB,qBAAqB;YAggBnC"}
|
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.
|
|
14
|
+
"version": "14.24.1",
|
|
15
15
|
"description": "Multi-tenancy support for AuthHero with organization-based access control and per-tenant database isolation",
|
|
16
16
|
"files": [
|
|
17
17
|
"dist"
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"vite": "^8.0.14",
|
|
38
38
|
"vitest": "^4.1.7",
|
|
39
39
|
"@authhero/adapter-interfaces": "3.1.1",
|
|
40
|
-
"@authhero/kysely-adapter": "11.8.
|
|
41
|
-
"authhero": "
|
|
40
|
+
"@authhero/kysely-adapter": "11.8.9",
|
|
41
|
+
"authhero": "8.4.1"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"zod": "^4.4.3"
|