@authhero/multi-tenancy 14.2.0 → 14.3.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.
Files changed (38) hide show
  1. package/dist/multi-tenancy.cjs +1 -1
  2. package/dist/multi-tenancy.mjs +24 -25
  3. package/dist/types/hooks/access-control.d.ts +25 -0
  4. package/dist/types/hooks/access-control.d.ts.map +1 -0
  5. package/dist/types/hooks/database.d.ts +35 -0
  6. package/dist/types/hooks/database.d.ts.map +1 -0
  7. package/dist/types/hooks/index.d.ts +5 -0
  8. package/dist/types/hooks/index.d.ts.map +1 -0
  9. package/dist/types/hooks/provisioning.d.ts +15 -0
  10. package/dist/types/hooks/provisioning.d.ts.map +1 -0
  11. package/dist/types/hooks/resource-server-sync.d.ts +140 -0
  12. package/dist/types/hooks/resource-server-sync.d.ts.map +1 -0
  13. package/dist/types/hooks/role-sync.d.ts +145 -0
  14. package/dist/types/hooks/role-sync.d.ts.map +1 -0
  15. package/dist/types/hooks/sync.d.ts +79 -0
  16. package/dist/types/hooks/sync.d.ts.map +1 -0
  17. package/dist/types/index.d.ts +117 -0
  18. package/dist/types/index.d.ts.map +1 -0
  19. package/dist/types/init.d.ts +110 -0
  20. package/dist/types/init.d.ts.map +1 -0
  21. package/dist/types/middleware/index.d.ts +114 -0
  22. package/dist/types/middleware/index.d.ts.map +1 -0
  23. package/dist/types/middleware/protect-synced.d.ts +40 -0
  24. package/dist/types/middleware/protect-synced.d.ts.map +1 -0
  25. package/dist/types/middleware/settings-inheritance.d.ts +89 -0
  26. package/dist/types/middleware/settings-inheritance.d.ts.map +1 -0
  27. package/dist/types/plugin.d.ts +66 -0
  28. package/dist/types/plugin.d.ts.map +1 -0
  29. package/dist/types/routes/index.d.ts +2 -0
  30. package/dist/types/routes/index.d.ts.map +1 -0
  31. package/dist/types/routes/tenants.d.ts +18 -0
  32. package/dist/types/routes/tenants.d.ts.map +1 -0
  33. package/dist/types/types.d.ts +295 -0
  34. package/dist/types/types.d.ts.map +1 -0
  35. package/dist/types/utils/index.d.ts +3 -0
  36. package/dist/types/utils/index.d.ts.map +1 -0
  37. package/package.json +9 -9
  38. package/dist/multi-tenancy.d.ts +0 -41331
