@authhero/multi-tenancy 14.1.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.
- package/dist/multi-tenancy.cjs +1 -1
- package/dist/multi-tenancy.mjs +45 -39
- package/dist/types/hooks/access-control.d.ts +25 -0
- package/dist/types/hooks/access-control.d.ts.map +1 -0
- package/dist/types/hooks/database.d.ts +35 -0
- package/dist/types/hooks/database.d.ts.map +1 -0
- package/dist/types/hooks/index.d.ts +5 -0
- package/dist/types/hooks/index.d.ts.map +1 -0
- package/dist/types/hooks/provisioning.d.ts +15 -0
- package/dist/types/hooks/provisioning.d.ts.map +1 -0
- package/dist/types/hooks/resource-server-sync.d.ts +140 -0
- package/dist/types/hooks/resource-server-sync.d.ts.map +1 -0
- package/dist/types/hooks/role-sync.d.ts +145 -0
- package/dist/types/hooks/role-sync.d.ts.map +1 -0
- package/dist/types/hooks/sync.d.ts +79 -0
- package/dist/types/hooks/sync.d.ts.map +1 -0
- package/dist/types/index.d.ts +117 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/init.d.ts +110 -0
- package/dist/types/init.d.ts.map +1 -0
- package/dist/types/middleware/index.d.ts +114 -0
- package/dist/types/middleware/index.d.ts.map +1 -0
- package/dist/types/middleware/protect-synced.d.ts +40 -0
- package/dist/types/middleware/protect-synced.d.ts.map +1 -0
- package/dist/types/middleware/settings-inheritance.d.ts +89 -0
- package/dist/types/middleware/settings-inheritance.d.ts.map +1 -0
- package/dist/types/plugin.d.ts +66 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/routes/index.d.ts +2 -0
- package/dist/types/routes/index.d.ts.map +1 -0
- package/dist/types/routes/tenants.d.ts +18 -0
- package/dist/types/routes/tenants.d.ts.map +1 -0
- package/dist/types/types.d.ts +295 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +3 -0
- package/dist/types/utils/index.d.ts.map +1 -0
- package/package.json +8 -8
- package/dist/multi-tenancy.d.ts +0 -41308
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 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;
|
package/dist/multi-tenancy.mjs
CHANGED
|
@@ -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
|
|
6
|
-
import { OpenAPIHono as
|
|
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 {
|
|
@@ -246,7 +245,7 @@ function N(t, e, n = () => !0) {
|
|
|
246
245
|
async afterCreate(o, r) {
|
|
247
246
|
if (r.id !== s)
|
|
248
247
|
try {
|
|
249
|
-
const c = await a(), m = await i(r.id), l = e(c), f = e(m), d = await
|
|
248
|
+
const c = await a(), m = await i(r.id), l = e(c), f = e(m), d = await I(
|
|
250
249
|
(u) => l.listPaginated(s, u),
|
|
251
250
|
l.listKey,
|
|
252
251
|
{ cursorField: "id", pageSize: 100 }
|
|
@@ -332,7 +331,7 @@ function ce(t) {
|
|
|
332
331
|
if (u.id !== t.controlPlaneTenantId) {
|
|
333
332
|
await ((p = c == null ? void 0 : c.afterCreate) == null ? void 0 : p.call(c, d, u));
|
|
334
333
|
try {
|
|
335
|
-
const g = await t.getControlPlaneAdapters(), y = await t.getAdapters(u.id), w = await
|
|
334
|
+
const g = await t.getControlPlaneAdapters(), y = await t.getAdapters(u.id), w = await I(
|
|
336
335
|
(v) => g.roles.list(
|
|
337
336
|
t.controlPlaneTenantId,
|
|
338
337
|
v
|
|
@@ -453,14 +452,14 @@ var b = class extends Error {
|
|
|
453
452
|
}
|
|
454
453
|
};
|
|
455
454
|
function M(t, e) {
|
|
456
|
-
const n = new
|
|
455
|
+
const n = new x();
|
|
457
456
|
return n.openapi(
|
|
458
|
-
|
|
457
|
+
O({
|
|
459
458
|
tags: ["tenants"],
|
|
460
459
|
method: "get",
|
|
461
460
|
path: "/",
|
|
462
461
|
request: {
|
|
463
|
-
query:
|
|
462
|
+
query: X
|
|
464
463
|
},
|
|
465
464
|
security: [
|
|
466
465
|
{
|
|
@@ -501,14 +500,14 @@ function M(t, e) {
|
|
|
501
500
|
}) : s.json({ tenants: w.tenants });
|
|
502
501
|
}
|
|
503
502
|
if (t.accessControl && (m != null && m.sub)) {
|
|
504
|
-
const w = t.accessControl.controlPlaneTenantId, v = (await
|
|
505
|
-
(
|
|
503
|
+
const w = t.accessControl.controlPlaneTenantId, v = (await I(
|
|
504
|
+
(P) => s.env.data.userOrganizations.listUserOrganizations(
|
|
506
505
|
w,
|
|
507
506
|
m.sub,
|
|
508
|
-
|
|
507
|
+
P
|
|
509
508
|
),
|
|
510
509
|
"organizations"
|
|
511
|
-
)).map((
|
|
510
|
+
)).map((P) => P.name);
|
|
512
511
|
if (v.length === 0)
|
|
513
512
|
return r ? s.json({
|
|
514
513
|
tenants: [],
|
|
@@ -524,7 +523,7 @@ function M(t, e) {
|
|
|
524
523
|
limit: _,
|
|
525
524
|
length: h
|
|
526
525
|
}) : s.json({ tenants: [] });
|
|
527
|
-
const F = D.map((
|
|
526
|
+
const F = D.map((P) => `id:${P}`).join(" OR "), K = c ? `(${F}) AND (${c})` : F, j = await s.env.data.tenants.list({
|
|
528
527
|
q: K,
|
|
529
528
|
per_page: _,
|
|
530
529
|
include_totals: !1
|
|
@@ -551,7 +550,7 @@ function M(t, e) {
|
|
|
551
550
|
}) : s.json({ tenants: d.tenants });
|
|
552
551
|
}
|
|
553
552
|
), n.openapi(
|
|
554
|
-
|
|
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:
|
|
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
|
-
|
|
605
|
+
O({
|
|
607
606
|
tags: ["tenants"],
|
|
608
607
|
method: "delete",
|
|
609
608
|
path: "/{id}",
|
|
@@ -642,7 +641,7 @@ function M(t, e) {
|
|
|
642
641
|
throw new b(403, {
|
|
643
642
|
message: "Cannot delete the control plane"
|
|
644
643
|
});
|
|
645
|
-
if (!(await
|
|
644
|
+
if (!(await I(
|
|
646
645
|
(u) => s.env.data.userOrganizations.listUserOrganizations(
|
|
647
646
|
l,
|
|
648
647
|
m.sub,
|
|
@@ -748,11 +747,11 @@ function U(t, e) {
|
|
|
748
747
|
);
|
|
749
748
|
if (!(u != null && u.options))
|
|
750
749
|
return d;
|
|
751
|
-
const p =
|
|
750
|
+
const p = z.parse({
|
|
752
751
|
...u || {},
|
|
753
752
|
...d
|
|
754
753
|
});
|
|
755
|
-
return p.options =
|
|
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 =
|
|
796
|
+
const m = z.parse({
|
|
798
797
|
...c,
|
|
799
798
|
...o
|
|
800
799
|
});
|
|
801
|
-
return m.options =
|
|
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 =
|
|
816
|
+
const f = z.parse({
|
|
818
817
|
...l,
|
|
819
818
|
...m
|
|
820
819
|
});
|
|
821
|
-
return f.options =
|
|
820
|
+
return f.options = R.parse({
|
|
822
821
|
...l.options || {},
|
|
823
822
|
...m.options
|
|
824
823
|
}), f;
|
|
@@ -843,6 +842,12 @@ function fe(t, e) {
|
|
|
843
842
|
}
|
|
844
843
|
const Ae = U, Ce = fe;
|
|
845
844
|
function ge(t) {
|
|
845
|
+
return async (e, n) => {
|
|
846
|
+
const s = e.var.user;
|
|
847
|
+
return (s == null ? void 0 : s.tenant_id) === t && s.org_name && e.set("tenant_id", s.org_name), n();
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
function pe(t) {
|
|
846
851
|
return async (e, n) => {
|
|
847
852
|
if (!t.accessControl)
|
|
848
853
|
return n();
|
|
@@ -865,7 +870,7 @@ function ge(t) {
|
|
|
865
870
|
return n();
|
|
866
871
|
};
|
|
867
872
|
}
|
|
868
|
-
function
|
|
873
|
+
function we(t) {
|
|
869
874
|
return async (e, n) => {
|
|
870
875
|
if (!t.subdomainRouting)
|
|
871
876
|
return n();
|
|
@@ -900,7 +905,7 @@ function pe(t) {
|
|
|
900
905
|
return e.set("tenant_id", c), n();
|
|
901
906
|
};
|
|
902
907
|
}
|
|
903
|
-
function
|
|
908
|
+
function ye(t) {
|
|
904
909
|
return async (e, n) => {
|
|
905
910
|
if (!t.databaseIsolation)
|
|
906
911
|
return n();
|
|
@@ -924,13 +929,13 @@ function we(t) {
|
|
|
924
929
|
};
|
|
925
930
|
}
|
|
926
931
|
function B(t) {
|
|
927
|
-
const e =
|
|
932
|
+
const e = we(t), n = pe(t), s = ye(t);
|
|
928
933
|
return async (a, i) => (await e(a, async () => {
|
|
929
934
|
}), await n(a, async () => {
|
|
930
935
|
}), await s(a, async () => {
|
|
931
936
|
}), i());
|
|
932
937
|
}
|
|
933
|
-
function
|
|
938
|
+
function Pe(t) {
|
|
934
939
|
const {
|
|
935
940
|
dataAdapter: e,
|
|
936
941
|
controlPlaneTenantId: n = "control_plane",
|
|
@@ -947,7 +952,7 @@ function Ie(t) {
|
|
|
947
952
|
roles: s.roles ?? !0
|
|
948
953
|
} : { resourceServers: !1, roles: !1 }, g = {
|
|
949
954
|
controlPlaneTenantId: n,
|
|
950
|
-
getChildTenantIds: c ?? (async () => (await
|
|
955
|
+
getChildTenantIds: c ?? (async () => (await I(
|
|
951
956
|
(_) => e.tenants.list(_),
|
|
952
957
|
"tenants",
|
|
953
958
|
{ cursorField: "id", pageSize: 100 }
|
|
@@ -973,7 +978,7 @@ function Ie(t) {
|
|
|
973
978
|
}
|
|
974
979
|
},
|
|
975
980
|
{ tenants: w }
|
|
976
|
-
), { app: h } =
|
|
981
|
+
), { app: h } = Z({
|
|
977
982
|
dataAdapter: e,
|
|
978
983
|
...l,
|
|
979
984
|
entityHooks: A,
|
|
@@ -982,9 +987,9 @@ function Ie(t) {
|
|
|
982
987
|
{ path: "/tenants", router: v }
|
|
983
988
|
]
|
|
984
989
|
});
|
|
985
|
-
return f && h.use("/api/v2/*", me()), { app: h, controlPlaneTenantId: n };
|
|
990
|
+
return h.use("/api/v2/*", ge(n)), f && h.use("/api/v2/*", me()), { app: h, controlPlaneTenantId: n };
|
|
986
991
|
}
|
|
987
|
-
function
|
|
992
|
+
function Ie(t) {
|
|
988
993
|
const e = q(t);
|
|
989
994
|
return {
|
|
990
995
|
name: "multi-tenancy",
|
|
@@ -1017,7 +1022,7 @@ function q(t) {
|
|
|
1017
1022
|
tenants: s
|
|
1018
1023
|
};
|
|
1019
1024
|
}
|
|
1020
|
-
function
|
|
1025
|
+
function he(t) {
|
|
1021
1026
|
const e = new V(), n = q(t);
|
|
1022
1027
|
return e.route("/tenants", M(t, n)), e;
|
|
1023
1028
|
}
|
|
@@ -1025,27 +1030,28 @@ function Se(t) {
|
|
|
1025
1030
|
return {
|
|
1026
1031
|
hooks: q(t),
|
|
1027
1032
|
middleware: B(t),
|
|
1028
|
-
app:
|
|
1033
|
+
app: he(t),
|
|
1029
1034
|
config: t
|
|
1030
1035
|
};
|
|
1031
1036
|
}
|
|
1032
1037
|
export {
|
|
1033
1038
|
ee as createAccessControlHooks,
|
|
1034
|
-
|
|
1039
|
+
pe as createAccessControlMiddleware,
|
|
1040
|
+
ge as createControlPlaneTenantMiddleware,
|
|
1035
1041
|
ne as createDatabaseHooks,
|
|
1036
|
-
|
|
1037
|
-
|
|
1042
|
+
ye as createDatabaseMiddleware,
|
|
1043
|
+
he as createMultiTenancy,
|
|
1038
1044
|
q as createMultiTenancyHooks,
|
|
1039
1045
|
B as createMultiTenancyMiddleware,
|
|
1040
|
-
|
|
1046
|
+
Ie as createMultiTenancyPlugin,
|
|
1041
1047
|
me as createProtectSyncedMiddleware,
|
|
1042
1048
|
re as createProvisioningHooks,
|
|
1043
1049
|
U as createRuntimeFallbackAdapter,
|
|
1044
1050
|
Ae as createSettingsInheritanceAdapter,
|
|
1045
|
-
|
|
1051
|
+
we as createSubdomainMiddleware,
|
|
1046
1052
|
ce as createSyncHooks,
|
|
1047
1053
|
M as createTenantsOpenAPIRouter,
|
|
1048
|
-
|
|
1054
|
+
Pe as initMultiTenant,
|
|
1049
1055
|
Se as setupMultiTenancy,
|
|
1050
1056
|
te as validateTenantAccess,
|
|
1051
1057
|
fe as withRuntimeFallback,
|
|
@@ -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"}
|