@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.
- package/dist/multi-tenancy.cjs +1 -1
- package/dist/multi-tenancy.mjs +24 -25
- 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 +9 -9
- package/dist/multi-tenancy.d.ts +0 -41331
package/dist/multi-tenancy.cjs
CHANGED
|
@@ -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;
|
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 I,
|
|
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 {
|
|
@@ -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
|
{
|
|
@@ -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}",
|
|
@@ -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;
|
|
@@ -841,7 +840,7 @@ function U(t, e) {
|
|
|
841
840
|
function fe(t, e) {
|
|
842
841
|
return U(t, e);
|
|
843
842
|
}
|
|
844
|
-
const
|
|
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
|
|
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 } =
|
|
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
|
|
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
|
|
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
|
-
|
|
1046
|
+
Ie as createMultiTenancyPlugin,
|
|
1048
1047
|
me as createProtectSyncedMiddleware,
|
|
1049
1048
|
re as createProvisioningHooks,
|
|
1050
1049
|
U as createRuntimeFallbackAdapter,
|
|
1051
|
-
|
|
1050
|
+
Ae as createSettingsInheritanceAdapter,
|
|
1052
1051
|
we as createSubdomainMiddleware,
|
|
1053
1052
|
ce as createSyncHooks,
|
|
1054
1053
|
M as createTenantsOpenAPIRouter,
|
|
1055
|
-
|
|
1056
|
-
|
|
1054
|
+
Pe as initMultiTenant,
|
|
1055
|
+
Se as setupMultiTenancy,
|
|
1057
1056
|
te as validateTenantAccess,
|
|
1058
1057
|
fe as withRuntimeFallback,
|
|
1059
|
-
|
|
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"}
|