@@ -1 +1 @@
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;
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 R=(t,e,n)=>ee(t,typeof e!="symbol"?e+"":e,n);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const te=require("hono"),v=require("authhero"),b=require("@hono/zod-openapi");function H(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 G(t,e,n,a){if(e===n)return!0;const r=a||t;return r?r.toLowerCase()===e.toLowerCase():!1}function U(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 L(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=v.MANAGEMENT_API_AUDIENCE,c=v.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 k(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},S=await s(p,u,l.name),T=S?g.getId(S):void 0;if(S&&T){const h=g.preserveOnUpdate?g.preserveOnUpdate(S,w):w;await g.update(u,T,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 r();await Promise.all(f.map(async d=>{try{const u=await i(d),p=e(u),g=await s(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 j(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 v.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 E=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})}),N=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 B(t){const{sync:e={},filters:n={}}=t,a=e.resourceServers??!0,r=e.roles??!0,i=a?k(t,E,n.resourceServers):void 0,o=r?k(t,N,n.roles):void 0,s=a?j(t,E,n.resourceServers):void 0,c=r?j(t,N,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(),y=await t.getAdapters(u.id),w=await v.fetchAll(T=>g.roles.list(t.controlPlaneTenantId,T),"roles",{cursorField:"id",pageSize:100}),S=new Map;for(const T of w.filter(h=>{var _;return((_=n.roles)==null?void 0:_.call(n,h))??!0})){const h=await l(y,u.id,T.name);h&&S.set(T.name,h.id)}for(const T of w.filter(h=>{var _;return((_=n.roles)==null?void 0:_.call(n,h))??!0})){const h=S.get(T.name);if(h)try{const _=await g.rolePermissions.list(t.controlPlaneTenantId,T.id,{});_.length>0&&await y.rolePermissions.assign(u.id,h,_.map(A=>({role_id:h,resource_server_identifier:A.resource_server_identifier,permission_name:A.permission_name})))}catch(_){console.error(`Failed to sync permissions for role "${T.name}" to tenant "${u.id}":`,_)}}}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 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 C=class extends Error{constructor(e=500,n){super(n==null?void 0:n.message,{cause:n==null?void 0:n.cause});R(this,"res");R(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 b.OpenAPIHono;return n.openapi(b.createRoute({tags:["tenants"],method:"get",path:"/",request:{query:v.auth0QuerySchema},security:[{Bearer:[]}],responses:{200:{content:{"application/json":{schema:b.z.object({tenants:b.z.array(v.tenantSchema),start:b.z.number().optional(),limit:b.z.number().optional(),length:b.z.number().optional()})}},description:"List of tenants"}}}),async a=>{var u,p,g,y;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,T=(await v.fetchAll(P=>a.env.data.userOrganizations.listUserOrganizations(w,m.sub,P),"organizations")).map(P=>P.name);if(T.length===0)return s?a.json({tenants:[],start:0,limit:o??50,length:0}):a.json({tenants:[]});const h=T.length,_=i??0,A=o??50,I=_*A,q=T.slice(I,I+A);if(q.length===0)return s?a.json({tenants:[],start:I,limit:A,length:h}):a.json({tenants:[]});const D=q.map(P=>`id:${P}`).join(" OR "),Z=c?`(${D}) AND (${c})`:D,F=await a.env.data.tenants.list({q:Z,per_page:A,include_totals:!1});return s?a.json({tenants:F.tenants,start:I,limit:A,length:h}):a.json({tenants:F.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:((y=d.totals)==null?void 0:y.limit)??o,length:d.tenants.length}):a.json({tenants:d.tenants})}),n.openapi(b.createRoute({tags:["tenants"],method:"post",path:"/",request:{body:{content:{"application/json":{schema:v.tenantInsertSchema}}}},security:[{Bearer:[]}],responses:{201:{content:{"application/json":{schema:v.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 C(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(b.createRoute({tags:["tenants"],method:"delete",path:"/{id}",request:{params:b.z.object({id:b.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 C(401,{message:"Authentication required"});if(r===l)throw new C(403,{message:"Cannot delete the control plane"});if(!(await v.fetchAll(u=>a.env.data.userOrganizations.listUserOrganizations(l,m.sub,u),"organizations")).some(u=>u.name===r))throw new C(403,{message:"Access denied to this tenant"})}if(!await a.env.data.tenants.get(r))throw new C(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 K(){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 C(403,{message:`This ${ce(n.type)} is a system resource and cannot be modified. Make changes in the control plane instead.`});return e()}}function $(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(y=>y.strategy===d.strategy);if(!(u!=null&&u.options))return d;const p=v.connectionSchema.parse({...u||{},...d});return p.options=v.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=v.connectionSchema.parse({...c,...o});return m.options=v.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=v.connectionSchema.parse({...l,...m});return f.options=v.connectionOptionsSchema.parse({...l.options||{},...m.options}),f});return{...o,connections:c}}}}}function W(t,e){return $(t,e)}const le=$,de=W;function Q(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 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(v.MANAGEMENT_API_AUDIENCE);if(!s&&o&&l&&(e.set("tenant_id",o),s=o),!s)throw new C(400,{message:"Tenant ID not found in request"});if(!G(i,s,a,r))throw new C(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 C(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 C(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 C(500,{message:"Failed to resolve tenant database"})}return n()}}function O(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 v.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:y,tenantHooks:w}=B(g),S={resourceServers:[y.resourceServers,...(s==null?void 0:s.resourceServers)??[]],roles:[y.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)??[]},T=M({accessControl:{controlPlaneTenantId:n,requireOrganizationMatch:i,defaultPermissions:r}},{tenants:w}),{app:h}=v.init({dataAdapter:e,...l,entityHooks:S,managementApiExtensions:[...o,{path:"/tenants",router:T}]});return h.use("/api/v2/*",Q(n)),f&&h.use("/api/v2/*",K()),{app:h,controlPlaneTenantId:n}}function me(t){const e=z(t);return{name:"multi-tenancy",middleware:O(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 z(t){const e=t.accessControl?H(t.accessControl):{},n=t.databaseIsolation?U(t.databaseIsolation):{},a=L(t);return{...e,...n,tenants:a}}function Y(t){const e=new te.Hono,n=z(t);return e.route("/tenants",M(t,n)),e}function fe(t){return{hooks:z(t),middleware:O(t),app:Y(t),config:t}}exports.createAccessControlHooks=H;exports.createAccessControlMiddleware=V;exports.createControlPlaneTenantMiddleware=Q;exports.createDatabaseHooks=U;exports.createDatabaseMiddleware=X;exports.createMultiTenancy=Y;exports.createMultiTenancyHooks=z;exports.createMultiTenancyMiddleware=O;exports.createMultiTenancyPlugin=me;exports.createProtectSyncedMiddleware=K;exports.createProvisioningHooks=L;exports.createRuntimeFallbackAdapter=$;exports.createSettingsInheritanceAdapter=le;exports.createSubdomainMiddleware=J;exports.createSyncHooks=B;exports.createTenantsOpenAPIRouter=M;exports.initMultiTenant=ue;exports.setupMultiTenancy=fe;exports.validateTenantAccess=G;exports.withRuntimeFallback=W;exports.withSettingsInheritance=de;
@@ -2,9 +2,8 @@ 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 I, init as X } from "authhero";
6
- import { OpenAPIHono as Y, createRoute as z, z as C } from "@hono/zod-openapi";
7
- import { auth0QuerySchema as Z, tenantSchema as k, tenantInsertSchema as x, connectionSchema as R, connectionOptionsSchema as O } from "@authhero/adapter-interfaces";
5
+ import { MANAGEMENT_API_SCOPES as J, MANAGEMENT_API_AUDIENCE as L, fetchAll as I, auth0QuerySchema as X, tenantSchema as k, tenantInsertSchema as Y, connectionSchema as z, connectionOptionsSchema as R, init as Z } from "authhero";
6
+ import { OpenAPIHono as x, createRoute as O, z as C } from "@hono/zod-openapi";
8
7
  function ee(t) {
9
8
  const { controlPlaneTenantId: e, requireOrganizationMatch: n = !0 } = t;
10
9
  return {
@@ -453,14 +452,14 @@ var b = class extends Error {
453
452
  }
454
453
  };
455
454
  function M(t, e) {
456
- const n = new Y();
455
+ const n = new x();
457
456
  return n.openapi(
458
- z({
457
+ O({
459
458
  tags: ["tenants"],
460
459
  method: "get",
461
460
  path: "/",
462
461
  request: {
463
- query: Z
462
+ query: X
464
463
  },
465
464
  security: [
466
465
  {
@@ -551,7 +550,7 @@ function M(t, e) {
551
550
  }) : s.json({ tenants: d.tenants });
552
551
  }
553
552
  ), n.openapi(
554
- z({
553
+ O({
555
554
  tags: ["tenants"],
556
555
  method: "post",
557
556
  path: "/",
@@ -559,7 +558,7 @@ function M(t, e) {
559
558
  body: {
560
559
  content: {
561
560
  "application/json": {
562
- schema: x
561
+ schema: Y
563
562
  }
564
563
  }
565
564
  }
@@ -603,7 +602,7 @@ function M(t, e) {
603
602
  return (m = e.tenants) != null && m.afterCreate && await e.tenants.afterCreate(o, r), s.json(r, 201);
604
603
  }
605
604
  ), n.openapi(
606
- z({
605
+ O({
607
606
  tags: ["tenants"],
608
607
  method: "delete",
609
608
  path: "/{id}",
@@ -748,11 +747,11 @@ function U(t, e) {
748
747
  );
749
748
  if (!(u != null && u.options))
750
749
  return d;
751
- const p = R.parse({
750
+ const p = z.parse({
752
751
  ...u || {},
753
752
  ...d
754
753
  });
755
- return p.options = O.parse({
754
+ return p.options = R.parse({
756
755
  ...u.options || {},
757
756
  ...d.options
758
757
  }), p;
@@ -794,11 +793,11 @@ function U(t, e) {
794
793
  );
795
794
  if (!(c != null && c.options))
796
795
  return o;
797
- const m = R.parse({
796
+ const m = z.parse({
798
797
  ...c,
799
798
  ...o
800
799
  });
801
- return m.options = O.parse({
800
+ return m.options = R.parse({
802
801
  ...c.options || {},
803
802
  ...o.options
804
803
  }), m;
@@ -814,11 +813,11 @@ function U(t, e) {
814
813
  );
815
814
  if (!(l != null && l.options))
816
815
  return m;
817
- const f = R.parse({
816
+ const f = z.parse({
818
817
  ...l,
819
818
  ...m
820
819
  });
821
- return f.options = O.parse({
820
+ return f.options = R.parse({
822
821
  ...l.options || {},
823
822
  ...m.options
824
823
  }), f;
@@ -841,7 +840,7 @@ function U(t, e) {
841
840
  function fe(t, e) {
842
841
  return U(t, e);
843
842
  }
844
- const Ce = U, Pe = fe;
843
+ const Ae = U, Ce = fe;
845
844
  function ge(t) {
846
845
  return async (e, n) => {
847
846
  const s = e.var.user;
@@ -936,7 +935,7 @@ function B(t) {
936
935
  }), await s(a, async () => {
937
936
  }), i());
938
937
  }
939
- function Ie(t) {
938
+ function Pe(t) {
940
939
  const {
941
940
  dataAdapter: e,
942
941
  controlPlaneTenantId: n = "control_plane",
@@ -979,7 +978,7 @@ function Ie(t) {
979
978
  }
980
979
  },
981
980
  { tenants: w }
982
- ), { app: h } = X({
981
+ ), { app: h } = Z({
983
982
  dataAdapter: e,
984
983
  ...l,
985
984
  entityHooks: A,
@@ -990,7 +989,7 @@ function Ie(t) {
990
989
  });
991
990
  return h.use("/api/v2/*", ge(n)), f && h.use("/api/v2/*", me()), { app: h, controlPlaneTenantId: n };
992
991
  }
993
- function Se(t) {
992
+ function Ie(t) {
994
993
  const e = q(t);
995
994
  return {
996
995
  name: "multi-tenancy",
@@ -1027,7 +1026,7 @@ function he(t) {
1027
1026
  const e = new V(), n = q(t);
1028
1027
  return e.route("/tenants", M(t, n)), e;
1029
1028
  }
1030
- function $e(t) {
1029
+ function Se(t) {
1031
1030
  return {
1032
1031
  hooks: q(t),
1033
1032
  middleware: B(t),
@@ -1044,17 +1043,17 @@ export {
1044
1043
  he as createMultiTenancy,
1045
1044
  q as createMultiTenancyHooks,
1046
1045
  B as createMultiTenancyMiddleware,
1047
- Se as createMultiTenancyPlugin,
1046
+ Ie as createMultiTenancyPlugin,
1048
1047
  me as createProtectSyncedMiddleware,
1049
1048
  re as createProvisioningHooks,
1050
1049
  U as createRuntimeFallbackAdapter,
1051
- Ce as createSettingsInheritanceAdapter,
1050
+ Ae as createSettingsInheritanceAdapter,
1052
1051
  we as createSubdomainMiddleware,
1053
1052
  ce as createSyncHooks,
1054
1053
  M as createTenantsOpenAPIRouter,
1055
- Ie as initMultiTenant,
1056
- $e as setupMultiTenancy,
1054
+ Pe as initMultiTenant,
1055
+ Se as setupMultiTenancy,
1057
1056
  te as validateTenantAccess,
1058
1057
  fe as withRuntimeFallback,
1059
- Pe as withSettingsInheritance
1058
+ Ce as withSettingsInheritance
1060
1059
  };
@@ -0,0 +1,25 @@
1
+ import { AccessControlConfig, MultiTenancyHooks } from "../types";
2
+ /**
3
+ * Creates hooks for organization-based tenant access control.
4
+ *
5
+ * This implements the following access model:
6
+ * - Control plane: Accessible without an organization claim
7
+ * - Child tenants: Require an organization claim matching the tenant ID
8
+ * - org_name (organization name) takes precedence and should match tenant ID
9
+ * - org_id (organization ID) is checked as fallback
10
+ *
11
+ * @param config - Access control configuration
12
+ * @returns Hooks for access validation
13
+ */
14
+ export declare function createAccessControlHooks(config: AccessControlConfig): Pick<MultiTenancyHooks, "onTenantAccessValidation">;
15
+ /**
16
+ * Validates that a token can access a specific tenant based on its organization claim.
17
+ *
18
+ * @param organizationId - The organization ID from the token (may be undefined)
19
+ * @param orgName - The organization name from the token (may be undefined, takes precedence)
20
+ * @param targetTenantId - The tenant ID being accessed
21
+ * @param controlPlaneTenantId - The control plane/management tenant ID
22
+ * @returns true if access is allowed
23
+ */
24
+ export declare function validateTenantAccess(organizationId: string | undefined, targetTenantId: string, controlPlaneTenantId: string, orgName?: string): boolean;
25
+ //# sourceMappingURL=access-control.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-control.d.ts","sourceRoot":"","sources":["../../../src/hooks/access-control.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EAEnB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,mBAAmB,GAC1B,IAAI,CAAC,iBAAiB,EAAE,0BAA0B,CAAC,CAuCrD;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,cAAc,EAAE,MAAM,EACtB,oBAAoB,EAAE,MAAM,EAC5B,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAiBT"}
@@ -0,0 +1,35 @@
1
+ import { DataAdapters } from "authhero";
2
+ import { DatabaseIsolationConfig, MultiTenancyHooks } from "../types";
3
+ /**
4
+ * Creates hooks for per-tenant database resolution.
5
+ *
6
+ * This enables scenarios where each tenant has its own database instance,
7
+ * providing complete data isolation.
8
+ *
9
+ * @param config - Database isolation configuration
10
+ * @returns Hooks for database resolution
11
+ */
12
+ export declare function createDatabaseHooks(config: DatabaseIsolationConfig): Pick<MultiTenancyHooks, "resolveDataAdapters">;
13
+ /**
14
+ * Database factory interface for creating tenant-specific database adapters.
15
+ *
16
+ * Implementations of this interface should live in the respective adapter packages:
17
+ * - D1: @authhero/cloudflare
18
+ * - Turso: @authhero/turso (or similar)
19
+ * - Custom: Implement your own
20
+ */
21
+ export interface DatabaseFactory {
22
+ /**
23
+ * Get or create a database adapter for a tenant.
24
+ */
25
+ getAdapters(tenantId: string): Promise<DataAdapters>;
26
+ /**
27
+ * Provision a new database for a tenant.
28
+ */
29
+ provision(tenantId: string): Promise<void>;
30
+ /**
31
+ * Deprovision (delete) a tenant's database.
32
+ */
33
+ deprovision(tenantId: string): Promise<void>;
34
+ }
35
+ //# sourceMappingURL=database.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../src/hooks/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAEtE;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,uBAAuB,GAC9B,IAAI,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,CAgBhD;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAErD;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3C;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C"}
@@ -0,0 +1,5 @@
1
+ export { createAccessControlHooks, validateTenantAccess, } from "./access-control";
2
+ export { createDatabaseHooks, type DatabaseFactory } from "./database";
3
+ export { createProvisioningHooks } from "./provisioning";
4
+ export { createSyncHooks, type EntitySyncConfig, type SyncHooksResult, } from "./sync";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,mBAAmB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EACL,eAAe,EACf,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,QAAQ,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { MultiTenancyConfig, TenantEntityHooks } from "../types";
2
+ /**
3
+ * Creates hooks for tenant provisioning and deprovisioning.
4
+ *
5
+ * This handles:
6
+ * - Setting the correct audience for new tenants (urn:authhero:tenant:{id})
7
+ * - Creating organizations on the control plane when a new tenant is created
8
+ * - Provisioning databases for new tenants
9
+ * - Cleaning up organizations and databases when tenants are deleted
10
+ *
11
+ * @param config - Multi-tenancy configuration
12
+ * @returns Tenant entity hooks for lifecycle events
13
+ */
14
+ export declare function createProvisioningHooks(config: MultiTenancyConfig): TenantEntityHooks;
15
+ //# sourceMappingURL=provisioning.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provisioning.d.ts","sourceRoot":"","sources":["../../../src/hooks/provisioning.ts"],"names":[],"mappings":"AAeA,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EAElB,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,kBAAkB,GACzB,iBAAiB,CAyEnB"}
@@ -0,0 +1,140 @@
1
+ import { DataAdapters, ResourceServer, ResourceServerInsert } from "authhero";
2
+ import { TenantEntityHooks } from "../types";
3
+ /**
4
+ * Configuration for resource server synchronization
5
+ */
6
+ export interface ResourceServerSyncConfig {
7
+ /**
8
+ * The control plane tenant ID from which resource servers are synced
9
+ */
10
+ controlPlaneTenantId: string;
11
+ /**
12
+ * Function to get the list of all tenant IDs to sync to.
13
+ * Called when a resource server is created/updated/deleted on the control plane.
14
+ */
15
+ getChildTenantIds: () => Promise<string[]>;
16
+ /**
17
+ * Function to get adapters for a specific tenant.
18
+ * Used to write resource servers to child tenants.
19
+ */
20
+ getAdapters: (tenantId: string) => Promise<DataAdapters>;
21
+ /**
22
+ * Optional: Filter function to determine if a resource server should be synced.
23
+ * Return true to sync, false to skip.
24
+ * @default All resource servers are synced
25
+ */
26
+ shouldSync?: (resourceServer: ResourceServer) => boolean;
27
+ /**
28
+ * Optional: Transform the resource server before syncing to child tenants.
29
+ * Useful for modifying identifiers or removing sensitive data.
30
+ */
31
+ transformForSync?: (resourceServer: ResourceServer, targetTenantId: string) => ResourceServerInsert;
32
+ }
33
+ /**
34
+ * Context passed to entity hooks
35
+ */
36
+ interface EntityHookContext {
37
+ tenantId: string;
38
+ adapters: DataAdapters;
39
+ }
40
+ /**
41
+ * Entity hooks for resource server CRUD operations
42
+ */
43
+ export interface ResourceServerEntityHooks {
44
+ afterCreate?: (ctx: EntityHookContext, entity: ResourceServer) => Promise<void>;
45
+ afterUpdate?: (ctx: EntityHookContext, id: string, entity: ResourceServer) => Promise<void>;
46
+ afterDelete?: (ctx: EntityHookContext, id: string) => Promise<void>;
47
+ }
48
+ /**
49
+ * Creates entity hooks for syncing resource servers from the control plane to all child tenants.
50
+ *
51
+ * When a resource server is created, updated, or deleted on the control plane,
52
+ * the change is automatically propagated to all child tenants.
53
+ *
54
+ * @param config - Resource server sync configuration
55
+ * @returns Entity hooks for resource server synchronization
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * import { createResourceServerSyncHooks } from "@authhero/multi-tenancy";
60
+ *
61
+ * const resourceServerHooks = createResourceServerSyncHooks({
62
+ * controlPlaneTenantId: "main",
63
+ * getChildTenantIds: async () => {
64
+ * const tenants = await db.tenants.list();
65
+ * return tenants.filter(t => t.id !== "main").map(t => t.id);
66
+ * },
67
+ * getAdapters: async (tenantId) => {
68
+ * return createAdaptersForTenant(tenantId);
69
+ * },
70
+ * });
71
+ *
72
+ * // Use with AuthHero config
73
+ * const config: AuthHeroConfig = {
74
+ * dataAdapter,
75
+ * entityHooks: {
76
+ * resourceServers: resourceServerHooks,
77
+ * },
78
+ * };
79
+ * ```
80
+ */
81
+ export declare function createResourceServerSyncHooks(config: ResourceServerSyncConfig): ResourceServerEntityHooks;
82
+ /**
83
+ * Configuration for syncing resource servers to new tenants
84
+ */
85
+ export interface TenantResourceServerSyncConfig {
86
+ /**
87
+ * The control plane tenant ID from which resource servers are copied
88
+ */
89
+ controlPlaneTenantId: string;
90
+ /**
91
+ * Function to get adapters for the control plane.
92
+ * Used to read existing resource servers.
93
+ */
94
+ getControlPlaneAdapters: () => Promise<DataAdapters>;
95
+ /**
96
+ * Function to get adapters for the new tenant.
97
+ * Used to write resource servers to the new tenant.
98
+ */
99
+ getAdapters: (tenantId: string) => Promise<DataAdapters>;
100
+ /**
101
+ * Optional: Filter function to determine if a resource server should be synced.
102
+ * Return true to sync, false to skip.
103
+ * @default All resource servers are synced
104
+ */
105
+ shouldSync?: (resourceServer: ResourceServer) => boolean;
106
+ /**
107
+ * Optional: Transform the resource server before syncing to the new tenant.
108
+ * Useful for modifying identifiers or removing sensitive data.
109
+ */
110
+ transformForSync?: (resourceServer: ResourceServer, targetTenantId: string) => ResourceServerInsert;
111
+ }
112
+ /**
113
+ * Creates a tenant afterCreate hook that copies all resource servers from the control plane
114
+ * to a newly created tenant.
115
+ *
116
+ * This should be used with the MultiTenancyHooks.tenants.afterCreate hook.
117
+ *
118
+ * @param config - Configuration for tenant resource server sync
119
+ * @returns A TenantEntityHooks object with afterCreate implemented
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * import { createTenantResourceServerSyncHooks } from "@authhero/multi-tenancy";
124
+ *
125
+ * const resourceServerSyncHooks = createTenantResourceServerSyncHooks({
126
+ * controlPlaneTenantId: "main",
127
+ * getControlPlaneAdapters: async () => controlPlaneAdapters,
128
+ * getAdapters: async (tenantId) => createAdaptersForTenant(tenantId),
129
+ * });
130
+ *
131
+ * const multiTenancyHooks: MultiTenancyHooks = {
132
+ * tenants: {
133
+ * afterCreate: resourceServerSyncHooks.afterCreate,
134
+ * },
135
+ * };
136
+ * ```
137
+ */
138
+ export declare function createTenantResourceServerSyncHooks(config: TenantResourceServerSyncConfig): TenantEntityHooks;
139
+ export {};
140
+ //# sourceMappingURL=resource-server-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-server-sync.d.ts","sourceRoot":"","sources":["../../../src/hooks/resource-server-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,cAAc,EACd,oBAAoB,EAErB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,iBAAiB,EAAqB,MAAM,UAAU,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IACH,iBAAiB,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE3C;;;OAGG;IACH,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzD;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,cAAc,EAAE,cAAc,KAAK,OAAO,CAAC;IAEzD;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CACjB,cAAc,EAAE,cAAc,EAC9B,cAAc,EAAE,MAAM,KACnB,oBAAoB,CAAC;CAC3B;AAED;;GAEG;AACH,UAAU,iBAAiB;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,YAAY,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,WAAW,CAAC,EAAE,CACZ,GAAG,EAAE,iBAAiB,EACtB,MAAM,EAAE,cAAc,KACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,WAAW,CAAC,EAAE,CACZ,GAAG,EAAE,iBAAiB,EACtB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,cAAc,KACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,wBAAwB,GAC/B,yBAAyB,CA2L3B;AAED;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC7C;;OAEG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IACH,uBAAuB,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;IAErD;;;OAGG;IACH,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzD;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,cAAc,EAAE,cAAc,KAAK,OAAO,CAAC;IAEzD;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CACjB,cAAc,EAAE,cAAc,EAC9B,cAAc,EAAE,MAAM,KACnB,oBAAoB,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,mCAAmC,CACjD,MAAM,EAAE,8BAA8B,GACrC,iBAAiB,CAiFnB"}
@@ -0,0 +1,145 @@
1
+ import { DataAdapters, Role, RoleInsert } from "authhero";
2
+ import { TenantEntityHooks } from "../types";
3
+ /**
4
+ * Configuration for role synchronization
5
+ */
6
+ export interface RoleSyncConfig {
7
+ /**
8
+ * The control plane tenant ID from which roles are synced
9
+ */
10
+ controlPlaneTenantId: string;
11
+ /**
12
+ * Function to get the list of all tenant IDs to sync to.
13
+ * Called when a role is created/updated/deleted on the control plane.
14
+ */
15
+ getChildTenantIds: () => Promise<string[]>;
16
+ /**
17
+ * Function to get adapters for a specific tenant.
18
+ * Used to write roles to child tenants.
19
+ */
20
+ getAdapters: (tenantId: string) => Promise<DataAdapters>;
21
+ /**
22
+ * Optional: Filter function to determine if a role should be synced.
23
+ * Return true to sync, false to skip.
24
+ * @default All roles are synced
25
+ */
26
+ shouldSync?: (role: Role) => boolean;
27
+ /**
28
+ * Optional: Transform the role before syncing to child tenants.
29
+ * Useful for modifying names or removing sensitive data.
30
+ */
31
+ transformForSync?: (role: Role, targetTenantId: string) => RoleInsert;
32
+ }
33
+ /**
34
+ * Context passed to entity hooks
35
+ */
36
+ interface EntityHookContext {
37
+ tenantId: string;
38
+ adapters: DataAdapters;
39
+ }
40
+ /**
41
+ * Entity hooks for role CRUD operations
42
+ */
43
+ export interface RoleEntityHooks {
44
+ afterCreate?: (ctx: EntityHookContext, entity: Role) => Promise<void>;
45
+ afterUpdate?: (ctx: EntityHookContext, id: string, entity: Role) => Promise<void>;
46
+ afterDelete?: (ctx: EntityHookContext, id: string) => Promise<void>;
47
+ }
48
+ /**
49
+ * Creates entity hooks for syncing roles from the control plane to all child tenants.
50
+ *
51
+ * When a role is created, updated, or deleted on the control plane,
52
+ * the change is automatically propagated to all child tenants.
53
+ *
54
+ * @param config - Role sync configuration
55
+ * @returns Entity hooks for role synchronization
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * import { createRoleSyncHooks } from "@authhero/multi-tenancy";
60
+ *
61
+ * const roleHooks = createRoleSyncHooks({
62
+ * controlPlaneTenantId: "main",
63
+ * getChildTenantIds: async () => {
64
+ * const tenants = await db.tenants.list();
65
+ * return tenants.filter(t => t.id !== "main").map(t => t.id);
66
+ * },
67
+ * getAdapters: async (tenantId) => {
68
+ * return createAdaptersForTenant(tenantId);
69
+ * },
70
+ * });
71
+ *
72
+ * // Use with AuthHero config
73
+ * const config: AuthHeroConfig = {
74
+ * dataAdapter,
75
+ * entityHooks: {
76
+ * roles: roleHooks,
77
+ * },
78
+ * };
79
+ * ```
80
+ */
81
+ export declare function createRoleSyncHooks(config: RoleSyncConfig): RoleEntityHooks;
82
+ /**
83
+ * Configuration for syncing roles to new tenants
84
+ */
85
+ export interface TenantRoleSyncConfig {
86
+ /**
87
+ * The control plane tenant ID from which roles are copied
88
+ */
89
+ controlPlaneTenantId: string;
90
+ /**
91
+ * Function to get adapters for the control plane.
92
+ * Used to read existing roles.
93
+ */
94
+ getControlPlaneAdapters: () => Promise<DataAdapters>;
95
+ /**
96
+ * Function to get adapters for the new tenant.
97
+ * Used to write roles to the new tenant.
98
+ */
99
+ getAdapters: (tenantId: string) => Promise<DataAdapters>;
100
+ /**
101
+ * Optional: Filter function to determine if a role should be synced.
102
+ * Return true to sync, false to skip.
103
+ * @default All roles are synced
104
+ */
105
+ shouldSync?: (role: Role) => boolean;
106
+ /**
107
+ * Optional: Transform the role before syncing to the new tenant.
108
+ * Useful for modifying names or removing sensitive data.
109
+ */
110
+ transformForSync?: (role: Role, targetTenantId: string) => RoleInsert;
111
+ /**
112
+ * Whether to also sync role permissions (scopes from resource servers).
113
+ * @default true
114
+ */
115
+ syncPermissions?: boolean;
116
+ }
117
+ /**
118
+ * Creates a tenant afterCreate hook that copies all roles from the control plane
119
+ * to a newly created tenant.
120
+ *
121
+ * This should be used with the MultiTenancyHooks.tenants.afterCreate hook.
122
+ *
123
+ * @param config - Configuration for tenant role sync
124
+ * @returns A TenantEntityHooks object with afterCreate implemented
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * import { createTenantRoleSyncHooks } from "@authhero/multi-tenancy";
129
+ *
130
+ * const roleSyncHooks = createTenantRoleSyncHooks({
131
+ * controlPlaneTenantId: "main",
132
+ * getControlPlaneAdapters: async () => controlPlaneAdapters,
133
+ * getAdapters: async (tenantId) => createAdaptersForTenant(tenantId),
134
+ * });
135
+ *
136
+ * const multiTenancyHooks: MultiTenancyHooks = {
137
+ * tenants: {
138
+ * afterCreate: roleSyncHooks.afterCreate,
139
+ * },
140
+ * };
141
+ * ```
142
+ */
143
+ export declare function createTenantRoleSyncHooks(config: TenantRoleSyncConfig): TenantEntityHooks;
144
+ export {};
145
+ //# sourceMappingURL=role-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-sync.d.ts","sourceRoot":"","sources":["../../../src/hooks/role-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAY,MAAM,UAAU,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAqB,MAAM,UAAU,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IACH,iBAAiB,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE3C;;;OAGG;IACH,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzD;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC;IAErC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,KAAK,UAAU,CAAC;CACvE;AAED;;GAEG;AACH,UAAU,iBAAiB;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,YAAY,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,EAAE,MAAM,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,WAAW,CAAC,EAAE,CACZ,GAAG,EAAE,iBAAiB,EACtB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,IAAI,KACT,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,eAAe,CAwI3E;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IACH,uBAAuB,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;IAErD;;;OAGG;IACH,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzD;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC;IAErC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,KAAK,UAAU,CAAC;IAEtE;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,oBAAoB,GAC3B,iBAAiB,CA4GnB"}