@authhero/multi-tenancy 14.16.0 → 14.17.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
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var oe=Object.defineProperty;var ie=(e,t,n)=>t in e?oe(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var k=(e,t,n)=>ie(e,typeof t!="symbol"?t+"":t,n);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const ce=require("hono"),T=require("authhero"),_=require("@hono/zod-openapi");function J(e){const{controlPlaneTenantId:t,requireOrganizationMatch:n=!0}=e;return{async onTenantAccessValidation(r,a){if(a===t)return!0;if(n){const o=r.var.org_name,s=r.var.organization_id,i=o||s;return i?i.toLowerCase()===a.toLowerCase():!1}return!0}}}function X(e,t,n,r){if(t===n)return!0;const a=r||e;return a?a.toLowerCase()===t.toLowerCase():!1}function Y(e){return{async resolveDataAdapters(t){try{return await e.getAdapters(t)}catch(n){console.error(`Failed to resolve data adapters for tenant ${t}:`,n);return}}}}function le(e){return`urn:authhero:tenant:${e.toLowerCase()}`}function N(e){return{async beforeCreate(t,n){return!n.audience&&n.id?{...n,audience:le(n.id)}:n},async afterCreate(t,n){const{accessControl:r,databaseIsolation:a}=e;r&&t.ctx&&await ue(t,n,r),a!=null&&a.onProvision&&await a.onProvision(n.id)},async beforeDelete(t,n){const{accessControl:r,databaseIsolation:a}=e;if(r)try{const s=(await t.adapters.organizations.list(r.controlPlaneTenantId)).organizations.find(i=>i.name===n);s&&await t.adapters.organizations.remove(r.controlPlaneTenantId,s.id)}catch(o){console.warn(`Failed to remove organization for tenant ${n}:`,o)}if(a!=null&&a.onDeprovision)try{await a.onDeprovision(n)}catch(o){console.warn(`Failed to deprovision database for tenant ${n}:`,o)}}}}async function ue(e,t,n){const{controlPlaneTenantId:r,defaultPermissions:a,defaultRoles:o,issuer:s,adminRoleName:i="Tenant Admin",adminRoleDescription:u="Full access to all tenant management operations",addCreatorToOrganization:c=!0}=n,l=await e.adapters.organizations.create(r,{name:t.id,display_name:t.friendly_name||t.id});let g;if(s&&(g=await fe(e,r,i,u)),c&&e.ctx){const d=e.ctx.var.user;if(d!=null&&d.sub&&!await de(e,r,d.sub))try{await e.adapters.userOrganizations.create(r,{user_id:d.sub,organization_id:l.id}),g&&await e.adapters.userRoles.create(r,d.sub,g,l.id)}catch(m){console.warn(`Failed to add creator ${d.sub} to organization ${l.id}:`,m)}}o&&o.length>0&&console.log(`Would assign roles ${o.join(", ")} to organization ${l.id}`),a&&a.length>0&&console.log(`Would grant permissions ${a.join(", ")} to organization ${l.id}`)}async function de(e,t,n){const r=await e.adapters.userRoles.list(t,n,void 0,"");for(const a of r)if((await e.adapters.rolePermissions.list(t,a.id,{per_page:1e3})).some(i=>i.permission_name==="admin:organizations"))return!0;return!1}async function fe(e,t,n,r){const o=(await e.adapters.roles.list(t,{})).roles.find(c=>c.name===n);if(o)return o.id;const s=await e.adapters.roles.create(t,{name:n,description:r}),i=T.MANAGEMENT_API_AUDIENCE,u=T.MANAGEMENT_API_SCOPES.map(c=>({role_id:s.id,resource_server_identifier:i,permission_name:c.value}));return await e.adapters.rolePermissions.assign(t,s.id,u),s.id}function U(e,t,n=()=>!0){const{controlPlaneTenantId:r,getChildTenantIds:a,getAdapters:o}=e,s=new Map;async function i(l,g,d){return(await t(l).list(g,{q:`name:${d}`,per_page:1}))[0]??null}async function u(l){const g=await a(),d=t(await o(r));await Promise.all(g.map(async f=>{try{const m=await o(f),w=t(m),y={...d.transform(l),is_system:!0},C=await i(m,f,l.name),A=C?w.getId(C):void 0;if(C&&A){const S=w.preserveOnUpdate?w.preserveOnUpdate(C,y):y;await w.update(f,A,S)}else await w.create(f,y)}catch(m){console.error(`Failed to sync ${d.listKey} "${l.name}" to tenant "${f}":`,m)}}))}async function c(l){const g=await a();await Promise.all(g.map(async d=>{try{const f=await o(d),m=t(f),w=await i(f,d,l),b=w?m.getId(w):void 0;w&&b&&await m.remove(d,b)}catch(f){console.error(`Failed to delete entity "${l}" from tenant "${d}":`,f)}}))}return{afterCreate:async(l,g)=>{l.tenantId===r&&n(g)&&await u(g)},afterUpdate:async(l,g,d)=>{l.tenantId===r&&n(d)&&await u(d)},beforeDelete:async(l,g)=>{if(l.tenantId!==r)return;const f=await t(l.adapters).get(l.tenantId,g);f&&n(f)&&s.set(g,f)},afterDelete:async(l,g)=>{if(l.tenantId!==r)return;const d=s.get(g);d&&(s.delete(g),await c(d.name))}}}function G(e,t,n=()=>!0){const{controlPlaneTenantId:r,getControlPlaneAdapters:a,getAdapters:o}=e;return{async afterCreate(s,i){if(i.id!==r)try{const u=await a(),c=await o(i.id),l=t(u),g=t(c),d=await T.fetchAll(f=>l.listPaginated(r,f),l.listKey,{cursorField:"id",pageSize:100});await Promise.all(d.filter(f=>n(f)).map(async f=>{try{const m=l.transform(f);await g.create(i.id,{...m,is_system:!0})}catch(m){console.error(`Failed to sync entity to new tenant "${i.id}":`,m)}}))}catch(u){console.error(`Failed to sync entities to new tenant "${i.id}":`,u)}}}}const L=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:t=>t.id,transform:t=>({id:t.id,name:t.name,identifier:t.identifier,scopes:t.scopes,signing_alg:t.signing_alg,token_lifetime:t.token_lifetime,token_lifetime_for_web:t.token_lifetime_for_web})}),W=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:t=>t.id,transform:t=>({id:t.id,name:t.name,description:t.description})});function K(e){var t;return((t=e.metadata)==null?void 0:t.sync)!==!1}function Z(e){const{sync:t={},filters:n={}}=e,r=t.resourceServers??!0,a=t.roles??!0,o=m=>K(m)?n.resourceServers?n.resourceServers(m):!0:!1,s=m=>K(m)?n.roles?n.roles(m):!0:!1,i=r?U(e,L,o):void 0,u=a?U(e,W,s):void 0,c=r?G(e,L,o):void 0,l=a?G(e,W,s):void 0,g=a?{async afterCreate(m,w){var b;if(w.id!==e.controlPlaneTenantId){await((b=l==null?void 0:l.afterCreate)==null?void 0:b.call(l,m,w));try{const y=await e.getControlPlaneAdapters(),C=await e.getAdapters(w.id),A=await T.fetchAll(p=>y.roles.list(e.controlPlaneTenantId,p),"roles",{cursorField:"id",pageSize:100}),S=new Map;for(const p of A.filter(P=>{var h;return((h=n.roles)==null?void 0:h.call(n,P))??!0})){const P=await d(C,w.id,p.name);P&&S.set(p.name,P.id)}for(const p of A.filter(P=>{var h;return((h=n.roles)==null?void 0:h.call(n,P))??!0})){const P=S.get(p.name);if(P)try{const h=await y.rolePermissions.list(e.controlPlaneTenantId,p.id,{});h.length>0&&await C.rolePermissions.assign(w.id,P,h.map(R=>({role_id:P,resource_server_identifier:R.resource_server_identifier,permission_name:R.permission_name})))}catch(h){console.error(`Failed to sync permissions for role "${p.name}" to tenant "${w.id}":`,h)}}}catch(y){console.error(`Failed to sync role permissions to tenant "${w.id}":`,y)}}}}:void 0;async function d(m,w,b){return(await m.roles.list(w,{q:`name:${b}`,per_page:1})).roles[0]??null}return{entityHooks:{resourceServers:i,roles:u},tenantHooks:{async afterCreate(m,w){const b=[c==null?void 0:c.afterCreate,(g==null?void 0:g.afterCreate)??(l==null?void 0:l.afterCreate)],y=[];for(const C of b)if(C)try{await C(m,w)}catch(A){y.push(A instanceof Error?A:new Error(String(A)))}if(y.length===1)throw y[0];if(y.length>1)throw new AggregateError(y,y.map(C=>C.message).join("; "))}}}}var I=class extends Error{constructor(t=500,n){super(n==null?void 0:n.message,{cause:n==null?void 0:n.cause});k(this,"res");k(this,"status");this.res=n==null?void 0:n.res,this.status=t}getResponse(){return this.res?new Response(this.res.body,{status:this.status,headers:this.res.headers}):new Response(this.message,{status:this.status})}};function q(e,t){const n=new _.OpenAPIHono;return n.openapi(_.createRoute({tags:["tenants"],method:"get",path:"/",request:{query:T.auth0QuerySchema},security:[{Bearer:[]}],responses:{200:{content:{"application/json":{schema:_.z.object({tenants:_.z.array(T.tenantSchema),start:_.z.number().optional(),limit:_.z.number().optional(),length:_.z.number().optional()})}},description:"List of tenants"}}}),async r=>{var m,w,b,y,C,A;const a=r.req.valid("query"),{page:o,per_page:s,include_totals:i,q:u}=a,c=r.var.user,l=(c==null?void 0:c.permissions)||[];if(l.includes("auth:read")||l.includes("admin:organizations")){const S=await r.env.data.tenants.list({page:o,per_page:s,include_totals:i,q:u});return i?r.json({tenants:S.tenants,start:((m=S.totals)==null?void 0:m.start)??0,limit:((w=S.totals)==null?void 0:w.limit)??s,length:S.tenants.length}):r.json({tenants:S.tenants})}const d=((b=e.accessControl)==null?void 0:b.controlPlaneTenantId)??((y=r.env.data.multiTenancyConfig)==null?void 0:y.controlPlaneTenantId);if(d&&(c!=null&&c.sub)){const p=(await T.fetchAll(M=>r.env.data.userOrganizations.listUserOrganizations(d,c.sub,M),"organizations")).map(M=>M.name);if(p.length===0)return i?r.json({tenants:[],start:0,limit:s??50,length:0}):r.json({tenants:[]});const P=p.length,h=o??0,R=s??50,F=h*R,D=p.slice(F,F+R);if(D.length===0)return i?r.json({tenants:[],start:F,limit:R,length:P}):r.json({tenants:[]});const z=D.map(M=>`id:${M}`).join(" OR "),v=u?`(${z}) AND (${u})`:z,$=await r.env.data.tenants.list({q:v,per_page:R,include_totals:!1});return i?r.json({tenants:$.tenants,start:F,limit:R,length:P}):r.json({tenants:$.tenants})}const f=await r.env.data.tenants.list({page:o,per_page:s,include_totals:i,q:u});return i?r.json({tenants:f.tenants,start:((C=f.totals)==null?void 0:C.start)??0,limit:((A=f.totals)==null?void 0:A.limit)??s,length:f.tenants.length}):r.json({tenants:f.tenants})}),n.openapi(_.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 r=>{var u,c;const a=r.var.user;if(!(a!=null&&a.sub))throw new I(401,{message:"Authentication required to create tenants"});let o=r.req.valid("json");const s={adapters:r.env.data,ctx:r};(u=t.tenants)!=null&&u.beforeCreate&&(o=await t.tenants.beforeCreate(s,o));const i=await r.env.data.tenants.create(o);return(c=t.tenants)!=null&&c.afterCreate&&await t.tenants.afterCreate(s,i),r.json(i,201)}),n.openapi(_.createRoute({tags:["tenants"],method:"delete",path:"/{id}",request:{params:_.z.object({id:_.z.string()})},security:[{Bearer:["delete:tenants"]}],responses:{204:{description:"Tenant deleted"},403:{description:"Access denied or cannot delete the control plane"},404:{description:"Tenant not found"}}}),async r=>{var u,c,l,g;const{id:a}=r.req.valid("param"),o=((u=e.accessControl)==null?void 0:u.controlPlaneTenantId)??((c=r.env.data.multiTenancyConfig)==null?void 0:c.controlPlaneTenantId);if(o){const d=r.var.user;if(!(d!=null&&d.sub))throw new I(401,{message:"Authentication required"});if(a===o)throw new I(403,{message:"Cannot delete the control plane"});if(!(await T.fetchAll(w=>r.env.data.userOrganizations.listUserOrganizations(o,d.sub,w),"organizations")).some(w=>w.name===a))throw new I(403,{message:"Access denied to this tenant"})}if(!await r.env.data.tenants.get(a))throw new I(404,{message:"Tenant not found"});const i={adapters:r.env.data,ctx:r};return(l=t.tenants)!=null&&l.beforeDelete&&await t.tenants.beforeDelete(i,a),await r.env.data.tenants.remove(a),(g=t.tenants)!=null&&g.afterDelete&&await t.tenants.afterDelete(i,a),r.body(null,204)}),n.openapi(_.createRoute({tags:["tenants","settings"],method:"get",path:"/settings",request:{headers:_.z.object({"tenant-id":_.z.string().optional()})},security:[{Bearer:["read:tenants","auth:read"]}],responses:{200:{content:{"application/json":{schema:T.tenantSchema}},description:"Current tenant settings"}}}),async r=>{const a=await r.env.data.tenants.get(r.var.tenant_id);if(!a)throw new I(404,{message:"Tenant not found"});return r.json(a)}),n.openapi(_.createRoute({tags:["tenants","settings"],method:"patch",path:"/settings",request:{headers:_.z.object({"tenant-id":_.z.string().optional()}),body:{content:{"application/json":{schema:_.z.object(T.tenantInsertSchema.shape).partial()}}}},security:[{Bearer:["update:tenants","auth:write"]}],responses:{200:{content:{"application/json":{schema:T.tenantSchema}},description:"Updated tenant settings"}}}),async r=>{const a=r.req.valid("json"),{id:o,...s}=a,i=await r.env.data.tenants.get(r.var.tenant_id);if(!i)throw new I(404,{message:"Tenant not found"});const u=T.deepMergePatch(i,s);await r.env.data.tenants.update(r.var.tenant_id,u);const c=await r.env.data.tenants.get(r.var.tenant_id);if(!c)throw new I(500,{message:"Failed to retrieve updated tenant"});return r.json(c)}),n}function me(e){const t=[{pattern:/\/api\/v2\/resource-servers\/([^/]+)$/,type:"resource_server"},{pattern:/\/api\/v2\/roles\/([^/]+)$/,type:"role"},{pattern:/\/api\/v2\/connections\/([^/]+)$/,type:"connection"}];for(const{pattern:n,type:r}of t){const a=e.match(n);if(a&&a[1])return{type:r,id:a[1]}}return null}async function ge(e,t,n){try{switch(n.type){case"resource_server":{const r=await e.resourceServers.get(t,n.id);return(r==null?void 0:r.is_system)===!0}case"role":{const r=await e.roles.get(t,n.id);return(r==null?void 0:r.is_system)===!0}case"connection":{const r=await e.connections.get(t,n.id);return(r==null?void 0:r.is_system)===!0}default:return!1}}catch{return!1}}function we(e){return{resource_server:"resource server",role:"role",connection:"connection"}[e]}function x(){return async(e,t)=>{if(!["PATCH","PUT","DELETE"].includes(e.req.method))return t();const n=me(e.req.path);if(!n)return t();const r=e.var.tenant_id||e.req.header("x-tenant-id")||e.req.header("tenant-id");if(!r)return t();if(await ge(e.env.data,r,n))throw new I(403,{message:`This ${we(n.type)} is a system resource and cannot be modified. Make changes in the control plane instead.`});return t()}}function E(e,t){const n=t.find(a=>a.strategy===e.strategy);if(!(n!=null&&n.options))return e;const r=T.connectionSchema.passthrough().parse({...n,...e});return r.options=T.connectionOptionsSchema.passthrough().parse({...n.options||{},...e.options}),r}function j(e,t){const n=[...t||[],...e||[]];return[...new Set(n)]}function pe(e,t){if(!(t!=null&&t.length))return e||[];if(!(e!=null&&e.length))return t;const n=new Map;for(const r of t)n.set(r.value,r);for(const r of e)n.set(r.value,r);return Array.from(n.values())}function Q(e,t){return t?{...e,scopes:pe(e.scopes,t.scopes)}:e}function V(e,t){return t?{...e,callbacks:j(e.callbacks,t.callbacks),web_origins:j(e.web_origins,t.web_origins),allowed_logout_urls:j(e.allowed_logout_urls,t.allowed_logout_urls),allowed_origins:j(e.allowed_origins,t.allowed_origins)}:e}function ee(e,t){const{controlPlaneTenantId:n,controlPlaneClientId:r}=t;return{...e,multiTenancyConfig:{controlPlaneTenantId:n,controlPlaneClientId:r},connections:{...e.connections,get:async(a,o)=>{const s=await e.connections.get(a,o);if(!s||!n||a===n)return s;const i=await e.connections.list(n);return E(s,i.connections||[])},list:async(a,o)=>{const s=await e.connections.list(a,o);if(!n||a===n)return s;const i=await e.connections.list(n),u=s.connections.map(c=>E(c,i.connections||[]));return{...s,connections:u}}},clientConnections:{...e.clientConnections,listByClient:async(a,o)=>{let s=await e.clientConnections.listByClient(a,o);if(s.length===0&&(s=(await e.connections.list(a)).connections||[]),!n||a===n)return s;const i=await e.connections.list(n);return s.map(u=>E(u,i.connections||[]))}},clients:{...e.clients,get:async(a,o)=>{const s=await e.clients.get(a,o);if(!s)return null;if(!n||!r||a===n&&o===r)return s;const i=await e.clients.get(n,r);return V(s,i)},getByClientId:async a=>{const o=await e.clients.getByClientId(a);if(!o)return null;if(!n||!r||o.tenant_id===n&&o.client_id===r)return o;const s=await e.clients.get(n,r);return{...V(o,s),tenant_id:o.tenant_id}}},emailProviders:{...e.emailProviders,get:async a=>{const o=await e.emailProviders.get(a);return o||(!n||a===n?null:e.emailProviders.get(n))}},resourceServers:{...e.resourceServers,get:async(a,o)=>{const s=await e.resourceServers.get(a,o);if(!s||!n||a===n)return s;const u=(await e.resourceServers.list(n,{q:`identifier:${s.identifier}`,per_page:1})).resource_servers[0]??null;return Q(s,u)},list:async(a,o)=>{const s=await e.resourceServers.list(a,o);if(!n||a===n)return s;const i=await e.resourceServers.list(n),u=new Map(i.resource_servers.map(l=>[l.identifier,l])),c=s.resource_servers.map(l=>Q(l,u.get(l.identifier)??null));return{...s,resource_servers:c}}}}}function B(e,t){return ee(e,t)}function te(e){return async(t,n)=>{const r=t.var.user;return(r==null?void 0:r.tenant_id)===e&&r.org_name&&t.set("tenant_id",r.org_name),n()}}function ne(e){return async(t,n)=>{if(!e.accessControl)return n();const{controlPlaneTenantId:r}=e.accessControl,a=t.var.org_name,o=t.var.organization_id,s=a||o;let i=t.var.tenant_id;const u=t.var.user,l=(u!=null&&u.aud?Array.isArray(u.aud)?u.aud:[u.aud]:[]).includes(T.MANAGEMENT_API_AUDIENCE);if(!i&&s&&l&&(t.set("tenant_id",s),i=s),!i)throw new I(400,{message:"Tenant ID not found in request"});if(!X(o,i,r,a))throw new I(403,{message:`Access denied to tenant ${i}`});return n()}}function re(e){return async(t,n)=>{if(!e.subdomainRouting)return n();const{baseDomain:r,reservedSubdomains:a=[],resolveSubdomain:o}=e.subdomainRouting,s=t.req.header("host")||"";let i=null;if(s.endsWith(r)){const c=s.slice(0,-(r.length+1));c&&!c.includes(".")&&(i=c)}if(i&&a.includes(i)&&(i=null),!i)return e.accessControl&&t.set("tenant_id",e.accessControl.controlPlaneTenantId),n();let u=null;if(o)u=await o(i);else if(e.subdomainRouting.useOrganizations!==!1&&e.accessControl)try{const c=await t.env.data.organizations.get(e.accessControl.controlPlaneTenantId,i);c&&(u=c.id)}catch{}if(!u)throw new I(404,{message:`Tenant not found for subdomain: ${i}`});return t.set("tenant_id",u),n()}}function ae(e){return async(t,n)=>{if(!e.databaseIsolation)return n();const r=t.var.tenant_id;if(!r)throw new I(400,{message:"Tenant ID not found in request"});try{const a=await e.databaseIsolation.getAdapters(r);t.env.data=a}catch(a){throw console.error(`Failed to resolve database for tenant ${r}:`,a),new I(500,{message:"Failed to resolve tenant database"})}return n()}}function H(e){const t=re(e),n=ne(e),r=ae(e);return async(a,o)=>(await t(a,async()=>{}),await n(a,async()=>{}),await r(a,async()=>{}),o())}function he(e){const{dataAdapter:t,controlPlane:n,controlPlane:{tenantId:r="control_plane",clientId:a}={},sync:o={resourceServers:!0,roles:!0},defaultPermissions:s=["tenant:admin"],requireOrganizationMatch:i=!1,managementApiExtensions:u=[],entityHooks:c,getChildTenantIds:l,getAdapters:g,...d}=e;let f=t,m=t;n&&(f=B(t,{controlPlaneTenantId:r,controlPlaneClientId:a}),m={...t,multiTenancyConfig:{controlPlaneTenantId:r,controlPlaneClientId:a}});const w=o!==!1,b=w?{resourceServers:o.resourceServers??!0,roles:o.roles??!0}:{resourceServers:!1,roles:!1},A={controlPlaneTenantId:r,getChildTenantIds:l??(async()=>(await T.fetchAll(v=>f.tenants.list(v),"tenants",{cursorField:"id",pageSize:100})).filter(v=>v.id!==r).map(v=>v.id)),getAdapters:g??(async()=>f),getControlPlaneAdapters:async()=>f,sync:b},{entityHooks:S,tenantHooks:p}=Z(A),P={resourceServers:[S.resourceServers,...(c==null?void 0:c.resourceServers)??[]],roles:[S.roles,...(c==null?void 0:c.roles)??[]],connections:(c==null?void 0:c.connections)??[],tenants:(c==null?void 0:c.tenants)??[],rolePermissions:(c==null?void 0:c.rolePermissions)??[]},h=N({accessControl:{controlPlaneTenantId:r,requireOrganizationMatch:i,defaultPermissions:s}}),F=q({accessControl:{controlPlaneTenantId:r,requireOrganizationMatch:i,defaultPermissions:s}},{tenants:{async beforeCreate(z,v){return h.beforeCreate&&(v=await h.beforeCreate(z,v)),p.beforeCreate&&(v=await p.beforeCreate(z,v)),v},async afterCreate(z,v){var $,M;await(($=h.afterCreate)==null?void 0:$.call(h,z,v)),await((M=p.afterCreate)==null?void 0:M.call(p,z,v))},async beforeDelete(z,v){var $,M;await(($=h.beforeDelete)==null?void 0:$.call(h,z,v)),await((M=p.beforeDelete)==null?void 0:M.call(p,z,v))}}}),{app:D}=T.init({dataAdapter:f,managementDataAdapter:m,...d,entityHooks:P,managementApiExtensions:[...u,{path:"/tenants",router:F}]});return D.use("/api/v2/*",te(r)),w&&D.use("/api/v2/*",x()),{app:D,controlPlaneTenantId:r}}function ye(e){const t=O(e);return{name:"multi-tenancy",middleware:H(e),hooks:t,routes:[{path:"/management",handler:q(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 O(e){const t=e.accessControl?J(e.accessControl):{},n=e.databaseIsolation?Y(e.databaseIsolation):{},r=N(e);return{...t,...n,tenants:r}}function se(e){const t=new ce.Hono,n=O(e);return t.route("/tenants",q(e,n)),t}function ve(e){return{hooks:O(e),middleware:H(e),app:se(e),config:e,wrapAdapters:(t,n)=>{var r;return B(t,{controlPlaneTenantId:(r=e.accessControl)==null?void 0:r.controlPlaneTenantId,controlPlaneClientId:n==null?void 0:n.controlPlaneClientId})}}}exports.createAccessControlHooks=J;exports.createAccessControlMiddleware=ne;exports.createControlPlaneTenantMiddleware=te;exports.createDatabaseHooks=Y;exports.createDatabaseMiddleware=ae;exports.createMultiTenancy=se;exports.createMultiTenancyHooks=O;exports.createMultiTenancyMiddleware=H;exports.createMultiTenancyPlugin=ye;exports.createProtectSyncedMiddleware=x;exports.createProvisioningHooks=N;exports.createRuntimeFallbackAdapter=ee;exports.createSubdomainMiddleware=re;exports.createSyncHooks=Z;exports.createTenantsOpenAPIRouter=q;exports.initMultiTenant=he;exports.setupMultiTenancy=ve;exports.validateTenantAccess=X;exports.withRuntimeFallback=B;
|
|
1
|
+
"use strict";var oe=Object.defineProperty;var ie=(e,t,n)=>t in e?oe(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var k=(e,t,n)=>ie(e,typeof t!="symbol"?t+"":t,n);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const ce=require("hono"),T=require("authhero"),_=require("@hono/zod-openapi");function J(e){const{controlPlaneTenantId:t,requireOrganizationMatch:n=!0}=e;return{async onTenantAccessValidation(r,a){if(a===t)return!0;if(n){const o=r.var.org_name,s=r.var.organization_id,i=o||s;return i?i.toLowerCase()===a.toLowerCase():!1}return!0}}}function X(e,t,n,r){if(t===n)return!0;const a=r||e;return a?a.toLowerCase()===t.toLowerCase():!1}function Y(e){return{async resolveDataAdapters(t){try{return await e.getAdapters(t)}catch(n){console.error(`Failed to resolve data adapters for tenant ${t}:`,n);return}}}}function le(e){return`urn:authhero:tenant:${e.toLowerCase()}`}function N(e){return{async beforeCreate(t,n){return!n.audience&&n.id?{...n,audience:le(n.id)}:n},async afterCreate(t,n){const{accessControl:r,databaseIsolation:a}=e;r&&t.ctx&&await ue(t,n,r),a!=null&&a.onProvision&&await a.onProvision(n.id)},async beforeDelete(t,n){const{accessControl:r,databaseIsolation:a}=e;if(r)try{const s=(await t.adapters.organizations.list(r.controlPlaneTenantId)).organizations.find(i=>i.name===n);s&&await t.adapters.organizations.remove(r.controlPlaneTenantId,s.id)}catch(o){console.warn(`Failed to remove organization for tenant ${n}:`,o)}if(a!=null&&a.onDeprovision)try{await a.onDeprovision(n)}catch(o){console.warn(`Failed to deprovision database for tenant ${n}:`,o)}}}}async function ue(e,t,n){const{controlPlaneTenantId:r,defaultPermissions:a,defaultRoles:o,issuer:s,adminRoleName:i="Tenant Admin",adminRoleDescription:u="Full access to all tenant management operations",addCreatorToOrganization:c=!0}=n,l=await e.adapters.organizations.create(r,{name:t.id,display_name:t.friendly_name||t.id});let g;if(s&&(g=await fe(e,r,i,u)),c&&e.ctx){const d=e.ctx.var.user;if(d!=null&&d.sub&&!await de(e,r,d.sub))try{await e.adapters.userOrganizations.create(r,{user_id:d.sub,organization_id:l.id}),g&&await e.adapters.userRoles.create(r,d.sub,g,l.id)}catch(m){console.warn(`Failed to add creator ${d.sub} to organization ${l.id}:`,m)}}o&&o.length>0&&console.log(`Would assign roles ${o.join(", ")} to organization ${l.id}`),a&&a.length>0&&console.log(`Would grant permissions ${a.join(", ")} to organization ${l.id}`)}async function de(e,t,n){const r=await e.adapters.userRoles.list(t,n,void 0,"");for(const a of r)if((await e.adapters.rolePermissions.list(t,a.id,{per_page:1e3})).some(i=>i.permission_name==="admin:organizations"))return!0;return!1}async function fe(e,t,n,r){const o=(await e.adapters.roles.list(t,{})).roles.find(c=>c.name===n);if(o)return o.id;const s=await e.adapters.roles.create(t,{name:n,description:r}),i=T.MANAGEMENT_API_AUDIENCE,u=T.MANAGEMENT_API_SCOPES.map(c=>({role_id:s.id,resource_server_identifier:i,permission_name:c.value}));return await e.adapters.rolePermissions.assign(t,s.id,u),s.id}function U(e,t,n=()=>!0){const{controlPlaneTenantId:r,getChildTenantIds:a,getAdapters:o}=e,s=new Map;async function i(l,g,d){return(await t(l).list(g,{q:`name:${d}`,per_page:1}))[0]??null}async function u(l){const g=await a(),d=t(await o(r));await Promise.all(g.map(async f=>{try{const m=await o(f),w=t(m),y={...d.transform(l),is_system:!0},C=await i(m,f,l.name),A=C?w.getId(C):void 0;if(C&&A){const S=w.preserveOnUpdate?w.preserveOnUpdate(C,y):y;await w.update(f,A,S)}else await w.create(f,y)}catch(m){console.error(`Failed to sync ${d.listKey} "${l.name}" to tenant "${f}":`,m)}}))}async function c(l){const g=await a();await Promise.all(g.map(async d=>{try{const f=await o(d),m=t(f),w=await i(f,d,l),b=w?m.getId(w):void 0;w&&b&&await m.remove(d,b)}catch(f){console.error(`Failed to delete entity "${l}" from tenant "${d}":`,f)}}))}return{afterCreate:async(l,g)=>{l.tenantId===r&&n(g)&&await u(g)},afterUpdate:async(l,g,d)=>{l.tenantId===r&&n(d)&&await u(d)},beforeDelete:async(l,g)=>{if(l.tenantId!==r)return;const f=await t(l.adapters).get(l.tenantId,g);f&&n(f)&&s.set(g,f)},afterDelete:async(l,g)=>{if(l.tenantId!==r)return;const d=s.get(g);d&&(s.delete(g),await c(d.name))}}}function G(e,t,n=()=>!0){const{controlPlaneTenantId:r,getControlPlaneAdapters:a,getAdapters:o}=e;return{async afterCreate(s,i){if(i.id!==r)try{const u=await a(),c=await o(i.id),l=t(u),g=t(c),d=await T.fetchAll(f=>l.listPaginated(r,f),l.listKey,{cursorField:"id",pageSize:100});await Promise.all(d.filter(f=>n(f)).map(async f=>{try{const m=l.transform(f);await g.create(i.id,{...m,is_system:!0})}catch(m){console.error(`Failed to sync entity to new tenant "${i.id}":`,m)}}))}catch(u){console.error(`Failed to sync entities to new tenant "${i.id}":`,u)}}}}const L=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:t=>t.id,transform:t=>({id:t.id,name:t.name,identifier:t.identifier,scopes:t.scopes,signing_alg:t.signing_alg,token_lifetime:t.token_lifetime,token_lifetime_for_web:t.token_lifetime_for_web})}),W=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:t=>t.id,transform:t=>({id:t.id,name:t.name,description:t.description})});function K(e){var t;return((t=e.metadata)==null?void 0:t.sync)!==!1}function Z(e){const{sync:t={},filters:n={}}=e,r=t.resourceServers??!0,a=t.roles??!0,o=m=>K(m)?n.resourceServers?n.resourceServers(m):!0:!1,s=m=>K(m)?n.roles?n.roles(m):!0:!1,i=r?U(e,L,o):void 0,u=a?U(e,W,s):void 0,c=r?G(e,L,o):void 0,l=a?G(e,W,s):void 0,g=a?{async afterCreate(m,w){var b;if(w.id!==e.controlPlaneTenantId){await((b=l==null?void 0:l.afterCreate)==null?void 0:b.call(l,m,w));try{const y=await e.getControlPlaneAdapters(),C=await e.getAdapters(w.id),A=await T.fetchAll(p=>y.roles.list(e.controlPlaneTenantId,p),"roles",{cursorField:"id",pageSize:100}),S=new Map;for(const p of A.filter(P=>{var h;return((h=n.roles)==null?void 0:h.call(n,P))??!0})){const P=await d(C,w.id,p.name);P&&S.set(p.name,P.id)}for(const p of A.filter(P=>{var h;return((h=n.roles)==null?void 0:h.call(n,P))??!0})){const P=S.get(p.name);if(P)try{const h=await y.rolePermissions.list(e.controlPlaneTenantId,p.id,{});h.length>0&&await C.rolePermissions.assign(w.id,P,h.map(R=>({role_id:P,resource_server_identifier:R.resource_server_identifier,permission_name:R.permission_name})))}catch(h){console.error(`Failed to sync permissions for role "${p.name}" to tenant "${w.id}":`,h)}}}catch(y){console.error(`Failed to sync role permissions to tenant "${w.id}":`,y)}}}}:void 0;async function d(m,w,b){return(await m.roles.list(w,{q:`name:${b}`,per_page:1})).roles[0]??null}return{entityHooks:{resourceServers:i,roles:u},tenantHooks:{async afterCreate(m,w){const b=[c==null?void 0:c.afterCreate,(g==null?void 0:g.afterCreate)??(l==null?void 0:l.afterCreate)],y=[];for(const C of b)if(C)try{await C(m,w)}catch(A){y.push(A instanceof Error?A:new Error(String(A)))}if(y.length===1)throw y[0];if(y.length>1)throw new AggregateError(y,y.map(C=>C.message).join("; "))}}}}var I=class extends Error{constructor(t=500,n){super(n==null?void 0:n.message,{cause:n==null?void 0:n.cause});k(this,"res");k(this,"status");this.res=n==null?void 0:n.res,this.status=t}getResponse(){return this.res?new Response(this.res.body,{status:this.status,headers:this.res.headers}):new Response(this.message,{status:this.status})}};function j(e,t){const n=new _.OpenAPIHono;return n.openapi(_.createRoute({tags:["tenants"],method:"get",path:"/",request:{query:T.auth0QuerySchema},security:[{Bearer:[]}],responses:{200:{content:{"application/json":{schema:_.z.object({tenants:_.z.array(T.tenantSchema),start:_.z.number().optional(),limit:_.z.number().optional(),length:_.z.number().optional()})}},description:"List of tenants"}}}),async r=>{var m,w,b,y,C,A;const a=r.req.valid("query"),{page:o,per_page:s,include_totals:i,q:u}=a,c=r.var.user,l=(c==null?void 0:c.permissions)||[];if(l.includes("auth:read")||l.includes("admin:organizations")){const S=await r.env.data.tenants.list({page:o,per_page:s,include_totals:i,q:u});return i?r.json({tenants:S.tenants,start:((m=S.totals)==null?void 0:m.start)??0,limit:((w=S.totals)==null?void 0:w.limit)??s,length:S.tenants.length}):r.json({tenants:S.tenants})}const d=((b=e.accessControl)==null?void 0:b.controlPlaneTenantId)??((y=r.env.data.multiTenancyConfig)==null?void 0:y.controlPlaneTenantId);if(d&&(c!=null&&c.sub)){const p=(await T.fetchAll(M=>r.env.data.userOrganizations.listUserOrganizations(d,c.sub,M),"organizations")).map(M=>M.name);if(p.length===0)return i?r.json({tenants:[],start:0,limit:s??50,length:0}):r.json({tenants:[]});const P=p.length,h=o??0,R=s??50,F=h*R,q=p.slice(F,F+R);if(q.length===0)return i?r.json({tenants:[],start:F,limit:R,length:P}):r.json({tenants:[]});const z=q.map(M=>`id:${M}`).join(" OR "),v=u?`(${z}) AND (${u})`:z,$=await r.env.data.tenants.list({q:v,per_page:R,include_totals:!1});return i?r.json({tenants:$.tenants,start:F,limit:R,length:P}):r.json({tenants:$.tenants})}const f=await r.env.data.tenants.list({page:o,per_page:s,include_totals:i,q:u});return i?r.json({tenants:f.tenants,start:((C=f.totals)==null?void 0:C.start)??0,limit:((A=f.totals)==null?void 0:A.limit)??s,length:f.tenants.length}):r.json({tenants:f.tenants})}),n.openapi(_.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 r=>{var u,c;const a=r.var.user;if(!(a!=null&&a.sub))throw new I(401,{message:"Authentication required to create tenants"});let o=r.req.valid("json");const s={adapters:r.env.data,ctx:r};(u=t.tenants)!=null&&u.beforeCreate&&(o=await t.tenants.beforeCreate(s,o));const i=await r.env.data.tenants.create(o);return(c=t.tenants)!=null&&c.afterCreate&&await t.tenants.afterCreate(s,i),r.json(i,201)}),n.openapi(_.createRoute({tags:["tenants"],method:"delete",path:"/{id}",request:{params:_.z.object({id:_.z.string()})},security:[{Bearer:["delete:tenants"]}],responses:{204:{description:"Tenant deleted"},403:{description:"Access denied or cannot delete the control plane"},404:{description:"Tenant not found"}}}),async r=>{var u,c,l,g;const{id:a}=r.req.valid("param"),o=((u=e.accessControl)==null?void 0:u.controlPlaneTenantId)??((c=r.env.data.multiTenancyConfig)==null?void 0:c.controlPlaneTenantId);if(o){const d=r.var.user;if(!(d!=null&&d.sub))throw new I(401,{message:"Authentication required"});if(a===o)throw new I(403,{message:"Cannot delete the control plane"});if(!(await T.fetchAll(w=>r.env.data.userOrganizations.listUserOrganizations(o,d.sub,w),"organizations")).some(w=>w.name===a))throw new I(403,{message:"Access denied to this tenant"})}if(!await r.env.data.tenants.get(a))throw new I(404,{message:"Tenant not found"});const i={adapters:r.env.data,ctx:r};return(l=t.tenants)!=null&&l.beforeDelete&&await t.tenants.beforeDelete(i,a),await r.env.data.tenants.remove(a),(g=t.tenants)!=null&&g.afterDelete&&await t.tenants.afterDelete(i,a),r.body(null,204)}),n.openapi(_.createRoute({tags:["tenants","settings"],method:"get",path:"/settings",request:{headers:_.z.object({"tenant-id":_.z.string().optional()})},security:[{Bearer:["read:tenants","auth:read"]}],responses:{200:{content:{"application/json":{schema:T.tenantSchema}},description:"Current tenant settings"}}}),async r=>{const a=await r.env.data.tenants.get(r.var.tenant_id);if(!a)throw new I(404,{message:"Tenant not found"});return r.json(a)}),n.openapi(_.createRoute({tags:["tenants","settings"],method:"patch",path:"/settings",request:{headers:_.z.object({"tenant-id":_.z.string().optional()}),body:{content:{"application/json":{schema:_.z.object(T.tenantInsertSchema.shape).partial()}}}},security:[{Bearer:["update:tenants","auth:write"]}],responses:{200:{content:{"application/json":{schema:T.tenantSchema}},description:"Updated tenant settings"}}}),async r=>{const a=r.req.valid("json"),{id:o,...s}=a,i=await r.env.data.tenants.get(r.var.tenant_id);if(!i)throw new I(404,{message:"Tenant not found"});const u=T.deepMergePatch(i,s);await r.env.data.tenants.update(r.var.tenant_id,u);const c=await r.env.data.tenants.get(r.var.tenant_id);if(!c)throw new I(500,{message:"Failed to retrieve updated tenant"});return r.json(c)}),n}function me(e){const t=[{pattern:/\/api\/v2\/resource-servers\/([^/]+)$/,type:"resource_server"},{pattern:/\/api\/v2\/roles\/([^/]+)$/,type:"role"},{pattern:/\/api\/v2\/connections\/([^/]+)$/,type:"connection"}];for(const{pattern:n,type:r}of t){const a=e.match(n);if(a&&a[1])return{type:r,id:a[1]}}return null}async function ge(e,t,n){try{switch(n.type){case"resource_server":{const r=await e.resourceServers.get(t,n.id);return(r==null?void 0:r.is_system)===!0}case"role":{const r=await e.roles.get(t,n.id);return(r==null?void 0:r.is_system)===!0}case"connection":{const r=await e.connections.get(t,n.id);return(r==null?void 0:r.is_system)===!0}default:return!1}}catch{return!1}}function we(e){return{resource_server:"resource server",role:"role",connection:"connection"}[e]}function x(){return async(e,t)=>{if(!["PATCH","PUT","DELETE"].includes(e.req.method))return t();const n=me(e.req.path);if(!n)return t();const r=e.var.tenant_id||e.req.header("x-tenant-id")||e.req.header("tenant-id");if(!r)return t();if(await ge(e.env.data,r,n))throw new I(403,{message:`This ${we(n.type)} is a system resource and cannot be modified. Make changes in the control plane instead.`});return t()}}function E(e,t){const n=t.find(a=>a.strategy===e.strategy);if(!(n!=null&&n.options))return e;const r=T.connectionSchema.passthrough().parse({...n,...e});return r.options=T.connectionOptionsSchema.passthrough().parse({...n.options||{},...e.options}),r}function D(e,t){const n=[...t||[],...e||[]];return[...new Set(n)]}function pe(e,t){if(!(t!=null&&t.length))return e||[];if(!(e!=null&&e.length))return t;const n=new Map;for(const r of t)n.set(r.value,r);for(const r of e)n.set(r.value,r);return Array.from(n.values())}function Q(e,t){return t?{...e,scopes:pe(e.scopes,t.scopes)}:e}function V(e,t){return t?{...e,callbacks:D(e.callbacks,t.callbacks),web_origins:D(e.web_origins,t.web_origins),allowed_logout_urls:D(e.allowed_logout_urls,t.allowed_logout_urls),allowed_origins:D(e.allowed_origins,t.allowed_origins)}:e}function ee(e,t){const{controlPlaneTenantId:n,controlPlaneClientId:r}=t;return{...e,multiTenancyConfig:{controlPlaneTenantId:n,controlPlaneClientId:r},connections:{...e.connections,get:async(a,o)=>{const s=await e.connections.get(a,o);if(!s||!n||a===n)return s;const i=await e.connections.list(n);return E(s,i.connections||[])},list:async(a,o)=>{const s=await e.connections.list(a,o);if(!n||a===n)return s;const i=await e.connections.list(n),u=s.connections.map(c=>E(c,i.connections||[]));return{...s,connections:u}}},clientConnections:{...e.clientConnections,listByClient:async(a,o)=>{let s=await e.clientConnections.listByClient(a,o);if(s.length===0&&(s=(await e.connections.list(a)).connections||[]),!n||a===n)return s;const i=await e.connections.list(n);return s.map(u=>E(u,i.connections||[]))}},clients:{...e.clients,get:async(a,o)=>{const s=await e.clients.get(a,o);if(!s)return null;if(!n||!r||a===n&&o===r)return s;const i=await e.clients.get(n,r);return V(s,i)},getByClientId:async a=>{const o=await e.clients.getByClientId(a);if(!o)return null;if(!n||!r||o.tenant_id===n&&o.client_id===r)return o;const s=await e.clients.get(n,r);return{...V(o,s),tenant_id:o.tenant_id}}},emailProviders:{...e.emailProviders,get:async a=>{const o=await e.emailProviders.get(a);return o||(!n||a===n?null:e.emailProviders.get(n))}},resourceServers:{...e.resourceServers,get:async(a,o)=>{const s=await e.resourceServers.get(a,o);if(!s||!n||a===n)return s;const u=(await e.resourceServers.list(n,{q:`identifier:${s.identifier}`,per_page:1})).resource_servers[0]??null;return Q(s,u)},list:async(a,o)=>{const s=await e.resourceServers.list(a,o);if(!n||a===n)return s;const i=await e.resourceServers.list(n),u=new Map(i.resource_servers.map(l=>[l.identifier,l])),c=s.resource_servers.map(l=>Q(l,u.get(l.identifier)??null));return{...s,resource_servers:c}}}}}function B(e,t){return ee(e,t)}function te(e){return async(t,n)=>{const r=t.var.user;return(r==null?void 0:r.tenant_id)===e&&r.org_name&&t.set("tenant_id",r.org_name),n()}}function ne(e){return async(t,n)=>{if(!e.accessControl)return n();const{controlPlaneTenantId:r}=e.accessControl,a=t.var.org_name,o=t.var.organization_id,s=a||o;let i=t.var.tenant_id;const u=t.var.user,l=(u!=null&&u.aud?Array.isArray(u.aud)?u.aud:[u.aud]:[]).includes(T.MANAGEMENT_API_AUDIENCE);if(!i&&s&&l&&(t.set("tenant_id",s),i=s),!i)throw new I(400,{message:"Tenant ID not found in request"});if(!X(o,i,r,a))throw new I(403,{message:`Access denied to tenant ${i}`});return n()}}function re(e){return async(t,n)=>{if(!e.subdomainRouting)return n();const{baseDomain:r,reservedSubdomains:a=[],resolveSubdomain:o}=e.subdomainRouting,s=t.req.header("x-forwarded-host")||t.req.header("host")||"";let i=null;if(s.endsWith(r)){const c=s.slice(0,-(r.length+1));c&&!c.includes(".")&&(i=c)}if(i&&a.includes(i)&&(i=null),!i)return e.accessControl&&t.set("tenant_id",e.accessControl.controlPlaneTenantId),n();let u=null;if(o)u=await o(i);else if(e.subdomainRouting.useOrganizations!==!1&&e.accessControl)try{const c=await t.env.data.organizations.get(e.accessControl.controlPlaneTenantId,i);c&&(u=c.id)}catch{}if(!u)throw new I(404,{message:`Tenant not found for subdomain: ${i}`});return t.set("tenant_id",u),n()}}function ae(e){return async(t,n)=>{if(!e.databaseIsolation)return n();const r=t.var.tenant_id;if(!r)throw new I(400,{message:"Tenant ID not found in request"});try{const a=await e.databaseIsolation.getAdapters(r);t.env.data=a}catch(a){throw console.error(`Failed to resolve database for tenant ${r}:`,a),new I(500,{message:"Failed to resolve tenant database"})}return n()}}function H(e){const t=re(e),n=ne(e),r=ae(e);return async(a,o)=>(await t(a,async()=>{}),await n(a,async()=>{}),await r(a,async()=>{}),o())}function he(e){const{dataAdapter:t,controlPlane:n,controlPlane:{tenantId:r="control_plane",clientId:a}={},sync:o={resourceServers:!0,roles:!0},defaultPermissions:s=["tenant:admin"],requireOrganizationMatch:i=!1,managementApiExtensions:u=[],entityHooks:c,getChildTenantIds:l,getAdapters:g,...d}=e;let f=t,m=t;n&&(f=B(t,{controlPlaneTenantId:r,controlPlaneClientId:a}),m={...t,multiTenancyConfig:{controlPlaneTenantId:r,controlPlaneClientId:a}});const w=o!==!1,b=w?{resourceServers:o.resourceServers??!0,roles:o.roles??!0}:{resourceServers:!1,roles:!1},A={controlPlaneTenantId:r,getChildTenantIds:l??(async()=>(await T.fetchAll(v=>f.tenants.list(v),"tenants",{cursorField:"id",pageSize:100})).filter(v=>v.id!==r).map(v=>v.id)),getAdapters:g??(async()=>f),getControlPlaneAdapters:async()=>f,sync:b},{entityHooks:S,tenantHooks:p}=Z(A),P={resourceServers:[S.resourceServers,...(c==null?void 0:c.resourceServers)??[]],roles:[S.roles,...(c==null?void 0:c.roles)??[]],connections:(c==null?void 0:c.connections)??[],tenants:(c==null?void 0:c.tenants)??[],rolePermissions:(c==null?void 0:c.rolePermissions)??[]},h=N({accessControl:{controlPlaneTenantId:r,requireOrganizationMatch:i,defaultPermissions:s}}),F=j({accessControl:{controlPlaneTenantId:r,requireOrganizationMatch:i,defaultPermissions:s}},{tenants:{async beforeCreate(z,v){return h.beforeCreate&&(v=await h.beforeCreate(z,v)),p.beforeCreate&&(v=await p.beforeCreate(z,v)),v},async afterCreate(z,v){var $,M;await(($=h.afterCreate)==null?void 0:$.call(h,z,v)),await((M=p.afterCreate)==null?void 0:M.call(p,z,v))},async beforeDelete(z,v){var $,M;await(($=h.beforeDelete)==null?void 0:$.call(h,z,v)),await((M=p.beforeDelete)==null?void 0:M.call(p,z,v))}}}),{app:q}=T.init({dataAdapter:f,managementDataAdapter:m,...d,entityHooks:P,managementApiExtensions:[...u,{path:"/tenants",router:F}]});return q.use("/api/v2/*",te(r)),w&&q.use("/api/v2/*",x()),{app:q,controlPlaneTenantId:r}}function ye(e){const t=O(e);return{name:"multi-tenancy",middleware:H(e),hooks:t,routes:[{path:"/management",handler:j(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 O(e){const t=e.accessControl?J(e.accessControl):{},n=e.databaseIsolation?Y(e.databaseIsolation):{},r=N(e);return{...t,...n,tenants:r}}function se(e){const t=new ce.Hono,n=O(e);return t.route("/tenants",j(e,n)),t}function ve(e){return{hooks:O(e),middleware:H(e),app:se(e),config:e,wrapAdapters:(t,n)=>{var r;return B(t,{controlPlaneTenantId:(r=e.accessControl)==null?void 0:r.controlPlaneTenantId,controlPlaneClientId:n==null?void 0:n.controlPlaneClientId})}}}exports.createAccessControlHooks=J;exports.createAccessControlMiddleware=ne;exports.createControlPlaneTenantMiddleware=te;exports.createDatabaseHooks=Y;exports.createDatabaseMiddleware=ae;exports.createMultiTenancy=se;exports.createMultiTenancyHooks=O;exports.createMultiTenancyMiddleware=H;exports.createMultiTenancyPlugin=ye;exports.createProtectSyncedMiddleware=x;exports.createProvisioningHooks=N;exports.createRuntimeFallbackAdapter=ee;exports.createSubdomainMiddleware=re;exports.createSyncHooks=Z;exports.createTenantsOpenAPIRouter=j;exports.initMultiTenant=he;exports.setupMultiTenancy=ve;exports.validateTenantAccess=X;exports.withRuntimeFallback=B;
|
package/dist/multi-tenancy.mjs
CHANGED
|
@@ -2,7 +2,7 @@ var x = Object.defineProperty;
|
|
|
2
2
|
var ee = (e, t, n) => t in e ? x(e, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[t] = n;
|
|
3
3
|
var E = (e, t, n) => ee(e, typeof t != "symbol" ? t + "" : t, n);
|
|
4
4
|
import { Hono as te } from "hono";
|
|
5
|
-
import { MANAGEMENT_API_SCOPES as ne, MANAGEMENT_API_AUDIENCE as J, fetchAll as
|
|
5
|
+
import { MANAGEMENT_API_SCOPES as ne, MANAGEMENT_API_AUDIENCE as J, fetchAll as q, auth0QuerySchema as re, tenantSchema as D, tenantInsertSchema as U, deepMergePatch as ae, connectionSchema as se, connectionOptionsSchema as oe, init as ie } from "authhero";
|
|
6
6
|
import { OpenAPIHono as ce, createRoute as M, z as S } from "@hono/zod-openapi";
|
|
7
7
|
function le(e) {
|
|
8
8
|
const { controlPlaneTenantId: t, requireOrganizationMatch: n = !0 } = e;
|
|
@@ -245,7 +245,7 @@ function L(e, t, n = () => !0) {
|
|
|
245
245
|
async afterCreate(s, i) {
|
|
246
246
|
if (i.id !== r)
|
|
247
247
|
try {
|
|
248
|
-
const u = await a(), c = await o(i.id), l = t(u), g = t(c), d = await
|
|
248
|
+
const u = await a(), c = await o(i.id), l = t(u), g = t(c), d = await q(
|
|
249
249
|
(f) => l.listPaginated(r, f),
|
|
250
250
|
l.listKey,
|
|
251
251
|
{ cursorField: "id", pageSize: 100 }
|
|
@@ -331,7 +331,7 @@ function pe(e) {
|
|
|
331
331
|
if (w.id !== e.controlPlaneTenantId) {
|
|
332
332
|
await ((C = l == null ? void 0 : l.afterCreate) == null ? void 0 : C.call(l, m, w));
|
|
333
333
|
try {
|
|
334
|
-
const y = await e.getControlPlaneAdapters(), _ = await e.getAdapters(w.id), b = await
|
|
334
|
+
const y = await e.getControlPlaneAdapters(), _ = await e.getAdapters(w.id), b = await q(
|
|
335
335
|
(p) => y.roles.list(
|
|
336
336
|
e.controlPlaneTenantId,
|
|
337
337
|
p
|
|
@@ -471,7 +471,7 @@ function N(e, t) {
|
|
|
471
471
|
content: {
|
|
472
472
|
"application/json": {
|
|
473
473
|
schema: S.object({
|
|
474
|
-
tenants: S.array(
|
|
474
|
+
tenants: S.array(D),
|
|
475
475
|
start: S.number().optional(),
|
|
476
476
|
limit: S.number().optional(),
|
|
477
477
|
length: S.number().optional()
|
|
@@ -501,7 +501,7 @@ function N(e, t) {
|
|
|
501
501
|
}
|
|
502
502
|
const d = ((C = e.accessControl) == null ? void 0 : C.controlPlaneTenantId) ?? ((y = r.env.data.multiTenancyConfig) == null ? void 0 : y.controlPlaneTenantId);
|
|
503
503
|
if (d && (c != null && c.sub)) {
|
|
504
|
-
const p = (await
|
|
504
|
+
const p = (await q(
|
|
505
505
|
(R) => r.env.data.userOrganizations.listUserOrganizations(
|
|
506
506
|
d,
|
|
507
507
|
c.sub,
|
|
@@ -573,7 +573,7 @@ function N(e, t) {
|
|
|
573
573
|
201: {
|
|
574
574
|
content: {
|
|
575
575
|
"application/json": {
|
|
576
|
-
schema:
|
|
576
|
+
schema: D
|
|
577
577
|
}
|
|
578
578
|
},
|
|
579
579
|
description: "Tenant created"
|
|
@@ -642,7 +642,7 @@ function N(e, t) {
|
|
|
642
642
|
throw new A(403, {
|
|
643
643
|
message: "Cannot delete the control plane"
|
|
644
644
|
});
|
|
645
|
-
if (!(await
|
|
645
|
+
if (!(await q(
|
|
646
646
|
(w) => r.env.data.userOrganizations.listUserOrganizations(
|
|
647
647
|
o,
|
|
648
648
|
d.sub,
|
|
@@ -683,7 +683,7 @@ function N(e, t) {
|
|
|
683
683
|
200: {
|
|
684
684
|
content: {
|
|
685
685
|
"application/json": {
|
|
686
|
-
schema:
|
|
686
|
+
schema: D
|
|
687
687
|
}
|
|
688
688
|
},
|
|
689
689
|
description: "Current tenant settings"
|
|
@@ -724,7 +724,7 @@ function N(e, t) {
|
|
|
724
724
|
200: {
|
|
725
725
|
content: {
|
|
726
726
|
"application/json": {
|
|
727
|
-
schema:
|
|
727
|
+
schema: D
|
|
728
728
|
}
|
|
729
729
|
},
|
|
730
730
|
description: "Updated tenant settings"
|
|
@@ -1047,7 +1047,7 @@ function Ae(e) {
|
|
|
1047
1047
|
baseDomain: r,
|
|
1048
1048
|
reservedSubdomains: a = [],
|
|
1049
1049
|
resolveSubdomain: o
|
|
1050
|
-
} = e.subdomainRouting, s = t.req.header("host") || "";
|
|
1050
|
+
} = e.subdomainRouting, s = t.req.header("x-forwarded-host") || t.req.header("host") || "";
|
|
1051
1051
|
let i = null;
|
|
1052
1052
|
if (s.endsWith(r)) {
|
|
1053
1053
|
const c = s.slice(0, -(r.length + 1));
|
|
@@ -1137,7 +1137,7 @@ function je(e) {
|
|
|
1137
1137
|
roles: o.roles ?? !0
|
|
1138
1138
|
} : { resourceServers: !1, roles: !1 }, b = {
|
|
1139
1139
|
controlPlaneTenantId: r,
|
|
1140
|
-
getChildTenantIds: l ?? (async () => (await
|
|
1140
|
+
getChildTenantIds: l ?? (async () => (await q(
|
|
1141
1141
|
(v) => f.tenants.list(v),
|
|
1142
1142
|
"tenants",
|
|
1143
1143
|
{ cursorField: "id", pageSize: 100 }
|
|
@@ -1233,7 +1233,7 @@ function Se(e) {
|
|
|
1233
1233
|
const t = new te(), n = B(e);
|
|
1234
1234
|
return t.route("/tenants", N(e, n)), t;
|
|
1235
1235
|
}
|
|
1236
|
-
function
|
|
1236
|
+
function qe(e) {
|
|
1237
1237
|
return {
|
|
1238
1238
|
hooks: B(e),
|
|
1239
1239
|
middleware: Z(e),
|
|
@@ -1273,7 +1273,7 @@ export {
|
|
|
1273
1273
|
pe as createSyncHooks,
|
|
1274
1274
|
N as createTenantsOpenAPIRouter,
|
|
1275
1275
|
je as initMultiTenant,
|
|
1276
|
-
|
|
1276
|
+
qe as setupMultiTenancy,
|
|
1277
1277
|
ue as validateTenantAccess,
|
|
1278
1278
|
Y as withRuntimeFallback
|
|
1279
1279
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/middleware/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAGzC,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,UAAU,CAAC;AAGlB;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kCAAkC,CAChD,oBAAoB,EAAE,MAAM,GAC3B,iBAAiB,CAAC;IACnB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,SAAS,EAAE,qBAAqB,CAAC;CAClC,CAAC,CAUD;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,kBAAkB,GACzB,iBAAiB,CAAC;IACnB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,SAAS,EAAE,qBAAqB,CAAC;CAClC,CAAC,CAsDD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,kBAAkB,GACzB,iBAAiB,CAAC;IACnB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,SAAS,EAAE,qBAAqB,CAAC;CAClC,CAAC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/middleware/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAGzC,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,UAAU,CAAC;AAGlB;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kCAAkC,CAChD,oBAAoB,EAAE,MAAM,GAC3B,iBAAiB,CAAC;IACnB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,SAAS,EAAE,qBAAqB,CAAC;CAClC,CAAC,CAUD;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,kBAAkB,GACzB,iBAAiB,CAAC;IACnB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,SAAS,EAAE,qBAAqB,CAAC;CAClC,CAAC,CAsDD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,kBAAkB,GACzB,iBAAiB,CAAC;IACnB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,SAAS,EAAE,qBAAqB,CAAC;CAClC,CAAC,CAsED;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,kBAAkB,GACzB,iBAAiB,CAAC;IACnB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,SAAS,EAAE,qBAAqB,CAAC;CAClC,CAAC,CA6BD;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,kBAAkB,GACzB,iBAAiB,CAAC;IACnB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,SAAS,EAAE,qBAAqB,CAAC;CAClC,CAAC,CAiBD;AAGD,OAAO,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAC;AAGjE,OAAO,EACL,4BAA4B,EAC5B,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC"}
|
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.17.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": "^6.0.0",
|
|
38
38
|
"vitest": "^2.1.0",
|
|
39
|
-
"@authhero/kysely-adapter": "10.
|
|
40
|
-
"
|
|
41
|
-
"authhero": "
|
|
39
|
+
"@authhero/kysely-adapter": "10.114.0",
|
|
40
|
+
"authhero": "4.80.0",
|
|
41
|
+
"@authhero/adapter-interfaces": "0.151.0"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"zod": "^3.24.0"
|