@authhero/multi-tenancy 14.1.0 → 14.2.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.d.ts +23 -0
- package/dist/multi-tenancy.mjs +34 -27
- package/package.json +3 -3
package/dist/multi-tenancy.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var x=Object.defineProperty;var ee=(t,e,n)=>e in t?x(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var $=(t,e,n)=>ee(t,typeof e!="symbol"?e+"":e,n);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const te=require("hono"),I=require("authhero"),_=require("@hono/zod-openapi"),S=require("@authhero/adapter-interfaces");function G(t){const{controlPlaneTenantId:e,requireOrganizationMatch:n=!0}=t;return{async onTenantAccessValidation(a,r){if(r===e)return!0;if(n){const i=a.var.org_name,o=a.var.organization_id,s=i||o;return s?s.toLowerCase()===r.toLowerCase():!1}return!0}}}function U(t,e,n,a){if(e===n)return!0;const r=a||t;return r?r.toLowerCase()===e.toLowerCase():!1}function L(t){return{async resolveDataAdapters(e){try{return await t.getAdapters(e)}catch(n){console.error(`Failed to resolve data adapters for tenant ${e}:`,n);return}}}}function ne(t){return`urn:authhero:tenant:${t.toLowerCase()}`}function B(t){return{async beforeCreate(e,n){return!n.audience&&n.id?{...n,audience:ne(n.id)}:n},async afterCreate(e,n){const{accessControl:a,databaseIsolation:r}=t;a&&e.ctx&&await ae(e,n,a),r!=null&&r.onProvision&&await r.onProvision(n.id)},async beforeDelete(e,n){const{accessControl:a,databaseIsolation:r}=t;if(a)try{const o=(await e.adapters.organizations.list(a.controlPlaneTenantId)).organizations.find(s=>s.name===n);o&&await e.adapters.organizations.remove(a.controlPlaneTenantId,o.id)}catch(i){console.warn(`Failed to remove organization for tenant ${n}:`,i)}if(r!=null&&r.onDeprovision)try{await r.onDeprovision(n)}catch(i){console.warn(`Failed to deprovision database for tenant ${n}:`,i)}}}}async function ae(t,e,n){const{controlPlaneTenantId:a,defaultPermissions:r,defaultRoles:i,issuer:o,adminRoleName:s="Tenant Admin",adminRoleDescription:c="Full access to all tenant management operations",addCreatorToOrganization:m=!0}=n,l=await t.adapters.organizations.create(a,{name:e.id,display_name:e.friendly_name||e.id});let f;if(o&&(f=await re(t,a,s,c)),m&&t.ctx){const d=t.ctx.var.user;if(d!=null&&d.sub&&!await se(t,a,d.sub))try{await t.adapters.userOrganizations.create(a,{user_id:d.sub,organization_id:l.id}),f&&await t.adapters.userRoles.create(a,d.sub,f,l.id)}catch(p){console.warn(`Failed to add creator ${d.sub} to organization ${l.id}:`,p)}}i&&i.length>0&&console.log(`Would assign roles ${i.join(", ")} to organization ${l.id}`),r&&r.length>0&&console.log(`Would grant permissions ${r.join(", ")} to organization ${l.id}`)}async function se(t,e,n){const a=await t.adapters.userRoles.list(e,n,void 0,"");for(const r of a)if((await t.adapters.rolePermissions.list(e,r.id,{per_page:1e3})).some(s=>s.permission_name==="admin:organizations"))return!0;return!1}async function re(t,e,n,a){const i=(await t.adapters.roles.list(e,{})).roles.find(m=>m.name===n);if(i)return i.id;const o=await t.adapters.roles.create(e,{name:n,description:a}),s=I.MANAGEMENT_API_AUDIENCE,c=I.MANAGEMENT_API_SCOPES.map(m=>({role_id:o.id,resource_server_identifier:s,permission_name:m.value}));return await t.adapters.rolePermissions.assign(e,o.id,c),o.id}function j(t,e,n=()=>!0){const{controlPlaneTenantId:a,getChildTenantIds:r,getAdapters:i}=t,o=new Map;async function s(l,f,d){return(await e(l).list(f,{q:`name:${d}`,per_page:1}))[0]??null}async function c(l){const f=await r(),d=e(await i(a));await Promise.all(f.map(async u=>{try{const p=await i(u),g=e(p),w={...d.transform(l),is_system:!0},C=await s(p,u,l.name),v=C?g.getId(C):void 0;if(C&&v){const y=g.preserveOnUpdate?g.preserveOnUpdate(C,w):w;await g.update(u,v,y)}else await g.create(u,w)}catch(p){console.error(`Failed to sync ${d.listKey} "${l.name}" to tenant "${u}":`,p)}}))}async function m(l){const f=await r();await Promise.all(f.map(async d=>{try{const u=await i(d),p=e(u),g=await s(u,d,l),h=g?p.getId(g):void 0;g&&h&&await p.remove(d,h)}catch(u){console.error(`Failed to delete entity "${l}" from tenant "${d}":`,u)}}))}return{afterCreate:async(l,f)=>{l.tenantId===a&&n(f)&&await c(f)},afterUpdate:async(l,f,d)=>{l.tenantId===a&&n(d)&&await c(d)},beforeDelete:async(l,f)=>{if(l.tenantId!==a)return;const u=await e(l.adapters).get(l.tenantId,f);u&&n(u)&&o.set(f,u)},afterDelete:async(l,f)=>{if(l.tenantId!==a)return;const d=o.get(f);d&&(o.delete(f),await m(d.name))}}}function E(t,e,n=()=>!0){const{controlPlaneTenantId:a,getControlPlaneAdapters:r,getAdapters:i}=t;return{async afterCreate(o,s){if(s.id!==a)try{const c=await r(),m=await i(s.id),l=e(c),f=e(m),d=await I.fetchAll(u=>l.listPaginated(a,u),l.listKey,{cursorField:"id",pageSize:100});await Promise.all(d.filter(u=>n(u)).map(async u=>{try{const p=l.transform(u);await f.create(s.id,{...p,is_system:!0})}catch(p){console.error(`Failed to sync entity to new tenant "${s.id}":`,p)}}))}catch(c){console.error(`Failed to sync entities to new tenant "${s.id}":`,c)}}}}const N=t=>({list:async(e,n)=>(await t.resourceServers.list(e,n)).resource_servers,listPaginated:(e,n)=>t.resourceServers.list(e,n),get:(e,n)=>t.resourceServers.get(e,n),create:(e,n)=>t.resourceServers.create(e,n),update:(e,n,a)=>t.resourceServers.update(e,n,a),remove:(e,n)=>t.resourceServers.remove(e,n),listKey:"resource_servers",getId:e=>e.id,transform:e=>({id:e.id,name:e.name,identifier:e.identifier,scopes:e.scopes,signing_alg:e.signing_alg,token_lifetime:e.token_lifetime,token_lifetime_for_web:e.token_lifetime_for_web})}),H=t=>({list:async(e,n)=>(await t.roles.list(e,n)).roles,listPaginated:(e,n)=>t.roles.list(e,n),get:(e,n)=>t.roles.get(e,n),create:(e,n)=>t.roles.create(e,n),update:(e,n,a)=>t.roles.update(e,n,a),remove:(e,n)=>t.roles.remove(e,n),listKey:"roles",getId:e=>e.id,transform:e=>({id:e.id,name:e.name,description:e.description})});function K(t){const{sync:e={},filters:n={}}=t,a=e.resourceServers??!0,r=e.roles??!0,i=a?j(t,N,n.resourceServers):void 0,o=r?j(t,H,n.roles):void 0,s=a?E(t,N,n.resourceServers):void 0,c=r?E(t,H,n.roles):void 0,m=r?{async afterCreate(d,u){var p;if(u.id!==t.controlPlaneTenantId){await((p=c==null?void 0:c.afterCreate)==null?void 0:p.call(c,d,u));try{const g=await t.getControlPlaneAdapters(),h=await t.getAdapters(u.id),w=await I.fetchAll(v=>g.roles.list(t.controlPlaneTenantId,v),"roles",{cursorField:"id",pageSize:100}),C=new Map;for(const v of w.filter(y=>{var T;return((T=n.roles)==null?void 0:T.call(n,y))??!0})){const y=await l(h,u.id,v.name);y&&C.set(v.name,y.id)}for(const v of w.filter(y=>{var T;return((T=n.roles)==null?void 0:T.call(n,y))??!0})){const y=C.get(v.name);if(y)try{const T=await g.rolePermissions.list(t.controlPlaneTenantId,v.id,{});T.length>0&&await h.rolePermissions.assign(u.id,y,T.map(A=>({role_id:y,resource_server_identifier:A.resource_server_identifier,permission_name:A.permission_name})))}catch(T){console.error(`Failed to sync permissions for role "${v.name}" to tenant "${u.id}":`,T)}}}catch(g){console.error(`Failed to sync role permissions to tenant "${u.id}":`,g)}}}}:void 0;async function l(d,u,p){return(await d.roles.list(u,{q:`name:${p}`,per_page:1})).roles[0]??null}return{entityHooks:{resourceServers:i,roles:o},tenantHooks:{async afterCreate(d,u){const p=[s==null?void 0:s.afterCreate,(m==null?void 0:m.afterCreate)??(c==null?void 0:c.afterCreate)],g=[];for(const h of p)if(h)try{await h(d,u)}catch(w){g.push(w instanceof Error?w:new Error(String(w)))}if(g.length===1)throw g[0];if(g.length>1)throw new AggregateError(g,g.map(h=>h.message).join("; "))}}}}var b=class extends Error{constructor(e=500,n){super(n==null?void 0:n.message,{cause:n==null?void 0:n.cause});$(this,"res");$(this,"status");this.res=n==null?void 0:n.res,this.status=e}getResponse(){return this.res?new Response(this.res.body,{status:this.status,headers:this.res.headers}):new Response(this.message,{status:this.status})}};function M(t,e){const n=new _.OpenAPIHono;return n.openapi(_.createRoute({tags:["tenants"],method:"get",path:"/",request:{query:S.auth0QuerySchema},security:[{Bearer:[]}],responses:{200:{content:{"application/json":{schema:_.z.object({tenants:_.z.array(S.tenantSchema),start:_.z.number().optional(),limit:_.z.number().optional(),length:_.z.number().optional()})}},description:"List of tenants"}}}),async a=>{var u,p,g,h;const r=a.req.valid("query"),{page:i,per_page:o,include_totals:s,q:c}=r,m=a.var.user,l=(m==null?void 0:m.permissions)||[];if(l.includes("auth:read")||l.includes("admin:organizations")){const w=await a.env.data.tenants.list({page:i,per_page:o,include_totals:s,q:c});return s?a.json({tenants:w.tenants,start:((u=w.totals)==null?void 0:u.start)??0,limit:((p=w.totals)==null?void 0:p.limit)??o,length:w.tenants.length}):a.json({tenants:w.tenants})}if(t.accessControl&&(m!=null&&m.sub)){const w=t.accessControl.controlPlaneTenantId,v=(await I.fetchAll(P=>a.env.data.userOrganizations.listUserOrganizations(w,m.sub,P),"organizations")).map(P=>P.name);if(v.length===0)return s?a.json({tenants:[],start:0,limit:o??50,length:0}):a.json({tenants:[]});const y=v.length,T=i??0,A=o??50,z=T*A,D=v.slice(z,z+A);if(D.length===0)return s?a.json({tenants:[],start:z,limit:A,length:y}):a.json({tenants:[]});const F=D.map(P=>`id:${P}`).join(" OR "),Z=c?`(${F}) AND (${c})`:F,k=await a.env.data.tenants.list({q:Z,per_page:A,include_totals:!1});return s?a.json({tenants:k.tenants,start:z,limit:A,length:y}):a.json({tenants:k.tenants})}const d=await a.env.data.tenants.list({page:i,per_page:o,include_totals:s,q:c});return s?a.json({tenants:d.tenants,start:((g=d.totals)==null?void 0:g.start)??0,limit:((h=d.totals)==null?void 0:h.limit)??o,length:d.tenants.length}):a.json({tenants:d.tenants})}),n.openapi(_.createRoute({tags:["tenants"],method:"post",path:"/",request:{body:{content:{"application/json":{schema:S.tenantInsertSchema}}}},security:[{Bearer:[]}],responses:{201:{content:{"application/json":{schema:S.tenantSchema}},description:"Tenant created"},400:{description:"Validation error"},409:{description:"Tenant with this ID already exists"}}}),async a=>{var c,m;const r=a.var.user;if(!(r!=null&&r.sub))throw new b(401,{message:"Authentication required to create tenants"});let i=a.req.valid("json");const o={adapters:a.env.data,ctx:a};(c=e.tenants)!=null&&c.beforeCreate&&(i=await e.tenants.beforeCreate(o,i));const s=await a.env.data.tenants.create(i);return(m=e.tenants)!=null&&m.afterCreate&&await e.tenants.afterCreate(o,s),a.json(s,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 a=>{var s,c;const{id:r}=a.req.valid("param");if(t.accessControl){const m=a.var.user,l=t.accessControl.controlPlaneTenantId;if(!(m!=null&&m.sub))throw new b(401,{message:"Authentication required"});if(r===l)throw new b(403,{message:"Cannot delete the control plane"});if(!(await I.fetchAll(u=>a.env.data.userOrganizations.listUserOrganizations(l,m.sub,u),"organizations")).some(u=>u.name===r))throw new b(403,{message:"Access denied to this tenant"})}if(!await a.env.data.tenants.get(r))throw new b(404,{message:"Tenant not found"});const o={adapters:a.env.data,ctx:a};return(s=e.tenants)!=null&&s.beforeDelete&&await e.tenants.beforeDelete(o,r),await a.env.data.tenants.remove(r),(c=e.tenants)!=null&&c.afterDelete&&await e.tenants.afterDelete(o,r),a.body(null,204)}),n}function oe(t){const e=[{pattern:/\/api\/v2\/resource-servers\/([^/]+)$/,type:"resource_server"},{pattern:/\/api\/v2\/roles\/([^/]+)$/,type:"role"},{pattern:/\/api\/v2\/connections\/([^/]+)$/,type:"connection"}];for(const{pattern:n,type:a}of e){const r=t.match(n);if(r&&r[1])return{type:a,id:r[1]}}return null}async function ie(t,e,n){try{switch(n.type){case"resource_server":{const a=await t.resourceServers.get(e,n.id);return(a==null?void 0:a.is_system)===!0}case"role":{const a=await t.roles.get(e,n.id);return(a==null?void 0:a.is_system)===!0}case"connection":{const a=await t.connections.get(e,n.id);return(a==null?void 0:a.is_system)===!0}default:return!1}}catch{return!1}}function ce(t){return{resource_server:"resource server",role:"role",connection:"connection"}[t]}function W(){return async(t,e)=>{if(!["PATCH","PUT","DELETE"].includes(t.req.method))return e();const n=oe(t.req.path);if(!n)return e();const a=t.var.tenant_id||t.req.header("x-tenant-id")||t.req.header("tenant-id");if(!a)return e();if(await ie(t.env.data,a,n))throw new b(403,{message:`This ${ce(n.type)} is a system resource and cannot be modified. Make changes in the control plane instead.`});return e()}}function O(t,e){const{controlPlaneTenantId:n,controlPlaneClientId:a}=e;return{...t,legacyClients:{...t.legacyClients,get:async r=>{var f;const i=await t.legacyClients.get(r);if(!i)return null;const o=a?await t.legacyClients.get(a):void 0,s=await t.connections.list(i.tenant.id),c=n?await t.connections.list(n):{connections:[]},m=s.connections.map(d=>{var g;const u=(g=c.connections)==null?void 0:g.find(h=>h.strategy===d.strategy);if(!(u!=null&&u.options))return d;const p=S.connectionSchema.parse({...u||{},...d});return p.options=S.connectionOptionsSchema.parse({...u.options||{},...d.options}),p}).filter(d=>d),l={...(o==null?void 0:o.tenant)||{},...i.tenant};return!i.tenant.audience&&((f=o==null?void 0:o.tenant)!=null&&f.audience)&&(l.audience=o.tenant.audience),{...i,web_origins:[...(o==null?void 0:o.web_origins)||[],...i.web_origins||[]],allowed_logout_urls:[...(o==null?void 0:o.allowed_logout_urls)||[],...i.allowed_logout_urls||[]],callbacks:[...(o==null?void 0:o.callbacks)||[],...i.callbacks||[]],connections:m,tenant:l}}},connections:{...t.connections,get:async(r,i)=>{var l;const o=await t.connections.get(r,i);if(!o||!n||r===n)return o;const c=(l=(await t.connections.list(n)).connections)==null?void 0:l.find(f=>f.strategy===o.strategy);if(!(c!=null&&c.options))return o;const m=S.connectionSchema.parse({...c,...o});return m.options=S.connectionOptionsSchema.parse({...c.options||{},...o.options}),m},list:async(r,i)=>{const o=await t.connections.list(r,i);if(!n||r===n)return o;const s=await t.connections.list(n),c=o.connections.map(m=>{var d;const l=(d=s.connections)==null?void 0:d.find(u=>u.strategy===m.strategy);if(!(l!=null&&l.options))return m;const f=S.connectionSchema.parse({...l,...m});return f.options=S.connectionOptionsSchema.parse({...l.options||{},...m.options}),f});return{...o,connections:c}}}}}function Q(t,e){return O(t,e)}const le=O,de=Q;function V(t){return async(e,n)=>{if(!t.accessControl)return n();const{controlPlaneTenantId:a}=t.accessControl,r=e.var.org_name,i=e.var.organization_id,o=r||i;let s=e.var.tenant_id;const c=e.var.user,l=(c!=null&&c.aud?Array.isArray(c.aud)?c.aud:[c.aud]:[]).includes(I.MANAGEMENT_API_AUDIENCE);if(!s&&o&&l&&(e.set("tenant_id",o),s=o),!s)throw new b(400,{message:"Tenant ID not found in request"});if(!U(i,s,a,r))throw new b(403,{message:`Access denied to tenant ${s}`});return n()}}function J(t){return async(e,n)=>{if(!t.subdomainRouting)return n();const{baseDomain:a,reservedSubdomains:r=[],resolveSubdomain:i}=t.subdomainRouting,o=e.req.header("host")||"";let s=null;if(o.endsWith(a)){const m=o.slice(0,-(a.length+1));m&&!m.includes(".")&&(s=m)}if(s&&r.includes(s)&&(s=null),!s)return t.accessControl&&e.set("tenant_id",t.accessControl.controlPlaneTenantId),n();let c=null;if(i)c=await i(s);else if(t.subdomainRouting.useOrganizations!==!1&&t.accessControl)try{const m=await e.env.data.organizations.get(t.accessControl.controlPlaneTenantId,s);m&&(c=m.id)}catch{}if(!c)throw new b(404,{message:`Tenant not found for subdomain: ${s}`});return e.set("tenant_id",c),n()}}function X(t){return async(e,n)=>{if(!t.databaseIsolation)return n();const a=e.var.tenant_id;if(!a)throw new b(400,{message:"Tenant ID not found in request"});try{const r=await t.databaseIsolation.getAdapters(a);e.env.data=r}catch(r){throw console.error(`Failed to resolve database for tenant ${a}:`,r),new b(500,{message:"Failed to resolve tenant database"})}return n()}}function q(t){const e=J(t),n=V(t),a=X(t);return async(r,i)=>(await e(r,async()=>{}),await n(r,async()=>{}),await a(r,async()=>{}),i())}function ue(t){const{dataAdapter:e,controlPlaneTenantId:n="control_plane",sync:a={resourceServers:!0,roles:!0},defaultPermissions:r=["tenant:admin"],requireOrganizationMatch:i=!1,managementApiExtensions:o=[],entityHooks:s,getChildTenantIds:c,getAdapters:m,...l}=t,f=a!==!1,d=f?{resourceServers:a.resourceServers??!0,roles:a.roles??!0}:{resourceServers:!1,roles:!1},g={controlPlaneTenantId:n,getChildTenantIds:c??(async()=>(await I.fetchAll(A=>e.tenants.list(A),"tenants",{cursorField:"id",pageSize:100})).filter(A=>A.id!==n).map(A=>A.id)),getAdapters:m??(async()=>e),getControlPlaneAdapters:async()=>e,sync:d},{entityHooks:h,tenantHooks:w}=K(g),C={resourceServers:[h.resourceServers,...(s==null?void 0:s.resourceServers)??[]],roles:[h.roles,...(s==null?void 0:s.roles)??[]],connections:(s==null?void 0:s.connections)??[],tenants:(s==null?void 0:s.tenants)??[],rolePermissions:(s==null?void 0:s.rolePermissions)??[]},v=M({accessControl:{controlPlaneTenantId:n,requireOrganizationMatch:i,defaultPermissions:r}},{tenants:w}),{app:y}=I.init({dataAdapter:e,...l,entityHooks:C,managementApiExtensions:[...o,{path:"/tenants",router:v}]});return f&&y.use("/api/v2/*",W()),{app:y,controlPlaneTenantId:n}}function me(t){const e=R(t);return{name:"multi-tenancy",middleware:q(t),hooks:e,routes:[{path:"/management",handler:M(t,e)}],onRegister:async()=>{console.log("Multi-tenancy plugin registered"),t.accessControl&&console.log(` - Access control enabled (control plane: ${t.accessControl.controlPlaneTenantId})`),t.subdomainRouting&&console.log(` - Subdomain routing enabled (base domain: ${t.subdomainRouting.baseDomain})`),t.databaseIsolation&&console.log(" - Database isolation enabled")}}}function R(t){const e=t.accessControl?G(t.accessControl):{},n=t.databaseIsolation?L(t.databaseIsolation):{},a=B(t);return{...e,...n,tenants:a}}function Y(t){const e=new te.Hono,n=R(t);return e.route("/tenants",M(t,n)),e}function fe(t){return{hooks:R(t),middleware:q(t),app:Y(t),config:t}}exports.createAccessControlHooks=G;exports.createAccessControlMiddleware=V;exports.createDatabaseHooks=L;exports.createDatabaseMiddleware=X;exports.createMultiTenancy=Y;exports.createMultiTenancyHooks=R;exports.createMultiTenancyMiddleware=q;exports.createMultiTenancyPlugin=me;exports.createProtectSyncedMiddleware=W;exports.createProvisioningHooks=B;exports.createRuntimeFallbackAdapter=O;exports.createSettingsInheritanceAdapter=le;exports.createSubdomainMiddleware=J;exports.createSyncHooks=K;exports.createTenantsOpenAPIRouter=M;exports.initMultiTenant=ue;exports.setupMultiTenancy=fe;exports.validateTenantAccess=U;exports.withRuntimeFallback=Q;exports.withSettingsInheritance=de;
|
|
1
|
+
"use strict";var ee=Object.defineProperty;var te=(t,e,n)=>e in t?ee(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var $=(t,e,n)=>te(t,typeof e!="symbol"?e+"":e,n);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const ne=require("hono"),P=require("authhero"),_=require("@hono/zod-openapi"),S=require("@authhero/adapter-interfaces");function G(t){const{controlPlaneTenantId:e,requireOrganizationMatch:n=!0}=t;return{async onTenantAccessValidation(a,s){if(s===e)return!0;if(n){const i=a.var.org_name,o=a.var.organization_id,r=i||o;return r?r.toLowerCase()===s.toLowerCase():!1}return!0}}}function U(t,e,n,a){if(e===n)return!0;const s=a||t;return s?s.toLowerCase()===e.toLowerCase():!1}function L(t){return{async resolveDataAdapters(e){try{return await t.getAdapters(e)}catch(n){console.error(`Failed to resolve data adapters for tenant ${e}:`,n);return}}}}function ae(t){return`urn:authhero:tenant:${t.toLowerCase()}`}function B(t){return{async beforeCreate(e,n){return!n.audience&&n.id?{...n,audience:ae(n.id)}:n},async afterCreate(e,n){const{accessControl:a,databaseIsolation:s}=t;a&&e.ctx&&await re(e,n,a),s!=null&&s.onProvision&&await s.onProvision(n.id)},async beforeDelete(e,n){const{accessControl:a,databaseIsolation:s}=t;if(a)try{const o=(await e.adapters.organizations.list(a.controlPlaneTenantId)).organizations.find(r=>r.name===n);o&&await e.adapters.organizations.remove(a.controlPlaneTenantId,o.id)}catch(i){console.warn(`Failed to remove organization for tenant ${n}:`,i)}if(s!=null&&s.onDeprovision)try{await s.onDeprovision(n)}catch(i){console.warn(`Failed to deprovision database for tenant ${n}:`,i)}}}}async function re(t,e,n){const{controlPlaneTenantId:a,defaultPermissions:s,defaultRoles:i,issuer:o,adminRoleName:r="Tenant Admin",adminRoleDescription:c="Full access to all tenant management operations",addCreatorToOrganization:m=!0}=n,l=await t.adapters.organizations.create(a,{name:e.id,display_name:e.friendly_name||e.id});let f;if(o&&(f=await oe(t,a,r,c)),m&&t.ctx){const d=t.ctx.var.user;if(d!=null&&d.sub&&!await se(t,a,d.sub))try{await t.adapters.userOrganizations.create(a,{user_id:d.sub,organization_id:l.id}),f&&await t.adapters.userRoles.create(a,d.sub,f,l.id)}catch(p){console.warn(`Failed to add creator ${d.sub} to organization ${l.id}:`,p)}}i&&i.length>0&&console.log(`Would assign roles ${i.join(", ")} to organization ${l.id}`),s&&s.length>0&&console.log(`Would grant permissions ${s.join(", ")} to organization ${l.id}`)}async function se(t,e,n){const a=await t.adapters.userRoles.list(e,n,void 0,"");for(const s of a)if((await t.adapters.rolePermissions.list(e,s.id,{per_page:1e3})).some(r=>r.permission_name==="admin:organizations"))return!0;return!1}async function oe(t,e,n,a){const i=(await t.adapters.roles.list(e,{})).roles.find(m=>m.name===n);if(i)return i.id;const o=await t.adapters.roles.create(e,{name:n,description:a}),r=P.MANAGEMENT_API_AUDIENCE,c=P.MANAGEMENT_API_SCOPES.map(m=>({role_id:o.id,resource_server_identifier:r,permission_name:m.value}));return await t.adapters.rolePermissions.assign(e,o.id,c),o.id}function j(t,e,n=()=>!0){const{controlPlaneTenantId:a,getChildTenantIds:s,getAdapters:i}=t,o=new Map;async function r(l,f,d){return(await e(l).list(f,{q:`name:${d}`,per_page:1}))[0]??null}async function c(l){const f=await s(),d=e(await i(a));await Promise.all(f.map(async u=>{try{const p=await i(u),g=e(p),w={...d.transform(l),is_system:!0},C=await r(p,u,l.name),v=C?g.getId(C):void 0;if(C&&v){const h=g.preserveOnUpdate?g.preserveOnUpdate(C,w):w;await g.update(u,v,h)}else await g.create(u,w)}catch(p){console.error(`Failed to sync ${d.listKey} "${l.name}" to tenant "${u}":`,p)}}))}async function m(l){const f=await s();await Promise.all(f.map(async d=>{try{const u=await i(d),p=e(u),g=await r(u,d,l),y=g?p.getId(g):void 0;g&&y&&await p.remove(d,y)}catch(u){console.error(`Failed to delete entity "${l}" from tenant "${d}":`,u)}}))}return{afterCreate:async(l,f)=>{l.tenantId===a&&n(f)&&await c(f)},afterUpdate:async(l,f,d)=>{l.tenantId===a&&n(d)&&await c(d)},beforeDelete:async(l,f)=>{if(l.tenantId!==a)return;const u=await e(l.adapters).get(l.tenantId,f);u&&n(u)&&o.set(f,u)},afterDelete:async(l,f)=>{if(l.tenantId!==a)return;const d=o.get(f);d&&(o.delete(f),await m(d.name))}}}function E(t,e,n=()=>!0){const{controlPlaneTenantId:a,getControlPlaneAdapters:s,getAdapters:i}=t;return{async afterCreate(o,r){if(r.id!==a)try{const c=await s(),m=await i(r.id),l=e(c),f=e(m),d=await P.fetchAll(u=>l.listPaginated(a,u),l.listKey,{cursorField:"id",pageSize:100});await Promise.all(d.filter(u=>n(u)).map(async u=>{try{const p=l.transform(u);await f.create(r.id,{...p,is_system:!0})}catch(p){console.error(`Failed to sync entity to new tenant "${r.id}":`,p)}}))}catch(c){console.error(`Failed to sync entities to new tenant "${r.id}":`,c)}}}}const N=t=>({list:async(e,n)=>(await t.resourceServers.list(e,n)).resource_servers,listPaginated:(e,n)=>t.resourceServers.list(e,n),get:(e,n)=>t.resourceServers.get(e,n),create:(e,n)=>t.resourceServers.create(e,n),update:(e,n,a)=>t.resourceServers.update(e,n,a),remove:(e,n)=>t.resourceServers.remove(e,n),listKey:"resource_servers",getId:e=>e.id,transform:e=>({id:e.id,name:e.name,identifier:e.identifier,scopes:e.scopes,signing_alg:e.signing_alg,token_lifetime:e.token_lifetime,token_lifetime_for_web:e.token_lifetime_for_web})}),H=t=>({list:async(e,n)=>(await t.roles.list(e,n)).roles,listPaginated:(e,n)=>t.roles.list(e,n),get:(e,n)=>t.roles.get(e,n),create:(e,n)=>t.roles.create(e,n),update:(e,n,a)=>t.roles.update(e,n,a),remove:(e,n)=>t.roles.remove(e,n),listKey:"roles",getId:e=>e.id,transform:e=>({id:e.id,name:e.name,description:e.description})});function K(t){const{sync:e={},filters:n={}}=t,a=e.resourceServers??!0,s=e.roles??!0,i=a?j(t,N,n.resourceServers):void 0,o=s?j(t,H,n.roles):void 0,r=a?E(t,N,n.resourceServers):void 0,c=s?E(t,H,n.roles):void 0,m=s?{async afterCreate(d,u){var p;if(u.id!==t.controlPlaneTenantId){await((p=c==null?void 0:c.afterCreate)==null?void 0:p.call(c,d,u));try{const g=await t.getControlPlaneAdapters(),y=await t.getAdapters(u.id),w=await P.fetchAll(v=>g.roles.list(t.controlPlaneTenantId,v),"roles",{cursorField:"id",pageSize:100}),C=new Map;for(const v of w.filter(h=>{var A;return((A=n.roles)==null?void 0:A.call(n,h))??!0})){const h=await l(y,u.id,v.name);h&&C.set(v.name,h.id)}for(const v of w.filter(h=>{var A;return((A=n.roles)==null?void 0:A.call(n,h))??!0})){const h=C.get(v.name);if(h)try{const A=await g.rolePermissions.list(t.controlPlaneTenantId,v.id,{});A.length>0&&await y.rolePermissions.assign(u.id,h,A.map(T=>({role_id:h,resource_server_identifier:T.resource_server_identifier,permission_name:T.permission_name})))}catch(A){console.error(`Failed to sync permissions for role "${v.name}" to tenant "${u.id}":`,A)}}}catch(g){console.error(`Failed to sync role permissions to tenant "${u.id}":`,g)}}}}:void 0;async function l(d,u,p){return(await d.roles.list(u,{q:`name:${p}`,per_page:1})).roles[0]??null}return{entityHooks:{resourceServers:i,roles:o},tenantHooks:{async afterCreate(d,u){const p=[r==null?void 0:r.afterCreate,(m==null?void 0:m.afterCreate)??(c==null?void 0:c.afterCreate)],g=[];for(const y of p)if(y)try{await y(d,u)}catch(w){g.push(w instanceof Error?w:new Error(String(w)))}if(g.length===1)throw g[0];if(g.length>1)throw new AggregateError(g,g.map(y=>y.message).join("; "))}}}}var b=class extends Error{constructor(e=500,n){super(n==null?void 0:n.message,{cause:n==null?void 0:n.cause});$(this,"res");$(this,"status");this.res=n==null?void 0:n.res,this.status=e}getResponse(){return this.res?new Response(this.res.body,{status:this.status,headers:this.res.headers}):new Response(this.message,{status:this.status})}};function z(t,e){const n=new _.OpenAPIHono;return n.openapi(_.createRoute({tags:["tenants"],method:"get",path:"/",request:{query:S.auth0QuerySchema},security:[{Bearer:[]}],responses:{200:{content:{"application/json":{schema:_.z.object({tenants:_.z.array(S.tenantSchema),start:_.z.number().optional(),limit:_.z.number().optional(),length:_.z.number().optional()})}},description:"List of tenants"}}}),async a=>{var u,p,g,y;const s=a.req.valid("query"),{page:i,per_page:o,include_totals:r,q:c}=s,m=a.var.user,l=(m==null?void 0:m.permissions)||[];if(l.includes("auth:read")||l.includes("admin:organizations")){const w=await a.env.data.tenants.list({page:i,per_page:o,include_totals:r,q:c});return r?a.json({tenants:w.tenants,start:((u=w.totals)==null?void 0:u.start)??0,limit:((p=w.totals)==null?void 0:p.limit)??o,length:w.tenants.length}):a.json({tenants:w.tenants})}if(t.accessControl&&(m!=null&&m.sub)){const w=t.accessControl.controlPlaneTenantId,v=(await P.fetchAll(I=>a.env.data.userOrganizations.listUserOrganizations(w,m.sub,I),"organizations")).map(I=>I.name);if(v.length===0)return r?a.json({tenants:[],start:0,limit:o??50,length:0}):a.json({tenants:[]});const h=v.length,A=i??0,T=o??50,M=A*T,D=v.slice(M,M+T);if(D.length===0)return r?a.json({tenants:[],start:M,limit:T,length:h}):a.json({tenants:[]});const F=D.map(I=>`id:${I}`).join(" OR "),x=c?`(${F}) AND (${c})`:F,k=await a.env.data.tenants.list({q:x,per_page:T,include_totals:!1});return r?a.json({tenants:k.tenants,start:M,limit:T,length:h}):a.json({tenants:k.tenants})}const d=await a.env.data.tenants.list({page:i,per_page:o,include_totals:r,q:c});return r?a.json({tenants:d.tenants,start:((g=d.totals)==null?void 0:g.start)??0,limit:((y=d.totals)==null?void 0:y.limit)??o,length:d.tenants.length}):a.json({tenants:d.tenants})}),n.openapi(_.createRoute({tags:["tenants"],method:"post",path:"/",request:{body:{content:{"application/json":{schema:S.tenantInsertSchema}}}},security:[{Bearer:[]}],responses:{201:{content:{"application/json":{schema:S.tenantSchema}},description:"Tenant created"},400:{description:"Validation error"},409:{description:"Tenant with this ID already exists"}}}),async a=>{var c,m;const s=a.var.user;if(!(s!=null&&s.sub))throw new b(401,{message:"Authentication required to create tenants"});let i=a.req.valid("json");const o={adapters:a.env.data,ctx:a};(c=e.tenants)!=null&&c.beforeCreate&&(i=await e.tenants.beforeCreate(o,i));const r=await a.env.data.tenants.create(i);return(m=e.tenants)!=null&&m.afterCreate&&await e.tenants.afterCreate(o,r),a.json(r,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 a=>{var r,c;const{id:s}=a.req.valid("param");if(t.accessControl){const m=a.var.user,l=t.accessControl.controlPlaneTenantId;if(!(m!=null&&m.sub))throw new b(401,{message:"Authentication required"});if(s===l)throw new b(403,{message:"Cannot delete the control plane"});if(!(await P.fetchAll(u=>a.env.data.userOrganizations.listUserOrganizations(l,m.sub,u),"organizations")).some(u=>u.name===s))throw new b(403,{message:"Access denied to this tenant"})}if(!await a.env.data.tenants.get(s))throw new b(404,{message:"Tenant not found"});const o={adapters:a.env.data,ctx:a};return(r=e.tenants)!=null&&r.beforeDelete&&await e.tenants.beforeDelete(o,s),await a.env.data.tenants.remove(s),(c=e.tenants)!=null&&c.afterDelete&&await e.tenants.afterDelete(o,s),a.body(null,204)}),n}function ie(t){const e=[{pattern:/\/api\/v2\/resource-servers\/([^/]+)$/,type:"resource_server"},{pattern:/\/api\/v2\/roles\/([^/]+)$/,type:"role"},{pattern:/\/api\/v2\/connections\/([^/]+)$/,type:"connection"}];for(const{pattern:n,type:a}of e){const s=t.match(n);if(s&&s[1])return{type:a,id:s[1]}}return null}async function ce(t,e,n){try{switch(n.type){case"resource_server":{const a=await t.resourceServers.get(e,n.id);return(a==null?void 0:a.is_system)===!0}case"role":{const a=await t.roles.get(e,n.id);return(a==null?void 0:a.is_system)===!0}case"connection":{const a=await t.connections.get(e,n.id);return(a==null?void 0:a.is_system)===!0}default:return!1}}catch{return!1}}function le(t){return{resource_server:"resource server",role:"role",connection:"connection"}[t]}function W(){return async(t,e)=>{if(!["PATCH","PUT","DELETE"].includes(t.req.method))return e();const n=ie(t.req.path);if(!n)return e();const a=t.var.tenant_id||t.req.header("x-tenant-id")||t.req.header("tenant-id");if(!a)return e();if(await ce(t.env.data,a,n))throw new b(403,{message:`This ${le(n.type)} is a system resource and cannot be modified. Make changes in the control plane instead.`});return e()}}function O(t,e){const{controlPlaneTenantId:n,controlPlaneClientId:a}=e;return{...t,legacyClients:{...t.legacyClients,get:async s=>{var f;const i=await t.legacyClients.get(s);if(!i)return null;const o=a?await t.legacyClients.get(a):void 0,r=await t.connections.list(i.tenant.id),c=n?await t.connections.list(n):{connections:[]},m=r.connections.map(d=>{var g;const u=(g=c.connections)==null?void 0:g.find(y=>y.strategy===d.strategy);if(!(u!=null&&u.options))return d;const p=S.connectionSchema.parse({...u||{},...d});return p.options=S.connectionOptionsSchema.parse({...u.options||{},...d.options}),p}).filter(d=>d),l={...(o==null?void 0:o.tenant)||{},...i.tenant};return!i.tenant.audience&&((f=o==null?void 0:o.tenant)!=null&&f.audience)&&(l.audience=o.tenant.audience),{...i,web_origins:[...(o==null?void 0:o.web_origins)||[],...i.web_origins||[]],allowed_logout_urls:[...(o==null?void 0:o.allowed_logout_urls)||[],...i.allowed_logout_urls||[]],callbacks:[...(o==null?void 0:o.callbacks)||[],...i.callbacks||[]],connections:m,tenant:l}}},connections:{...t.connections,get:async(s,i)=>{var l;const o=await t.connections.get(s,i);if(!o||!n||s===n)return o;const c=(l=(await t.connections.list(n)).connections)==null?void 0:l.find(f=>f.strategy===o.strategy);if(!(c!=null&&c.options))return o;const m=S.connectionSchema.parse({...c,...o});return m.options=S.connectionOptionsSchema.parse({...c.options||{},...o.options}),m},list:async(s,i)=>{const o=await t.connections.list(s,i);if(!n||s===n)return o;const r=await t.connections.list(n),c=o.connections.map(m=>{var d;const l=(d=r.connections)==null?void 0:d.find(u=>u.strategy===m.strategy);if(!(l!=null&&l.options))return m;const f=S.connectionSchema.parse({...l,...m});return f.options=S.connectionOptionsSchema.parse({...l.options||{},...m.options}),f});return{...o,connections:c}}}}}function Q(t,e){return O(t,e)}const de=O,ue=Q;function V(t){return async(e,n)=>{const a=e.var.user;return(a==null?void 0:a.tenant_id)===t&&a.org_name&&e.set("tenant_id",a.org_name),n()}}function J(t){return async(e,n)=>{if(!t.accessControl)return n();const{controlPlaneTenantId:a}=t.accessControl,s=e.var.org_name,i=e.var.organization_id,o=s||i;let r=e.var.tenant_id;const c=e.var.user,l=(c!=null&&c.aud?Array.isArray(c.aud)?c.aud:[c.aud]:[]).includes(P.MANAGEMENT_API_AUDIENCE);if(!r&&o&&l&&(e.set("tenant_id",o),r=o),!r)throw new b(400,{message:"Tenant ID not found in request"});if(!U(i,r,a,s))throw new b(403,{message:`Access denied to tenant ${r}`});return n()}}function X(t){return async(e,n)=>{if(!t.subdomainRouting)return n();const{baseDomain:a,reservedSubdomains:s=[],resolveSubdomain:i}=t.subdomainRouting,o=e.req.header("host")||"";let r=null;if(o.endsWith(a)){const m=o.slice(0,-(a.length+1));m&&!m.includes(".")&&(r=m)}if(r&&s.includes(r)&&(r=null),!r)return t.accessControl&&e.set("tenant_id",t.accessControl.controlPlaneTenantId),n();let c=null;if(i)c=await i(r);else if(t.subdomainRouting.useOrganizations!==!1&&t.accessControl)try{const m=await e.env.data.organizations.get(t.accessControl.controlPlaneTenantId,r);m&&(c=m.id)}catch{}if(!c)throw new b(404,{message:`Tenant not found for subdomain: ${r}`});return e.set("tenant_id",c),n()}}function Y(t){return async(e,n)=>{if(!t.databaseIsolation)return n();const a=e.var.tenant_id;if(!a)throw new b(400,{message:"Tenant ID not found in request"});try{const s=await t.databaseIsolation.getAdapters(a);e.env.data=s}catch(s){throw console.error(`Failed to resolve database for tenant ${a}:`,s),new b(500,{message:"Failed to resolve tenant database"})}return n()}}function q(t){const e=X(t),n=J(t),a=Y(t);return async(s,i)=>(await e(s,async()=>{}),await n(s,async()=>{}),await a(s,async()=>{}),i())}function me(t){const{dataAdapter:e,controlPlaneTenantId:n="control_plane",sync:a={resourceServers:!0,roles:!0},defaultPermissions:s=["tenant:admin"],requireOrganizationMatch:i=!1,managementApiExtensions:o=[],entityHooks:r,getChildTenantIds:c,getAdapters:m,...l}=t,f=a!==!1,d=f?{resourceServers:a.resourceServers??!0,roles:a.roles??!0}:{resourceServers:!1,roles:!1},g={controlPlaneTenantId:n,getChildTenantIds:c??(async()=>(await P.fetchAll(T=>e.tenants.list(T),"tenants",{cursorField:"id",pageSize:100})).filter(T=>T.id!==n).map(T=>T.id)),getAdapters:m??(async()=>e),getControlPlaneAdapters:async()=>e,sync:d},{entityHooks:y,tenantHooks:w}=K(g),C={resourceServers:[y.resourceServers,...(r==null?void 0:r.resourceServers)??[]],roles:[y.roles,...(r==null?void 0:r.roles)??[]],connections:(r==null?void 0:r.connections)??[],tenants:(r==null?void 0:r.tenants)??[],rolePermissions:(r==null?void 0:r.rolePermissions)??[]},v=z({accessControl:{controlPlaneTenantId:n,requireOrganizationMatch:i,defaultPermissions:s}},{tenants:w}),{app:h}=P.init({dataAdapter:e,...l,entityHooks:C,managementApiExtensions:[...o,{path:"/tenants",router:v}]});return h.use("/api/v2/*",V(n)),f&&h.use("/api/v2/*",W()),{app:h,controlPlaneTenantId:n}}function fe(t){const e=R(t);return{name:"multi-tenancy",middleware:q(t),hooks:e,routes:[{path:"/management",handler:z(t,e)}],onRegister:async()=>{console.log("Multi-tenancy plugin registered"),t.accessControl&&console.log(` - Access control enabled (control plane: ${t.accessControl.controlPlaneTenantId})`),t.subdomainRouting&&console.log(` - Subdomain routing enabled (base domain: ${t.subdomainRouting.baseDomain})`),t.databaseIsolation&&console.log(" - Database isolation enabled")}}}function R(t){const e=t.accessControl?G(t.accessControl):{},n=t.databaseIsolation?L(t.databaseIsolation):{},a=B(t);return{...e,...n,tenants:a}}function Z(t){const e=new ne.Hono,n=R(t);return e.route("/tenants",z(t,n)),e}function ge(t){return{hooks:R(t),middleware:q(t),app:Z(t),config:t}}exports.createAccessControlHooks=G;exports.createAccessControlMiddleware=J;exports.createControlPlaneTenantMiddleware=V;exports.createDatabaseHooks=L;exports.createDatabaseMiddleware=Y;exports.createMultiTenancy=Z;exports.createMultiTenancyHooks=R;exports.createMultiTenancyMiddleware=q;exports.createMultiTenancyPlugin=fe;exports.createProtectSyncedMiddleware=W;exports.createProvisioningHooks=B;exports.createRuntimeFallbackAdapter=O;exports.createSettingsInheritanceAdapter=de;exports.createSubdomainMiddleware=X;exports.createSyncHooks=K;exports.createTenantsOpenAPIRouter=z;exports.initMultiTenant=me;exports.setupMultiTenancy=ge;exports.validateTenantAccess=U;exports.withRuntimeFallback=Q;exports.withSettingsInheritance=ue;
|
package/dist/multi-tenancy.d.ts
CHANGED
|
@@ -40988,6 +40988,29 @@ export declare const createSettingsInheritanceAdapter: typeof createRuntimeFallb
|
|
|
40988
40988
|
* @deprecated Use `withRuntimeFallback` instead
|
|
40989
40989
|
*/
|
|
40990
40990
|
export declare const withSettingsInheritance: typeof withRuntimeFallback;
|
|
40991
|
+
/**
|
|
40992
|
+
* Creates middleware that resolves tenant_id from org_name for control plane users.
|
|
40993
|
+
*
|
|
40994
|
+
* When a user authenticates to the control plane tenant and has an org_name claim,
|
|
40995
|
+
* this middleware sets the tenant_id to the org_name, allowing them to access
|
|
40996
|
+
* that child tenant's resources.
|
|
40997
|
+
*
|
|
40998
|
+
* @param controlPlaneTenantId - The ID of the control plane tenant
|
|
40999
|
+
* @returns Hono middleware handler
|
|
41000
|
+
*
|
|
41001
|
+
* @example
|
|
41002
|
+
* ```typescript
|
|
41003
|
+
* import { createControlPlaneTenantMiddleware } from "@authhero/multi-tenancy";
|
|
41004
|
+
*
|
|
41005
|
+
* const middleware = createControlPlaneTenantMiddleware("control_plane");
|
|
41006
|
+
*
|
|
41007
|
+
* app.use("/api/*", middleware);
|
|
41008
|
+
* ```
|
|
41009
|
+
*/
|
|
41010
|
+
export declare function createControlPlaneTenantMiddleware(controlPlaneTenantId: string): MiddlewareHandler<{
|
|
41011
|
+
Bindings: MultiTenancyBindings;
|
|
41012
|
+
Variables: MultiTenancyVariables;
|
|
41013
|
+
}>;
|
|
40991
41014
|
/**
|
|
40992
41015
|
* Creates middleware for validating organization-based tenant access.
|
|
40993
41016
|
*
|
package/dist/multi-tenancy.mjs
CHANGED
|
@@ -2,7 +2,7 @@ var W = Object.defineProperty;
|
|
|
2
2
|
var Q = (t, e, n) => e in t ? W(t, e, { enumerable: !0, configurable: !0, writable: !0, value: n }) : t[e] = n;
|
|
3
3
|
var $ = (t, e, n) => Q(t, typeof e != "symbol" ? e + "" : e, n);
|
|
4
4
|
import { Hono as V } from "hono";
|
|
5
|
-
import { MANAGEMENT_API_SCOPES as J, MANAGEMENT_API_AUDIENCE as L, fetchAll as
|
|
5
|
+
import { MANAGEMENT_API_SCOPES as J, MANAGEMENT_API_AUDIENCE as L, fetchAll as I, init as X } from "authhero";
|
|
6
6
|
import { OpenAPIHono as Y, createRoute as z, z as C } from "@hono/zod-openapi";
|
|
7
7
|
import { auth0QuerySchema as Z, tenantSchema as k, tenantInsertSchema as x, connectionSchema as R, connectionOptionsSchema as O } from "@authhero/adapter-interfaces";
|
|
8
8
|
function ee(t) {
|
|
@@ -246,7 +246,7 @@ function N(t, e, n = () => !0) {
|
|
|
246
246
|
async afterCreate(o, r) {
|
|
247
247
|
if (r.id !== s)
|
|
248
248
|
try {
|
|
249
|
-
const c = await a(), m = await i(r.id), l = e(c), f = e(m), d = await
|
|
249
|
+
const c = await a(), m = await i(r.id), l = e(c), f = e(m), d = await I(
|
|
250
250
|
(u) => l.listPaginated(s, u),
|
|
251
251
|
l.listKey,
|
|
252
252
|
{ cursorField: "id", pageSize: 100 }
|
|
@@ -332,7 +332,7 @@ function ce(t) {
|
|
|
332
332
|
if (u.id !== t.controlPlaneTenantId) {
|
|
333
333
|
await ((p = c == null ? void 0 : c.afterCreate) == null ? void 0 : p.call(c, d, u));
|
|
334
334
|
try {
|
|
335
|
-
const g = await t.getControlPlaneAdapters(), y = await t.getAdapters(u.id), w = await
|
|
335
|
+
const g = await t.getControlPlaneAdapters(), y = await t.getAdapters(u.id), w = await I(
|
|
336
336
|
(v) => g.roles.list(
|
|
337
337
|
t.controlPlaneTenantId,
|
|
338
338
|
v
|
|
@@ -501,14 +501,14 @@ function M(t, e) {
|
|
|
501
501
|
}) : s.json({ tenants: w.tenants });
|
|
502
502
|
}
|
|
503
503
|
if (t.accessControl && (m != null && m.sub)) {
|
|
504
|
-
const w = t.accessControl.controlPlaneTenantId, v = (await
|
|
505
|
-
(
|
|
504
|
+
const w = t.accessControl.controlPlaneTenantId, v = (await I(
|
|
505
|
+
(P) => s.env.data.userOrganizations.listUserOrganizations(
|
|
506
506
|
w,
|
|
507
507
|
m.sub,
|
|
508
|
-
|
|
508
|
+
P
|
|
509
509
|
),
|
|
510
510
|
"organizations"
|
|
511
|
-
)).map((
|
|
511
|
+
)).map((P) => P.name);
|
|
512
512
|
if (v.length === 0)
|
|
513
513
|
return r ? s.json({
|
|
514
514
|
tenants: [],
|
|
@@ -524,7 +524,7 @@ function M(t, e) {
|
|
|
524
524
|
limit: _,
|
|
525
525
|
length: h
|
|
526
526
|
}) : s.json({ tenants: [] });
|
|
527
|
-
const F = D.map((
|
|
527
|
+
const F = D.map((P) => `id:${P}`).join(" OR "), K = c ? `(${F}) AND (${c})` : F, j = await s.env.data.tenants.list({
|
|
528
528
|
q: K,
|
|
529
529
|
per_page: _,
|
|
530
530
|
include_totals: !1
|
|
@@ -642,7 +642,7 @@ function M(t, e) {
|
|
|
642
642
|
throw new b(403, {
|
|
643
643
|
message: "Cannot delete the control plane"
|
|
644
644
|
});
|
|
645
|
-
if (!(await
|
|
645
|
+
if (!(await I(
|
|
646
646
|
(u) => s.env.data.userOrganizations.listUserOrganizations(
|
|
647
647
|
l,
|
|
648
648
|
m.sub,
|
|
@@ -841,8 +841,14 @@ function U(t, e) {
|
|
|
841
841
|
function fe(t, e) {
|
|
842
842
|
return U(t, e);
|
|
843
843
|
}
|
|
844
|
-
const
|
|
844
|
+
const Ce = U, Pe = fe;
|
|
845
845
|
function ge(t) {
|
|
846
|
+
return async (e, n) => {
|
|
847
|
+
const s = e.var.user;
|
|
848
|
+
return (s == null ? void 0 : s.tenant_id) === t && s.org_name && e.set("tenant_id", s.org_name), n();
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
function pe(t) {
|
|
846
852
|
return async (e, n) => {
|
|
847
853
|
if (!t.accessControl)
|
|
848
854
|
return n();
|
|
@@ -865,7 +871,7 @@ function ge(t) {
|
|
|
865
871
|
return n();
|
|
866
872
|
};
|
|
867
873
|
}
|
|
868
|
-
function
|
|
874
|
+
function we(t) {
|
|
869
875
|
return async (e, n) => {
|
|
870
876
|
if (!t.subdomainRouting)
|
|
871
877
|
return n();
|
|
@@ -900,7 +906,7 @@ function pe(t) {
|
|
|
900
906
|
return e.set("tenant_id", c), n();
|
|
901
907
|
};
|
|
902
908
|
}
|
|
903
|
-
function
|
|
909
|
+
function ye(t) {
|
|
904
910
|
return async (e, n) => {
|
|
905
911
|
if (!t.databaseIsolation)
|
|
906
912
|
return n();
|
|
@@ -924,7 +930,7 @@ function we(t) {
|
|
|
924
930
|
};
|
|
925
931
|
}
|
|
926
932
|
function B(t) {
|
|
927
|
-
const e =
|
|
933
|
+
const e = we(t), n = pe(t), s = ye(t);
|
|
928
934
|
return async (a, i) => (await e(a, async () => {
|
|
929
935
|
}), await n(a, async () => {
|
|
930
936
|
}), await s(a, async () => {
|
|
@@ -947,7 +953,7 @@ function Ie(t) {
|
|
|
947
953
|
roles: s.roles ?? !0
|
|
948
954
|
} : { resourceServers: !1, roles: !1 }, g = {
|
|
949
955
|
controlPlaneTenantId: n,
|
|
950
|
-
getChildTenantIds: c ?? (async () => (await
|
|
956
|
+
getChildTenantIds: c ?? (async () => (await I(
|
|
951
957
|
(_) => e.tenants.list(_),
|
|
952
958
|
"tenants",
|
|
953
959
|
{ cursorField: "id", pageSize: 100 }
|
|
@@ -982,9 +988,9 @@ function Ie(t) {
|
|
|
982
988
|
{ path: "/tenants", router: v }
|
|
983
989
|
]
|
|
984
990
|
});
|
|
985
|
-
return f && h.use("/api/v2/*", me()), { app: h, controlPlaneTenantId: n };
|
|
991
|
+
return h.use("/api/v2/*", ge(n)), f && h.use("/api/v2/*", me()), { app: h, controlPlaneTenantId: n };
|
|
986
992
|
}
|
|
987
|
-
function
|
|
993
|
+
function Se(t) {
|
|
988
994
|
const e = q(t);
|
|
989
995
|
return {
|
|
990
996
|
name: "multi-tenancy",
|
|
@@ -1017,37 +1023,38 @@ function q(t) {
|
|
|
1017
1023
|
tenants: s
|
|
1018
1024
|
};
|
|
1019
1025
|
}
|
|
1020
|
-
function
|
|
1026
|
+
function he(t) {
|
|
1021
1027
|
const e = new V(), n = q(t);
|
|
1022
1028
|
return e.route("/tenants", M(t, n)), e;
|
|
1023
1029
|
}
|
|
1024
|
-
function
|
|
1030
|
+
function $e(t) {
|
|
1025
1031
|
return {
|
|
1026
1032
|
hooks: q(t),
|
|
1027
1033
|
middleware: B(t),
|
|
1028
|
-
app:
|
|
1034
|
+
app: he(t),
|
|
1029
1035
|
config: t
|
|
1030
1036
|
};
|
|
1031
1037
|
}
|
|
1032
1038
|
export {
|
|
1033
1039
|
ee as createAccessControlHooks,
|
|
1034
|
-
|
|
1040
|
+
pe as createAccessControlMiddleware,
|
|
1041
|
+
ge as createControlPlaneTenantMiddleware,
|
|
1035
1042
|
ne as createDatabaseHooks,
|
|
1036
|
-
|
|
1037
|
-
|
|
1043
|
+
ye as createDatabaseMiddleware,
|
|
1044
|
+
he as createMultiTenancy,
|
|
1038
1045
|
q as createMultiTenancyHooks,
|
|
1039
1046
|
B as createMultiTenancyMiddleware,
|
|
1040
|
-
|
|
1047
|
+
Se as createMultiTenancyPlugin,
|
|
1041
1048
|
me as createProtectSyncedMiddleware,
|
|
1042
1049
|
re as createProvisioningHooks,
|
|
1043
1050
|
U as createRuntimeFallbackAdapter,
|
|
1044
|
-
|
|
1045
|
-
|
|
1051
|
+
Ce as createSettingsInheritanceAdapter,
|
|
1052
|
+
we as createSubdomainMiddleware,
|
|
1046
1053
|
ce as createSyncHooks,
|
|
1047
1054
|
M as createTenantsOpenAPIRouter,
|
|
1048
1055
|
Ie as initMultiTenant,
|
|
1049
|
-
|
|
1056
|
+
$e as setupMultiTenancy,
|
|
1050
1057
|
te as validateTenantAccess,
|
|
1051
1058
|
fe as withRuntimeFallback,
|
|
1052
|
-
|
|
1059
|
+
Pe as withSettingsInheritance
|
|
1053
1060
|
};
|
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.2.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"
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"typescript": "^5.6.0",
|
|
38
38
|
"vite": "^6.0.0",
|
|
39
39
|
"vitest": "^2.1.0",
|
|
40
|
-
"
|
|
41
|
-
"authhero": "
|
|
40
|
+
"authhero": "4.1.0",
|
|
41
|
+
"@authhero/kysely-adapter": "10.77.0"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"zod": "^3.24.0",
|