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