@authhero/multi-tenancy 13.4.0 → 13.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/multi-tenancy.cjs +1 -1
- package/dist/multi-tenancy.d.ts +35 -0
- package/dist/multi-tenancy.mjs +332 -280
- package/package.json +2 -2
package/dist/multi-tenancy.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var W=Object.defineProperty;var Q=(a,e,s)=>e in a?W(a,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):a[e]=s;var S=(a,e,s)=>Q(a,typeof e!="symbol"?e+"":e,s);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const $=require("hono"),D=require("authhero"),L=require("zod"),M=require("@authhero/adapter-interfaces");function I(a){const{mainTenantId:e,requireOrganizationMatch:s=!0}=a;return{async onTenantAccessValidation(n,t){if(t===e)return!0;if(s){const o=n.var.organization_id;return o?o===t:!1}return!0}}}function z(a,e,s){return e===s?!0:a?a===e:!1}function F(a){return{async resolveDataAdapters(e){try{return await a.getAdapters(e)}catch(s){console.error(`Failed to resolve data adapters for tenant ${e}:`,s);return}}}}function P(a){return{async afterCreate(e,s){const{accessControl:n,databaseIsolation:t,settingsInheritance:o}=a;n&&e.ctx&&await Y(e,s,n),t!=null&&t.onProvision&&await t.onProvision(s.id),(o==null?void 0:o.inheritFromMain)!==!1&&e.ctx&&await B(e,s,a)},async beforeDelete(e,s){const{accessControl:n,databaseIsolation:t}=a;if(n)try{const d=(await e.adapters.organizations.list(n.mainTenantId)).organizations.find(c=>c.name===s);d&&await e.adapters.organizations.remove(n.mainTenantId,d.id)}catch(o){console.warn(`Failed to remove organization for tenant ${s}:`,o)}if(t!=null&&t.onDeprovision)try{await t.onDeprovision(s)}catch(o){console.warn(`Failed to deprovision database for tenant ${s}:`,o)}}}}async function Y(a,e,s){const{mainTenantId:n,defaultPermissions:t,defaultRoles:o}=s;await a.adapters.organizations.create(n,{id:e.id,name:e.id,display_name:e.friendly_name||e.id}),o&&o.length>0&&console.log(`Would assign roles ${o.join(", ")} to organization ${e.id}`),t&&t.length>0&&console.log(`Would grant permissions ${t.join(", ")} to organization ${e.id}`)}async function B(a,e,s){const{accessControl:n,settingsInheritance:t}=s;if(!n)return;const o=await a.adapters.tenants.get(n.mainTenantId);if(!o)return;let d={...o};const c=["id","created_at","updated_at","friendly_name","audience","sender_email","sender_name"];for(const i of c)delete d[i];if(t!=null&&t.inheritedKeys){const i={};for(const r of t.inheritedKeys)r in o&&!c.includes(r)&&(i[r]=o[r]);d=i}if(t!=null&&t.excludedKeys)for(const i of t.excludedKeys)delete d[i];t!=null&&t.transformSettings&&(d=t.transformSettings(d,e.id)),Object.keys(d).length>0&&await a.adapters.tenants.update(e.id,d)}async function q(a,e,s={}){const{cursorField:n="id",sortOrder:t="asc",pageSize:o=100,maxItems:d=1e4,q:c}=s,i=[];let r,u=!0;for(;u;){let f=c||"";if(r){const w=`${n}:${t==="asc"?">":"<"}${r}`;f=f?`(${f}) AND ${w}`:w}const l={per_page:o,page:0,sort:{sort_by:n,sort_order:t},...f&&{q:f}},h=(await a(l))[e]||[];if(h.length===0)u=!1;else{i.push(...h);const _=h[h.length-1];if(_&&typeof _=="object"){const w=_[n];w!=null&&(r=String(w))}h.length<o&&(u=!1),d!==-1&&i.length>=d&&(console.warn(`fetchAll: Reached maxItems limit (${d}). There may be more items.`),u=!1)}}return i}function j(a){const{mainTenantId:e,getChildTenantIds:s,getAdapters:n,shouldSync:t=()=>!0,transformForSync:o}=a;async function d(r,u,f){return(await r.resourceServers.list(u,{q:`identifier:${f}`,per_page:1})).resource_servers[0]??null}async function c(r,u){const f=await s();await Promise.all(f.map(async l=>{try{const m=await n(l),_={...o?o(r,l):{name:r.name,identifier:r.identifier,scopes:r.scopes,signing_alg:r.signing_alg,signing_secret:r.signing_secret,token_lifetime:r.token_lifetime,token_lifetime_for_web:r.token_lifetime_for_web,skip_consent_for_verifiable_first_party_clients:r.skip_consent_for_verifiable_first_party_clients,allow_offline_access:r.allow_offline_access,verificationKey:r.verificationKey,options:r.options},is_system:!0};if(u==="create"){const w=await d(m,l,r.identifier);w&&w.id?await m.resourceServers.update(l,w.id,_):await m.resourceServers.create(l,_)}else{const w=await d(m,l,r.identifier);w&&w.id&&await m.resourceServers.update(l,w.id,_)}}catch(m){console.error(`Failed to sync resource server "${r.identifier}" to tenant "${l}":`,m)}}))}async function i(r){const u=await s();await Promise.all(u.map(async f=>{try{const l=await n(f),m=await d(l,f,r);m&&m.id&&await l.resourceServers.remove(f,m.id)}catch(l){console.error(`Failed to delete resource server "${r}" from tenant "${f}":`,l)}}))}return{afterCreate:async(r,u)=>{r.tenantId===e&&t(u)&&await c(u,"create")},afterUpdate:async(r,u,f)=>{r.tenantId===e&&t(f)&&await c(f,"update")},afterDelete:async(r,u)=>{r.tenantId===e&&await i(u)}}}function U(a){const{mainTenantId:e,getMainTenantAdapters:s,getAdapters:n,shouldSync:t=()=>!0,transformForSync:o}=a;return{async afterCreate(d,c){if(c.id!==e)try{const i=await s(),r=await n(c.id),u=await q(f=>i.resourceServers.list(e,f),"resource_servers",{cursorField:"id",pageSize:100});await Promise.all(u.filter(f=>t(f)).map(async f=>{const l=f;try{const m=o?o(l,c.id):{name:l.name,identifier:l.identifier,scopes:l.scopes,signing_alg:l.signing_alg,signing_secret:l.signing_secret,token_lifetime:l.token_lifetime,token_lifetime_for_web:l.token_lifetime_for_web,skip_consent_for_verifiable_first_party_clients:l.skip_consent_for_verifiable_first_party_clients,allow_offline_access:l.allow_offline_access,verificationKey:l.verificationKey,options:l.options};await r.resourceServers.create(c.id,{...m,is_system:!0})}catch(m){console.error(`Failed to sync resource server "${l.identifier}" to new tenant "${c.id}":`,m)}}))}catch(i){console.error(`Failed to sync resource servers to new tenant "${c.id}":`,i)}}}}var p=class extends Error{constructor(e=500,s){super(s==null?void 0:s.message,{cause:s==null?void 0:s.cause});S(this,"res");S(this,"status");this.res=s==null?void 0:s.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 C(a,e){const s=new $.Hono;return s.get("/",async n=>{var u;if(a.accessControl&&await((u=e.onTenantAccessValidation)==null?void 0:u.call(e,n,a.accessControl.mainTenantId))===!1)throw new p(403,{message:"Access denied to tenant management"});const t=M.auth0QuerySchema.parse(n.req.query()),{page:o,per_page:d,include_totals:c,q:i}=t,r=await n.env.data.tenants.list({page:o,per_page:d,include_totals:c,q:i});return c?n.json(r):n.json(r.tenants)}),s.get("/:id",async n=>{var c;const t=n.req.param("id");if(await((c=e.onTenantAccessValidation)==null?void 0:c.call(e,n,t))===!1)throw new p(403,{message:"Access denied to this tenant"});const d=await n.env.data.tenants.get(t);if(!d)throw new p(404,{message:"Tenant not found"});return n.json(d)}),s.post("/",async n=>{var t,o,d,c;try{if(a.accessControl&&await((t=e.onTenantAccessValidation)==null?void 0:t.call(e,n,a.accessControl.mainTenantId))===!1)throw new p(403,{message:"Access denied to create tenants"});let i=M.tenantInsertSchema.parse(await n.req.json());const r={adapters:n.env.data,ctx:n};(o=e.tenants)!=null&&o.beforeCreate&&(i=await e.tenants.beforeCreate(r,i));const u=await n.env.data.tenants.create(i);return(d=e.tenants)!=null&&d.afterCreate&&await e.tenants.afterCreate(r,u),n.json(u,201)}catch(i){throw i instanceof L.z.ZodError?new p(400,{message:"Validation error",cause:i}):i instanceof Error&&("code"in i&&i.code==="SQLITE_CONSTRAINT_PRIMARYKEY"||(c=i.message)!=null&&c.includes("UNIQUE constraint failed"))?new p(409,{message:"Tenant with this ID already exists"}):i}}),s.patch("/:id",async n=>{var m,h,_;const t=n.req.param("id");if(await((m=e.onTenantAccessValidation)==null?void 0:m.call(e,n,t))===!1)throw new p(403,{message:"Access denied to update this tenant"});const d=M.tenantInsertSchema.partial().parse(await n.req.json()),{id:c,...i}=d;if(!await n.env.data.tenants.get(t))throw new p(404,{message:"Tenant not found"});const u={adapters:n.env.data,ctx:n};let f=i;(h=e.tenants)!=null&&h.beforeUpdate&&(f=await e.tenants.beforeUpdate(u,t,i)),await n.env.data.tenants.update(t,f);const l=await n.env.data.tenants.get(t);if(!l)throw new p(404,{message:"Tenant not found after update"});return(_=e.tenants)!=null&&_.afterUpdate&&await e.tenants.afterUpdate(u,l),n.json(l)}),s.delete("/:id",async n=>{var c,i,r;const t=n.req.param("id");if(a.accessControl&&t===a.accessControl.mainTenantId)throw new p(400,{message:"Cannot delete the main tenant"});if(a.accessControl&&await((c=e.onTenantAccessValidation)==null?void 0:c.call(e,n,a.accessControl.mainTenantId))===!1)throw new p(403,{message:"Access denied to delete tenants"});if(!await n.env.data.tenants.get(t))throw new p(404,{message:"Tenant not found"});const d={adapters:n.env.data,ctx:n};return(i=e.tenants)!=null&&i.beforeDelete&&await e.tenants.beforeDelete(d,t),await n.env.data.tenants.remove(t),(r=e.tenants)!=null&&r.afterDelete&&await e.tenants.afterDelete(d,t),n.body(null,204)}),s}function Z(a){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:s,type:n}of e){const t=a.match(s);if(t&&t[1])return{type:n,id:t[1]}}return null}async function G(a,e,s){try{switch(s.type){case"resource_server":{const n=await a.resourceServers.get(e,s.id);return(n==null?void 0:n.is_system)===!0}case"role":{const n=await a.roles.get(e,s.id);return(n==null?void 0:n.is_system)===!0}case"connection":{const n=await a.connections.get(e,s.id);return(n==null?void 0:n.is_system)===!0}default:return!1}}catch{return!1}}function J(a){return{resource_server:"resource server",role:"role",connection:"connection"}[a]}function K(){return async(a,e)=>{if(!["PATCH","PUT","DELETE"].includes(a.req.method))return e();const s=Z(a.req.path);if(!s)return e();const n=a.var.tenant_id||a.req.header("x-tenant-id")||a.req.header("tenant-id");if(!n)return e();if(await G(a.env.data,n,s))throw new p(403,{message:`This ${J(s.type)} is a system resource and cannot be modified. Make changes in the main tenant instead.`});return e()}}function O(a){return async(e,s)=>{if(!a.accessControl)return s();const n=e.var.tenant_id,t=e.var.organization_id;if(!n)throw new p(400,{message:"Tenant ID not found in request"});if(!z(t,n,a.accessControl.mainTenantId))throw new p(403,{message:`Access denied to tenant ${n}`});return s()}}function V(a){return async(e,s)=>{if(!a.subdomainRouting)return s();const{baseDomain:n,reservedSubdomains:t=[],resolveSubdomain:o}=a.subdomainRouting,d=e.req.header("host")||"";let c=null;if(d.endsWith(n)){const r=d.slice(0,-(n.length+1));r&&!r.includes(".")&&(c=r)}if(c&&t.includes(c)&&(c=null),!c)return a.accessControl&&e.set("tenant_id",a.accessControl.mainTenantId),s();let i=null;if(o)i=await o(c);else if(a.subdomainRouting.useOrganizations!==!1&&a.accessControl)try{const r=await e.env.data.organizations.get(a.accessControl.mainTenantId,c);r&&(i=r.id)}catch{}if(!i)throw new p(404,{message:`Tenant not found for subdomain: ${c}`});return e.set("tenant_id",i),s()}}function H(a){return async(e,s)=>{if(!a.databaseIsolation)return s();const n=e.var.tenant_id;if(!n)throw new p(400,{message:"Tenant ID not found in request"});try{const t=await a.databaseIsolation.getAdapters(n);e.env.data=t}catch(t){throw console.error(`Failed to resolve database for tenant ${n}:`,t),new p(500,{message:"Failed to resolve tenant database"})}return s()}}function R(a){const e=V(a),s=O(a),n=H(a);return async(t,o)=>(await e(t,async()=>{}),await s(t,async()=>{}),await n(t,async()=>{}),o())}function X(a){const e=A(a);return{name:"multi-tenancy",middleware:R(a),hooks:e,routes:[{path:"/management",handler:C(a,e)}],onRegister:async()=>{console.log("Multi-tenancy plugin registered"),a.accessControl&&console.log(` - Access control enabled (main tenant: ${a.accessControl.mainTenantId})`),a.subdomainRouting&&console.log(` - Subdomain routing enabled (base domain: ${a.subdomainRouting.baseDomain})`),a.databaseIsolation&&console.log(" - Database isolation enabled")}}}function A(a){const e=a.accessControl?I(a.accessControl):{},s=a.databaseIsolation?F(a.databaseIsolation):{},n=P(a);return{...e,...s,tenants:n}}function N(a){const e=new $.Hono,s=A(a);return e.route("/tenants",C(a,s)),e}function k(a){return{hooks:A(a),middleware:R(a),app:N(a),config:a}}function x(a){const{mainTenantId:e="main",syncResourceServers:s=!0,multiTenancy:n,entityHooks:t,...o}=a,d={...n,accessControl:{mainTenantId:e,requireOrganizationMatch:!1,defaultPermissions:["tenant:admin"],...n==null?void 0:n.accessControl}},c=A(d);let i,r;s&&(i=j({mainTenantId:e,getChildTenantIds:async()=>(await q(y=>a.dataAdapter.tenants.list(y),"tenants",{cursorField:"id",pageSize:100})).filter(y=>y.id!==e).map(y=>y.id),getAdapters:async g=>a.dataAdapter}),r=U({mainTenantId:e,getMainTenantAdapters:async()=>a.dataAdapter,getAdapters:async g=>a.dataAdapter}));const u=async(g,y,...T)=>{const v=[];if(g)try{await g(...T)}catch(b){v.push(b instanceof Error?b:new Error(String(b)))}if(y)try{await y(...T)}catch(b){v.push(b instanceof Error?b:new Error(String(b)))}if(v.length===1)throw v[0];if(v.length>1)throw new AggregateError(v,`Multiple hook errors: ${v.map(b=>b.message).join("; ")}`)},f={...t,resourceServers:i?{...t==null?void 0:t.resourceServers,afterCreate:async(g,y)=>{var T;await u((T=t==null?void 0:t.resourceServers)==null?void 0:T.afterCreate,i==null?void 0:i.afterCreate,g,y)},afterUpdate:async(g,y,T)=>{var v;await u((v=t==null?void 0:t.resourceServers)==null?void 0:v.afterUpdate,i==null?void 0:i.afterUpdate,g,y,T)},afterDelete:async(g,y)=>{var T;await u((T=t==null?void 0:t.resourceServers)==null?void 0:T.afterDelete,i==null?void 0:i.afterDelete,g,y)}}:t==null?void 0:t.resourceServers,tenants:r?{...t==null?void 0:t.tenants,afterCreate:async(g,y)=>{var T;await u((T=t==null?void 0:t.tenants)==null?void 0:T.afterCreate,r==null?void 0:r.afterCreate,g,y)}}:t==null?void 0:t.tenants},l=D.init({...o,entityHooks:f}),{app:m,managementApp:h,..._}=l,w=new $.Hono;w.use("/api/v2/*",K()),w.route("/",m);const E=C(d,c);return w.route("/api/v2/tenants",E),{app:w,managementApp:h,..._,multiTenancyConfig:d,multiTenancyHooks:c}}Object.defineProperty(exports,"seed",{enumerable:!0,get:()=>D.seed});exports.createAccessControlHooks=I;exports.createAccessControlMiddleware=O;exports.createDatabaseHooks=F;exports.createDatabaseMiddleware=H;exports.createMultiTenancy=N;exports.createMultiTenancyHooks=A;exports.createMultiTenancyMiddleware=R;exports.createMultiTenancyPlugin=X;exports.createProtectSyncedMiddleware=K;exports.createProvisioningHooks=P;exports.createResourceServerSyncHooks=j;exports.createSubdomainMiddleware=V;exports.createTenantResourceServerSyncHooks=U;exports.createTenantsRouter=C;exports.fetchAll=q;exports.init=x;exports.setupMultiTenancy=k;exports.validateTenantAccess=z;
|
|
1
|
+
"use strict";var W=Object.defineProperty;var G=(t,e,s)=>e in t?W(t,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[e]=s;var M=(t,e,s)=>G(t,typeof e!="symbol"?e+"":e,s);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const R=require("hono"),C=require("authhero"),Q=require("zod"),$=require("@authhero/adapter-interfaces");function q(t){const{mainTenantId:e,requireOrganizationMatch:s=!0}=t;return{async onTenantAccessValidation(n,a){if(a===e)return!0;if(s){const c=n.var.organization_id;return c?c===a:!1}return!0}}}function D(t,e,s){return e===s?!0:t?t===e:!1}function z(t){return{async resolveDataAdapters(e){try{return await t.getAdapters(e)}catch(s){console.error(`Failed to resolve data adapters for tenant ${e}:`,s);return}}}}function F(t){return{async afterCreate(e,s){const{accessControl:n,databaseIsolation:a,settingsInheritance:c}=t;n&&e.ctx&&await L(e,s,n),a!=null&&a.onProvision&&await a.onProvision(s.id),(c==null?void 0:c.inheritFromMain)!==!1&&e.ctx&&await B(e,s,t)},async beforeDelete(e,s){const{accessControl:n,databaseIsolation:a}=t;if(n)try{const d=(await e.adapters.organizations.list(n.mainTenantId)).organizations.find(o=>o.name===s);d&&await e.adapters.organizations.remove(n.mainTenantId,d.id)}catch(c){console.warn(`Failed to remove organization for tenant ${s}:`,c)}if(a!=null&&a.onDeprovision)try{await a.onDeprovision(s)}catch(c){console.warn(`Failed to deprovision database for tenant ${s}:`,c)}}}}async function L(t,e,s){const{mainTenantId:n,defaultPermissions:a,defaultRoles:c,issuer:d,adminRoleName:o="Tenant Admin",adminRoleDescription:i="Full access to all tenant management operations",addCreatorToOrganization:r=!0}=s;await t.adapters.organizations.create(n,{id:e.id,name:e.id,display_name:e.friendly_name||e.id});let l=null;if(d&&(l=await Y(t,n,d,o,i)),r&&t.ctx){const f=t.ctx.var.user;if(f!=null&&f.sub)try{await t.adapters.userOrganizations.create(n,{user_id:f.sub,organization_id:e.id}),l&&await t.adapters.userRoles.create(n,f.sub,l,e.id)}catch(u){console.warn(`Failed to add creator ${f.sub} to organization ${e.id}:`,u)}}c&&c.length>0&&console.log(`Would assign roles ${c.join(", ")} to organization ${e.id}`),a&&a.length>0&&console.log(`Would grant permissions ${a.join(", ")} to organization ${e.id}`)}async function Y(t,e,s,n,a){const d=(await t.adapters.roles.list(e,{})).roles.find(l=>l.name===n);if(d)return d.id;const o=await t.adapters.roles.create(e,{name:n,description:a}),i=`${s}api/v2/`,r=C.MANAGEMENT_API_SCOPES.map(l=>({role_id:o.id,resource_server_identifier:i,permission_name:l.value}));return await t.adapters.rolePermissions.assign(e,o.id,r),o.id}async function B(t,e,s){const{accessControl:n,settingsInheritance:a}=s;if(!n)return;const c=await t.adapters.tenants.get(n.mainTenantId);if(!c)return;let d={...c};const o=["id","created_at","updated_at","friendly_name","audience","sender_email","sender_name"];for(const i of o)delete d[i];if(a!=null&&a.inheritedKeys){const i={};for(const r of a.inheritedKeys)r in c&&!o.includes(r)&&(i[r]=c[r]);d=i}if(a!=null&&a.excludedKeys)for(const i of a.excludedKeys)delete d[i];a!=null&&a.transformSettings&&(d=a.transformSettings(d,e.id)),Object.keys(d).length>0&&await t.adapters.tenants.update(e.id,d)}async function P(t,e,s={}){const{cursorField:n="id",sortOrder:a="asc",pageSize:c=100,maxItems:d=1e4,q:o}=s,i=[];let r,l=!0;for(;l;){let f=o||"";if(r){const w=`${n}:${a==="asc"?">":"<"}${r}`;f=f?`(${f}) AND ${w}`:w}const u={per_page:c,page:0,sort:{sort_by:n,sort_order:a},...f&&{q:f}},h=(await t(u))[e]||[];if(h.length===0)l=!1;else{i.push(...h);const _=h[h.length-1];if(_&&typeof _=="object"){const w=_[n];w!=null&&(r=String(w))}h.length<c&&(l=!1),d!==-1&&i.length>=d&&(console.warn(`fetchAll: Reached maxItems limit (${d}). There may be more items.`),l=!1)}}return i}function j(t){const{mainTenantId:e,getChildTenantIds:s,getAdapters:n,shouldSync:a=()=>!0,transformForSync:c}=t;async function d(r,l,f){return(await r.resourceServers.list(l,{q:`identifier:${f}`,per_page:1})).resource_servers[0]??null}async function o(r,l){const f=await s();await Promise.all(f.map(async u=>{try{const m=await n(u),_={...c?c(r,u):{name:r.name,identifier:r.identifier,scopes:r.scopes,signing_alg:r.signing_alg,signing_secret:r.signing_secret,token_lifetime:r.token_lifetime,token_lifetime_for_web:r.token_lifetime_for_web,skip_consent_for_verifiable_first_party_clients:r.skip_consent_for_verifiable_first_party_clients,allow_offline_access:r.allow_offline_access,verificationKey:r.verificationKey,options:r.options},is_system:!0};if(l==="create"){const w=await d(m,u,r.identifier);w&&w.id?await m.resourceServers.update(u,w.id,_):await m.resourceServers.create(u,_)}else{const w=await d(m,u,r.identifier);w&&w.id&&await m.resourceServers.update(u,w.id,_)}}catch(m){console.error(`Failed to sync resource server "${r.identifier}" to tenant "${u}":`,m)}}))}async function i(r){const l=await s();await Promise.all(l.map(async f=>{try{const u=await n(f),m=await d(u,f,r);m&&m.id&&await u.resourceServers.remove(f,m.id)}catch(u){console.error(`Failed to delete resource server "${r}" from tenant "${f}":`,u)}}))}return{afterCreate:async(r,l)=>{r.tenantId===e&&a(l)&&await o(l,"create")},afterUpdate:async(r,l,f)=>{r.tenantId===e&&a(f)&&await o(f,"update")},afterDelete:async(r,l)=>{r.tenantId===e&&await i(l)}}}function O(t){const{mainTenantId:e,getMainTenantAdapters:s,getAdapters:n,shouldSync:a=()=>!0,transformForSync:c}=t;return{async afterCreate(d,o){if(o.id!==e)try{const i=await s(),r=await n(o.id),l=await P(f=>i.resourceServers.list(e,f),"resource_servers",{cursorField:"id",pageSize:100});await Promise.all(l.filter(f=>a(f)).map(async f=>{const u=f;try{const m=c?c(u,o.id):{name:u.name,identifier:u.identifier,scopes:u.scopes,signing_alg:u.signing_alg,signing_secret:u.signing_secret,token_lifetime:u.token_lifetime,token_lifetime_for_web:u.token_lifetime_for_web,skip_consent_for_verifiable_first_party_clients:u.skip_consent_for_verifiable_first_party_clients,allow_offline_access:u.allow_offline_access,verificationKey:u.verificationKey,options:u.options};await r.resourceServers.create(o.id,{...m,is_system:!0})}catch(m){console.error(`Failed to sync resource server "${u.identifier}" to new tenant "${o.id}":`,m)}}))}catch(i){console.error(`Failed to sync resource servers to new tenant "${o.id}":`,i)}}}}var p=class extends Error{constructor(e=500,s){super(s==null?void 0:s.message,{cause:s==null?void 0:s.cause});M(this,"res");M(this,"status");this.res=s==null?void 0:s.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 S(t,e){const s=new R.Hono;return s.get("/",async n=>{var l;if(t.accessControl&&await((l=e.onTenantAccessValidation)==null?void 0:l.call(e,n,t.accessControl.mainTenantId))===!1)throw new p(403,{message:"Access denied to tenant management"});const a=$.auth0QuerySchema.parse(n.req.query()),{page:c,per_page:d,include_totals:o,q:i}=a,r=await n.env.data.tenants.list({page:c,per_page:d,include_totals:o,q:i});return o?n.json(r):n.json(r.tenants)}),s.get("/:id",async n=>{var o;const a=n.req.param("id");if(await((o=e.onTenantAccessValidation)==null?void 0:o.call(e,n,a))===!1)throw new p(403,{message:"Access denied to this tenant"});const d=await n.env.data.tenants.get(a);if(!d)throw new p(404,{message:"Tenant not found"});return n.json(d)}),s.post("/",async n=>{var a,c,d,o;try{if(t.accessControl&&await((a=e.onTenantAccessValidation)==null?void 0:a.call(e,n,t.accessControl.mainTenantId))===!1)throw new p(403,{message:"Access denied to create tenants"});let i=$.tenantInsertSchema.parse(await n.req.json());const r={adapters:n.env.data,ctx:n};(c=e.tenants)!=null&&c.beforeCreate&&(i=await e.tenants.beforeCreate(r,i));const l=await n.env.data.tenants.create(i);return(d=e.tenants)!=null&&d.afterCreate&&await e.tenants.afterCreate(r,l),n.json(l,201)}catch(i){throw i instanceof Q.z.ZodError?new p(400,{message:"Validation error",cause:i}):i instanceof Error&&("code"in i&&i.code==="SQLITE_CONSTRAINT_PRIMARYKEY"||(o=i.message)!=null&&o.includes("UNIQUE constraint failed"))?new p(409,{message:"Tenant with this ID already exists"}):i}}),s.patch("/:id",async n=>{var m,h,_;const a=n.req.param("id");if(await((m=e.onTenantAccessValidation)==null?void 0:m.call(e,n,a))===!1)throw new p(403,{message:"Access denied to update this tenant"});const d=$.tenantInsertSchema.partial().parse(await n.req.json()),{id:o,...i}=d;if(!await n.env.data.tenants.get(a))throw new p(404,{message:"Tenant not found"});const l={adapters:n.env.data,ctx:n};let f=i;(h=e.tenants)!=null&&h.beforeUpdate&&(f=await e.tenants.beforeUpdate(l,a,i)),await n.env.data.tenants.update(a,f);const u=await n.env.data.tenants.get(a);if(!u)throw new p(404,{message:"Tenant not found after update"});return(_=e.tenants)!=null&&_.afterUpdate&&await e.tenants.afterUpdate(l,u),n.json(u)}),s.delete("/:id",async n=>{var o,i,r;const a=n.req.param("id");if(t.accessControl&&a===t.accessControl.mainTenantId)throw new p(400,{message:"Cannot delete the main tenant"});if(t.accessControl&&await((o=e.onTenantAccessValidation)==null?void 0:o.call(e,n,t.accessControl.mainTenantId))===!1)throw new p(403,{message:"Access denied to delete tenants"});if(!await n.env.data.tenants.get(a))throw new p(404,{message:"Tenant not found"});const d={adapters:n.env.data,ctx:n};return(i=e.tenants)!=null&&i.beforeDelete&&await e.tenants.beforeDelete(d,a),await n.env.data.tenants.remove(a),(r=e.tenants)!=null&&r.afterDelete&&await e.tenants.afterDelete(d,a),n.body(null,204)}),s}function Z(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:s,type:n}of e){const a=t.match(s);if(a&&a[1])return{type:n,id:a[1]}}return null}async function J(t,e,s){try{switch(s.type){case"resource_server":{const n=await t.resourceServers.get(e,s.id);return(n==null?void 0:n.is_system)===!0}case"role":{const n=await t.roles.get(e,s.id);return(n==null?void 0:n.is_system)===!0}case"connection":{const n=await t.connections.get(e,s.id);return(n==null?void 0:n.is_system)===!0}default:return!1}}catch{return!1}}function X(t){return{resource_server:"resource server",role:"role",connection:"connection"}[t]}function E(){return async(t,e)=>{if(!["PATCH","PUT","DELETE"].includes(t.req.method))return e();const s=Z(t.req.path);if(!s)return e();const n=t.var.tenant_id||t.req.header("x-tenant-id")||t.req.header("tenant-id");if(!n)return e();if(await J(t.env.data,n,s))throw new p(403,{message:`This ${X(s.type)} is a system resource and cannot be modified. Make changes in the main tenant instead.`});return e()}}function U(t){return async(e,s)=>{if(!t.accessControl)return s();const n=e.var.tenant_id,a=e.var.organization_id;if(!n)throw new p(400,{message:"Tenant ID not found in request"});if(!D(a,n,t.accessControl.mainTenantId))throw new p(403,{message:`Access denied to tenant ${n}`});return s()}}function N(t){return async(e,s)=>{if(!t.subdomainRouting)return s();const{baseDomain:n,reservedSubdomains:a=[],resolveSubdomain:c}=t.subdomainRouting,d=e.req.header("host")||"";let o=null;if(d.endsWith(n)){const r=d.slice(0,-(n.length+1));r&&!r.includes(".")&&(o=r)}if(o&&a.includes(o)&&(o=null),!o)return t.accessControl&&e.set("tenant_id",t.accessControl.mainTenantId),s();let i=null;if(c)i=await c(o);else if(t.subdomainRouting.useOrganizations!==!1&&t.accessControl)try{const r=await e.env.data.organizations.get(t.accessControl.mainTenantId,o);r&&(i=r.id)}catch{}if(!i)throw new p(404,{message:`Tenant not found for subdomain: ${o}`});return e.set("tenant_id",i),s()}}function K(t){return async(e,s)=>{if(!t.databaseIsolation)return s();const n=e.var.tenant_id;if(!n)throw new p(400,{message:"Tenant ID not found in request"});try{const a=await t.databaseIsolation.getAdapters(n);e.env.data=a}catch(a){throw console.error(`Failed to resolve database for tenant ${n}:`,a),new p(500,{message:"Failed to resolve tenant database"})}return s()}}function I(t){const e=N(t),s=U(t),n=K(t);return async(a,c)=>(await e(a,async()=>{}),await s(a,async()=>{}),await n(a,async()=>{}),c())}function k(t){const e=b(t);return{name:"multi-tenancy",middleware:I(t),hooks:e,routes:[{path:"/management",handler:S(t,e)}],onRegister:async()=>{console.log("Multi-tenancy plugin registered"),t.accessControl&&console.log(` - Access control enabled (main tenant: ${t.accessControl.mainTenantId})`),t.subdomainRouting&&console.log(` - Subdomain routing enabled (base domain: ${t.subdomainRouting.baseDomain})`),t.databaseIsolation&&console.log(" - Database isolation enabled")}}}function b(t){const e=t.accessControl?q(t.accessControl):{},s=t.databaseIsolation?z(t.databaseIsolation):{},n=F(t);return{...e,...s,tenants:n}}function V(t){const e=new R.Hono,s=b(t);return e.route("/tenants",S(t,s)),e}function x(t){return{hooks:b(t),middleware:I(t),app:V(t),config:t}}function ee(t){const{mainTenantId:e="main",syncResourceServers:s=!0,multiTenancy:n,entityHooks:a,...c}=t,d={...n,accessControl:{mainTenantId:e,requireOrganizationMatch:!1,defaultPermissions:["tenant:admin"],...n==null?void 0:n.accessControl}},o=b(d);let i,r;s&&(i=j({mainTenantId:e,getChildTenantIds:async()=>(await P(y=>t.dataAdapter.tenants.list(y),"tenants",{cursorField:"id",pageSize:100})).filter(y=>y.id!==e).map(y=>y.id),getAdapters:async g=>t.dataAdapter}),r=O({mainTenantId:e,getMainTenantAdapters:async()=>t.dataAdapter,getAdapters:async g=>t.dataAdapter}));const l=async(g,y,...T)=>{const v=[];if(g)try{await g(...T)}catch(A){v.push(A instanceof Error?A:new Error(String(A)))}if(y)try{await y(...T)}catch(A){v.push(A instanceof Error?A:new Error(String(A)))}if(v.length===1)throw v[0];if(v.length>1)throw new AggregateError(v,`Multiple hook errors: ${v.map(A=>A.message).join("; ")}`)},f={...a,resourceServers:i?{...a==null?void 0:a.resourceServers,afterCreate:async(g,y)=>{var T;await l((T=a==null?void 0:a.resourceServers)==null?void 0:T.afterCreate,i==null?void 0:i.afterCreate,g,y)},afterUpdate:async(g,y,T)=>{var v;await l((v=a==null?void 0:a.resourceServers)==null?void 0:v.afterUpdate,i==null?void 0:i.afterUpdate,g,y,T)},afterDelete:async(g,y)=>{var T;await l((T=a==null?void 0:a.resourceServers)==null?void 0:T.afterDelete,i==null?void 0:i.afterDelete,g,y)}}:a==null?void 0:a.resourceServers,tenants:r?{...a==null?void 0:a.tenants,afterCreate:async(g,y)=>{var T;await l((T=a==null?void 0:a.tenants)==null?void 0:T.afterCreate,r==null?void 0:r.afterCreate,g,y)}}:a==null?void 0:a.tenants},u=C.init({...c,entityHooks:f}),{app:m,managementApp:h,..._}=u,w=new R.Hono;w.use("/api/v2/*",E()),w.route("/",m);const H=S(d,o);return w.route("/api/v2/tenants",H),{app:w,managementApp:h,..._,multiTenancyConfig:d,multiTenancyHooks:o}}Object.defineProperty(exports,"MANAGEMENT_API_SCOPES",{enumerable:!0,get:()=>C.MANAGEMENT_API_SCOPES});Object.defineProperty(exports,"seed",{enumerable:!0,get:()=>C.seed});exports.createAccessControlHooks=q;exports.createAccessControlMiddleware=U;exports.createDatabaseHooks=z;exports.createDatabaseMiddleware=K;exports.createMultiTenancy=V;exports.createMultiTenancyHooks=b;exports.createMultiTenancyMiddleware=I;exports.createMultiTenancyPlugin=k;exports.createProtectSyncedMiddleware=E;exports.createProvisioningHooks=F;exports.createResourceServerSyncHooks=j;exports.createSubdomainMiddleware=N;exports.createTenantResourceServerSyncHooks=O;exports.createTenantsRouter=S;exports.fetchAll=P;exports.init=ee;exports.setupMultiTenancy=x;exports.validateTenantAccess=D;
|
package/dist/multi-tenancy.d.ts
CHANGED
|
@@ -9659,6 +9659,13 @@ export type Bindings = {
|
|
|
9659
9659
|
export interface CreateX509CertificateParams {
|
|
9660
9660
|
name: string;
|
|
9661
9661
|
}
|
|
9662
|
+
/**
|
|
9663
|
+
* Management API scopes for the AuthHero Management API
|
|
9664
|
+
*/
|
|
9665
|
+
export declare const MANAGEMENT_API_SCOPES: {
|
|
9666
|
+
description: string;
|
|
9667
|
+
value: string;
|
|
9668
|
+
}[];
|
|
9662
9669
|
export interface SeedOptions {
|
|
9663
9670
|
/**
|
|
9664
9671
|
* The admin user's email address
|
|
@@ -9696,6 +9703,10 @@ export interface SeedOptions {
|
|
|
9696
9703
|
* Whether to log progress (defaults to true)
|
|
9697
9704
|
*/
|
|
9698
9705
|
debug?: boolean;
|
|
9706
|
+
/**
|
|
9707
|
+
* The issuer URL (used to construct the Management API identifier)
|
|
9708
|
+
*/
|
|
9709
|
+
issuer?: string;
|
|
9699
9710
|
}
|
|
9700
9711
|
export interface SeedResult {
|
|
9701
9712
|
tenantId: string;
|
|
@@ -18998,6 +19009,30 @@ export interface AccessControlConfig {
|
|
|
18998
19009
|
* These roles should exist on the main tenant.
|
|
18999
19010
|
*/
|
|
19000
19011
|
defaultRoles?: string[];
|
|
19012
|
+
/**
|
|
19013
|
+
* The issuer URL used to construct the Management API identifier.
|
|
19014
|
+
* If provided, a "Tenant Admin" role will be created with all Management API
|
|
19015
|
+
* permissions, and the user creating a new tenant will be assigned this role.
|
|
19016
|
+
* @example "https://auth.example.com/"
|
|
19017
|
+
*/
|
|
19018
|
+
issuer?: string;
|
|
19019
|
+
/**
|
|
19020
|
+
* The name of the admin role to create/use for tenant administrators.
|
|
19021
|
+
* This role will have full Management API permissions.
|
|
19022
|
+
* @default "Tenant Admin"
|
|
19023
|
+
*/
|
|
19024
|
+
adminRoleName?: string;
|
|
19025
|
+
/**
|
|
19026
|
+
* Description for the admin role.
|
|
19027
|
+
* @default "Full access to all tenant management operations"
|
|
19028
|
+
*/
|
|
19029
|
+
adminRoleDescription?: string;
|
|
19030
|
+
/**
|
|
19031
|
+
* If true, the user creating a tenant will be added to the organization
|
|
19032
|
+
* and assigned the admin role.
|
|
19033
|
+
* @default true
|
|
19034
|
+
*/
|
|
19035
|
+
addCreatorToOrganization?: boolean;
|
|
19001
19036
|
}
|
|
19002
19037
|
/**
|
|
19003
19038
|
* Configuration for per-tenant database isolation.
|
package/dist/multi-tenancy.mjs
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var C = (
|
|
1
|
+
var D = Object.defineProperty;
|
|
2
|
+
var q = (t, e, s) => e in t ? D(t, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : t[e] = s;
|
|
3
|
+
var C = (t, e, s) => q(t, typeof e != "symbol" ? e + "" : e, s);
|
|
4
4
|
import { Hono as $ } from "hono";
|
|
5
|
-
import { init as
|
|
6
|
-
import { seed as
|
|
7
|
-
import { z as
|
|
8
|
-
import { auth0QuerySchema as
|
|
9
|
-
function
|
|
10
|
-
const { mainTenantId: e, requireOrganizationMatch: s = !0 } =
|
|
5
|
+
import { MANAGEMENT_API_SCOPES as F, init as P } from "authhero";
|
|
6
|
+
import { MANAGEMENT_API_SCOPES as le, seed as ue } from "authhero";
|
|
7
|
+
import { z as j } from "zod";
|
|
8
|
+
import { auth0QuerySchema as U, tenantInsertSchema as R } from "@authhero/adapter-interfaces";
|
|
9
|
+
function O(t) {
|
|
10
|
+
const { mainTenantId: e, requireOrganizationMatch: s = !0 } = t;
|
|
11
11
|
return {
|
|
12
|
-
async onTenantAccessValidation(n,
|
|
13
|
-
if (
|
|
12
|
+
async onTenantAccessValidation(n, a) {
|
|
13
|
+
if (a === e)
|
|
14
14
|
return !0;
|
|
15
15
|
if (s) {
|
|
16
|
-
const
|
|
17
|
-
return
|
|
16
|
+
const c = n.var.organization_id;
|
|
17
|
+
return c ? c === a : !1;
|
|
18
18
|
}
|
|
19
19
|
return !0;
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
function
|
|
24
|
-
return e === s ? !0 :
|
|
23
|
+
function E(t, e, s) {
|
|
24
|
+
return e === s ? !0 : t ? t === e : !1;
|
|
25
25
|
}
|
|
26
|
-
function
|
|
26
|
+
function K(t) {
|
|
27
27
|
return {
|
|
28
28
|
async resolveDataAdapters(e) {
|
|
29
29
|
try {
|
|
30
|
-
return await
|
|
30
|
+
return await t.getAdapters(e);
|
|
31
31
|
} catch (s) {
|
|
32
32
|
console.error(
|
|
33
33
|
`Failed to resolve data adapters for tenant ${e}:`,
|
|
@@ -38,62 +38,113 @@ function V(a) {
|
|
|
38
38
|
}
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
-
function
|
|
41
|
+
function N(t) {
|
|
42
42
|
return {
|
|
43
43
|
async afterCreate(e, s) {
|
|
44
|
-
const { accessControl: n, databaseIsolation:
|
|
45
|
-
n && e.ctx && await
|
|
44
|
+
const { accessControl: n, databaseIsolation: a, settingsInheritance: c } = t;
|
|
45
|
+
n && e.ctx && await V(e, s, n), a != null && a.onProvision && await a.onProvision(s.id), (c == null ? void 0 : c.inheritFromMain) !== !1 && e.ctx && await Q(e, s, t);
|
|
46
46
|
},
|
|
47
47
|
async beforeDelete(e, s) {
|
|
48
|
-
const { accessControl: n, databaseIsolation:
|
|
48
|
+
const { accessControl: n, databaseIsolation: a } = t;
|
|
49
49
|
if (n)
|
|
50
50
|
try {
|
|
51
51
|
const d = (await e.adapters.organizations.list(
|
|
52
52
|
n.mainTenantId
|
|
53
|
-
)).organizations.find((
|
|
53
|
+
)).organizations.find((o) => o.name === s);
|
|
54
54
|
d && await e.adapters.organizations.remove(
|
|
55
55
|
n.mainTenantId,
|
|
56
56
|
d.id
|
|
57
57
|
);
|
|
58
|
-
} catch (
|
|
58
|
+
} catch (c) {
|
|
59
59
|
console.warn(
|
|
60
60
|
`Failed to remove organization for tenant ${s}:`,
|
|
61
|
-
|
|
61
|
+
c
|
|
62
62
|
);
|
|
63
63
|
}
|
|
64
|
-
if (
|
|
64
|
+
if (a != null && a.onDeprovision)
|
|
65
65
|
try {
|
|
66
|
-
await
|
|
67
|
-
} catch (
|
|
66
|
+
await a.onDeprovision(s);
|
|
67
|
+
} catch (c) {
|
|
68
68
|
console.warn(
|
|
69
69
|
`Failed to deprovision database for tenant ${s}:`,
|
|
70
|
-
|
|
70
|
+
c
|
|
71
71
|
);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
|
-
async function
|
|
77
|
-
const {
|
|
78
|
-
|
|
76
|
+
async function V(t, e, s) {
|
|
77
|
+
const {
|
|
78
|
+
mainTenantId: n,
|
|
79
|
+
defaultPermissions: a,
|
|
80
|
+
defaultRoles: c,
|
|
81
|
+
issuer: d,
|
|
82
|
+
adminRoleName: o = "Tenant Admin",
|
|
83
|
+
adminRoleDescription: i = "Full access to all tenant management operations",
|
|
84
|
+
addCreatorToOrganization: r = !0
|
|
85
|
+
} = s;
|
|
86
|
+
await t.adapters.organizations.create(n, {
|
|
79
87
|
id: e.id,
|
|
80
88
|
name: e.id,
|
|
81
89
|
display_name: e.friendly_name || e.id
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
});
|
|
91
|
+
let l = null;
|
|
92
|
+
if (d && (l = await W(
|
|
93
|
+
t,
|
|
94
|
+
n,
|
|
95
|
+
d,
|
|
96
|
+
o,
|
|
97
|
+
i
|
|
98
|
+
)), r && t.ctx) {
|
|
99
|
+
const f = t.ctx.var.user;
|
|
100
|
+
if (f != null && f.sub)
|
|
101
|
+
try {
|
|
102
|
+
await t.adapters.userOrganizations.create(n, {
|
|
103
|
+
user_id: f.sub,
|
|
104
|
+
organization_id: e.id
|
|
105
|
+
}), l && await t.adapters.userRoles.create(
|
|
106
|
+
n,
|
|
107
|
+
f.sub,
|
|
108
|
+
l,
|
|
109
|
+
e.id
|
|
110
|
+
// organizationId
|
|
111
|
+
);
|
|
112
|
+
} catch (u) {
|
|
113
|
+
console.warn(
|
|
114
|
+
`Failed to add creator ${f.sub} to organization ${e.id}:`,
|
|
115
|
+
u
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
c && c.length > 0 && console.log(
|
|
120
|
+
`Would assign roles ${c.join(", ")} to organization ${e.id}`
|
|
121
|
+
), a && a.length > 0 && console.log(
|
|
122
|
+
`Would grant permissions ${a.join(", ")} to organization ${e.id}`
|
|
86
123
|
);
|
|
87
124
|
}
|
|
88
|
-
async function
|
|
89
|
-
const
|
|
125
|
+
async function W(t, e, s, n, a) {
|
|
126
|
+
const d = (await t.adapters.roles.list(e, {})).roles.find((l) => l.name === n);
|
|
127
|
+
if (d)
|
|
128
|
+
return d.id;
|
|
129
|
+
const o = await t.adapters.roles.create(e, {
|
|
130
|
+
name: n,
|
|
131
|
+
description: a
|
|
132
|
+
}), i = `${s}api/v2/`, r = F.map((l) => ({
|
|
133
|
+
role_id: o.id,
|
|
134
|
+
resource_server_identifier: i,
|
|
135
|
+
permission_name: l.value
|
|
136
|
+
}));
|
|
137
|
+
return await t.adapters.rolePermissions.assign(e, o.id, r), o.id;
|
|
138
|
+
}
|
|
139
|
+
async function Q(t, e, s) {
|
|
140
|
+
const { accessControl: n, settingsInheritance: a } = s;
|
|
90
141
|
if (!n)
|
|
91
142
|
return;
|
|
92
|
-
const
|
|
93
|
-
if (!
|
|
143
|
+
const c = await t.adapters.tenants.get(n.mainTenantId);
|
|
144
|
+
if (!c)
|
|
94
145
|
return;
|
|
95
|
-
let d = { ...
|
|
96
|
-
const
|
|
146
|
+
let d = { ...c };
|
|
147
|
+
const o = [
|
|
97
148
|
"id",
|
|
98
149
|
"created_at",
|
|
99
150
|
"updated_at",
|
|
@@ -103,49 +154,49 @@ async function E(a, e, s) {
|
|
|
103
154
|
"sender_email",
|
|
104
155
|
"sender_name"
|
|
105
156
|
];
|
|
106
|
-
for (const i of
|
|
157
|
+
for (const i of o)
|
|
107
158
|
delete d[i];
|
|
108
|
-
if (
|
|
159
|
+
if (a != null && a.inheritedKeys) {
|
|
109
160
|
const i = {};
|
|
110
|
-
for (const r of
|
|
111
|
-
r in
|
|
161
|
+
for (const r of a.inheritedKeys)
|
|
162
|
+
r in c && !o.includes(r) && (i[r] = c[r]);
|
|
112
163
|
d = i;
|
|
113
164
|
}
|
|
114
|
-
if (
|
|
115
|
-
for (const i of
|
|
165
|
+
if (a != null && a.excludedKeys)
|
|
166
|
+
for (const i of a.excludedKeys)
|
|
116
167
|
delete d[i];
|
|
117
|
-
|
|
168
|
+
a != null && a.transformSettings && (d = a.transformSettings(
|
|
118
169
|
d,
|
|
119
170
|
e.id
|
|
120
|
-
)), Object.keys(d).length > 0 && await
|
|
171
|
+
)), Object.keys(d).length > 0 && await t.adapters.tenants.update(e.id, d);
|
|
121
172
|
}
|
|
122
|
-
async function
|
|
173
|
+
async function M(t, e, s = {}) {
|
|
123
174
|
const {
|
|
124
175
|
cursorField: n = "id",
|
|
125
|
-
sortOrder:
|
|
126
|
-
pageSize:
|
|
176
|
+
sortOrder: a = "asc",
|
|
177
|
+
pageSize: c = 100,
|
|
127
178
|
maxItems: d = 1e4,
|
|
128
|
-
q:
|
|
179
|
+
q: o
|
|
129
180
|
} = s, i = [];
|
|
130
|
-
let r,
|
|
131
|
-
for (;
|
|
132
|
-
let f =
|
|
181
|
+
let r, l = !0;
|
|
182
|
+
for (; l; ) {
|
|
183
|
+
let f = o || "";
|
|
133
184
|
if (r) {
|
|
134
|
-
const w = `${n}:${
|
|
185
|
+
const w = `${n}:${a === "asc" ? ">" : "<"}${r}`;
|
|
135
186
|
f = f ? `(${f}) AND ${w}` : w;
|
|
136
187
|
}
|
|
137
|
-
const
|
|
138
|
-
per_page:
|
|
188
|
+
const u = {
|
|
189
|
+
per_page: c,
|
|
139
190
|
page: 0,
|
|
140
191
|
// Always use page 0 since we're doing cursor-based pagination
|
|
141
192
|
sort: {
|
|
142
193
|
sort_by: n,
|
|
143
|
-
sort_order:
|
|
194
|
+
sort_order: a
|
|
144
195
|
},
|
|
145
196
|
...f && { q: f }
|
|
146
|
-
}, T = (await
|
|
197
|
+
}, T = (await t(u))[e] || [];
|
|
147
198
|
if (T.length === 0)
|
|
148
|
-
|
|
199
|
+
l = !1;
|
|
149
200
|
else {
|
|
150
201
|
i.push(...T);
|
|
151
202
|
const _ = T[T.length - 1];
|
|
@@ -153,33 +204,33 @@ async function q(a, e, s = {}) {
|
|
|
153
204
|
const w = _[n];
|
|
154
205
|
w != null && (r = String(w));
|
|
155
206
|
}
|
|
156
|
-
T.length <
|
|
207
|
+
T.length < c && (l = !1), d !== -1 && i.length >= d && (console.warn(
|
|
157
208
|
`fetchAll: Reached maxItems limit (${d}). There may be more items.`
|
|
158
|
-
),
|
|
209
|
+
), l = !1);
|
|
159
210
|
}
|
|
160
211
|
}
|
|
161
212
|
return i;
|
|
162
213
|
}
|
|
163
|
-
function
|
|
214
|
+
function G(t) {
|
|
164
215
|
const {
|
|
165
216
|
mainTenantId: e,
|
|
166
217
|
getChildTenantIds: s,
|
|
167
218
|
getAdapters: n,
|
|
168
|
-
shouldSync:
|
|
169
|
-
transformForSync:
|
|
170
|
-
} =
|
|
171
|
-
async function d(r,
|
|
172
|
-
return (await r.resourceServers.list(
|
|
219
|
+
shouldSync: a = () => !0,
|
|
220
|
+
transformForSync: c
|
|
221
|
+
} = t;
|
|
222
|
+
async function d(r, l, f) {
|
|
223
|
+
return (await r.resourceServers.list(l, {
|
|
173
224
|
q: `identifier:${f}`,
|
|
174
225
|
per_page: 1
|
|
175
226
|
})).resource_servers[0] ?? null;
|
|
176
227
|
}
|
|
177
|
-
async function
|
|
228
|
+
async function o(r, l) {
|
|
178
229
|
const f = await s();
|
|
179
230
|
await Promise.all(
|
|
180
|
-
f.map(async (
|
|
231
|
+
f.map(async (u) => {
|
|
181
232
|
try {
|
|
182
|
-
const m = await n(
|
|
233
|
+
const m = await n(u), _ = { ...c ? c(r, u) : {
|
|
183
234
|
name: r.name,
|
|
184
235
|
identifier: r.identifier,
|
|
185
236
|
scopes: r.scopes,
|
|
@@ -192,32 +243,32 @@ function W(a) {
|
|
|
192
243
|
verificationKey: r.verificationKey,
|
|
193
244
|
options: r.options
|
|
194
245
|
}, is_system: !0 };
|
|
195
|
-
if (
|
|
246
|
+
if (l === "create") {
|
|
196
247
|
const w = await d(
|
|
197
248
|
m,
|
|
198
|
-
|
|
249
|
+
u,
|
|
199
250
|
r.identifier
|
|
200
251
|
);
|
|
201
252
|
w && w.id ? await m.resourceServers.update(
|
|
202
|
-
|
|
253
|
+
u,
|
|
203
254
|
w.id,
|
|
204
255
|
_
|
|
205
|
-
) : await m.resourceServers.create(
|
|
256
|
+
) : await m.resourceServers.create(u, _);
|
|
206
257
|
} else {
|
|
207
258
|
const w = await d(
|
|
208
259
|
m,
|
|
209
|
-
|
|
260
|
+
u,
|
|
210
261
|
r.identifier
|
|
211
262
|
);
|
|
212
263
|
w && w.id && await m.resourceServers.update(
|
|
213
|
-
|
|
264
|
+
u,
|
|
214
265
|
w.id,
|
|
215
266
|
_
|
|
216
267
|
);
|
|
217
268
|
}
|
|
218
269
|
} catch (m) {
|
|
219
270
|
console.error(
|
|
220
|
-
`Failed to sync resource server "${r.identifier}" to tenant "${
|
|
271
|
+
`Failed to sync resource server "${r.identifier}" to tenant "${u}":`,
|
|
221
272
|
m
|
|
222
273
|
);
|
|
223
274
|
}
|
|
@@ -225,78 +276,78 @@ function W(a) {
|
|
|
225
276
|
);
|
|
226
277
|
}
|
|
227
278
|
async function i(r) {
|
|
228
|
-
const
|
|
279
|
+
const l = await s();
|
|
229
280
|
await Promise.all(
|
|
230
|
-
|
|
281
|
+
l.map(async (f) => {
|
|
231
282
|
try {
|
|
232
|
-
const
|
|
233
|
-
|
|
283
|
+
const u = await n(f), m = await d(
|
|
284
|
+
u,
|
|
234
285
|
f,
|
|
235
286
|
r
|
|
236
287
|
);
|
|
237
|
-
m && m.id && await
|
|
238
|
-
} catch (
|
|
288
|
+
m && m.id && await u.resourceServers.remove(f, m.id);
|
|
289
|
+
} catch (u) {
|
|
239
290
|
console.error(
|
|
240
291
|
`Failed to delete resource server "${r}" from tenant "${f}":`,
|
|
241
|
-
|
|
292
|
+
u
|
|
242
293
|
);
|
|
243
294
|
}
|
|
244
295
|
})
|
|
245
296
|
);
|
|
246
297
|
}
|
|
247
298
|
return {
|
|
248
|
-
afterCreate: async (r,
|
|
249
|
-
r.tenantId === e &&
|
|
299
|
+
afterCreate: async (r, l) => {
|
|
300
|
+
r.tenantId === e && a(l) && await o(l, "create");
|
|
250
301
|
},
|
|
251
|
-
afterUpdate: async (r,
|
|
252
|
-
r.tenantId === e &&
|
|
302
|
+
afterUpdate: async (r, l, f) => {
|
|
303
|
+
r.tenantId === e && a(f) && await o(f, "update");
|
|
253
304
|
},
|
|
254
|
-
afterDelete: async (r,
|
|
255
|
-
r.tenantId === e && await i(
|
|
305
|
+
afterDelete: async (r, l) => {
|
|
306
|
+
r.tenantId === e && await i(l);
|
|
256
307
|
}
|
|
257
308
|
};
|
|
258
309
|
}
|
|
259
|
-
function
|
|
310
|
+
function L(t) {
|
|
260
311
|
const {
|
|
261
312
|
mainTenantId: e,
|
|
262
313
|
getMainTenantAdapters: s,
|
|
263
314
|
getAdapters: n,
|
|
264
|
-
shouldSync:
|
|
265
|
-
transformForSync:
|
|
266
|
-
} =
|
|
315
|
+
shouldSync: a = () => !0,
|
|
316
|
+
transformForSync: c
|
|
317
|
+
} = t;
|
|
267
318
|
return {
|
|
268
|
-
async afterCreate(d,
|
|
269
|
-
if (
|
|
319
|
+
async afterCreate(d, o) {
|
|
320
|
+
if (o.id !== e)
|
|
270
321
|
try {
|
|
271
|
-
const i = await s(), r = await n(
|
|
322
|
+
const i = await s(), r = await n(o.id), l = await M(
|
|
272
323
|
(f) => i.resourceServers.list(e, f),
|
|
273
324
|
"resource_servers",
|
|
274
325
|
{ cursorField: "id", pageSize: 100 }
|
|
275
326
|
);
|
|
276
327
|
await Promise.all(
|
|
277
|
-
|
|
278
|
-
const
|
|
328
|
+
l.filter((f) => a(f)).map(async (f) => {
|
|
329
|
+
const u = f;
|
|
279
330
|
try {
|
|
280
|
-
const m =
|
|
281
|
-
name:
|
|
282
|
-
identifier:
|
|
283
|
-
scopes:
|
|
284
|
-
signing_alg:
|
|
285
|
-
signing_secret:
|
|
286
|
-
token_lifetime:
|
|
287
|
-
token_lifetime_for_web:
|
|
288
|
-
skip_consent_for_verifiable_first_party_clients:
|
|
289
|
-
allow_offline_access:
|
|
290
|
-
verificationKey:
|
|
291
|
-
options:
|
|
331
|
+
const m = c ? c(u, o.id) : {
|
|
332
|
+
name: u.name,
|
|
333
|
+
identifier: u.identifier,
|
|
334
|
+
scopes: u.scopes,
|
|
335
|
+
signing_alg: u.signing_alg,
|
|
336
|
+
signing_secret: u.signing_secret,
|
|
337
|
+
token_lifetime: u.token_lifetime,
|
|
338
|
+
token_lifetime_for_web: u.token_lifetime_for_web,
|
|
339
|
+
skip_consent_for_verifiable_first_party_clients: u.skip_consent_for_verifiable_first_party_clients,
|
|
340
|
+
allow_offline_access: u.allow_offline_access,
|
|
341
|
+
verificationKey: u.verificationKey,
|
|
342
|
+
options: u.options
|
|
292
343
|
};
|
|
293
|
-
await r.resourceServers.create(
|
|
344
|
+
await r.resourceServers.create(o.id, {
|
|
294
345
|
...m,
|
|
295
346
|
is_system: !0
|
|
296
347
|
});
|
|
297
348
|
} catch (m) {
|
|
298
349
|
console.error(
|
|
299
|
-
`Failed to sync resource server "${
|
|
350
|
+
`Failed to sync resource server "${u.identifier}" to new tenant "${o.id}":`,
|
|
300
351
|
m
|
|
301
352
|
);
|
|
302
353
|
}
|
|
@@ -304,7 +355,7 @@ function Q(a) {
|
|
|
304
355
|
);
|
|
305
356
|
} catch (i) {
|
|
306
357
|
console.error(
|
|
307
|
-
`Failed to sync resource servers to new tenant "${
|
|
358
|
+
`Failed to sync resource servers to new tenant "${o.id}":`,
|
|
308
359
|
i
|
|
309
360
|
);
|
|
310
361
|
}
|
|
@@ -337,107 +388,107 @@ var p = class extends Error {
|
|
|
337
388
|
});
|
|
338
389
|
}
|
|
339
390
|
};
|
|
340
|
-
function S(
|
|
391
|
+
function S(t, e) {
|
|
341
392
|
const s = new $();
|
|
342
393
|
return s.get("/", async (n) => {
|
|
343
|
-
var
|
|
344
|
-
if (
|
|
394
|
+
var l;
|
|
395
|
+
if (t.accessControl && await ((l = e.onTenantAccessValidation) == null ? void 0 : l.call(
|
|
345
396
|
e,
|
|
346
397
|
n,
|
|
347
|
-
|
|
398
|
+
t.accessControl.mainTenantId
|
|
348
399
|
)) === !1)
|
|
349
400
|
throw new p(403, {
|
|
350
401
|
message: "Access denied to tenant management"
|
|
351
402
|
});
|
|
352
|
-
const
|
|
353
|
-
page:
|
|
403
|
+
const a = U.parse(n.req.query()), { page: c, per_page: d, include_totals: o, q: i } = a, r = await n.env.data.tenants.list({
|
|
404
|
+
page: c,
|
|
354
405
|
per_page: d,
|
|
355
|
-
include_totals:
|
|
406
|
+
include_totals: o,
|
|
356
407
|
q: i
|
|
357
408
|
});
|
|
358
|
-
return
|
|
409
|
+
return o ? n.json(r) : n.json(r.tenants);
|
|
359
410
|
}), s.get("/:id", async (n) => {
|
|
360
|
-
var
|
|
361
|
-
const
|
|
362
|
-
if (await ((
|
|
411
|
+
var o;
|
|
412
|
+
const a = n.req.param("id");
|
|
413
|
+
if (await ((o = e.onTenantAccessValidation) == null ? void 0 : o.call(e, n, a)) === !1)
|
|
363
414
|
throw new p(403, {
|
|
364
415
|
message: "Access denied to this tenant"
|
|
365
416
|
});
|
|
366
|
-
const d = await n.env.data.tenants.get(
|
|
417
|
+
const d = await n.env.data.tenants.get(a);
|
|
367
418
|
if (!d)
|
|
368
419
|
throw new p(404, {
|
|
369
420
|
message: "Tenant not found"
|
|
370
421
|
});
|
|
371
422
|
return n.json(d);
|
|
372
423
|
}), s.post("/", async (n) => {
|
|
373
|
-
var
|
|
424
|
+
var a, c, d, o;
|
|
374
425
|
try {
|
|
375
|
-
if (
|
|
426
|
+
if (t.accessControl && await ((a = e.onTenantAccessValidation) == null ? void 0 : a.call(
|
|
376
427
|
e,
|
|
377
428
|
n,
|
|
378
|
-
|
|
429
|
+
t.accessControl.mainTenantId
|
|
379
430
|
)) === !1)
|
|
380
431
|
throw new p(403, {
|
|
381
432
|
message: "Access denied to create tenants"
|
|
382
433
|
});
|
|
383
|
-
let i =
|
|
434
|
+
let i = R.parse(
|
|
384
435
|
await n.req.json()
|
|
385
436
|
);
|
|
386
437
|
const r = {
|
|
387
438
|
adapters: n.env.data,
|
|
388
439
|
ctx: n
|
|
389
440
|
};
|
|
390
|
-
(
|
|
391
|
-
const
|
|
392
|
-
return (d = e.tenants) != null && d.afterCreate && await e.tenants.afterCreate(r,
|
|
441
|
+
(c = e.tenants) != null && c.beforeCreate && (i = await e.tenants.beforeCreate(r, i));
|
|
442
|
+
const l = await n.env.data.tenants.create(i);
|
|
443
|
+
return (d = e.tenants) != null && d.afterCreate && await e.tenants.afterCreate(r, l), n.json(l, 201);
|
|
393
444
|
} catch (i) {
|
|
394
|
-
throw i instanceof
|
|
445
|
+
throw i instanceof j.ZodError ? new p(400, {
|
|
395
446
|
message: "Validation error",
|
|
396
447
|
cause: i
|
|
397
|
-
}) : i instanceof Error && ("code" in i && i.code === "SQLITE_CONSTRAINT_PRIMARYKEY" || (
|
|
448
|
+
}) : i instanceof Error && ("code" in i && i.code === "SQLITE_CONSTRAINT_PRIMARYKEY" || (o = i.message) != null && o.includes("UNIQUE constraint failed")) ? new p(409, {
|
|
398
449
|
message: "Tenant with this ID already exists"
|
|
399
450
|
}) : i;
|
|
400
451
|
}
|
|
401
452
|
}), s.patch("/:id", async (n) => {
|
|
402
453
|
var m, T, _;
|
|
403
|
-
const
|
|
404
|
-
if (await ((m = e.onTenantAccessValidation) == null ? void 0 : m.call(e, n,
|
|
454
|
+
const a = n.req.param("id");
|
|
455
|
+
if (await ((m = e.onTenantAccessValidation) == null ? void 0 : m.call(e, n, a)) === !1)
|
|
405
456
|
throw new p(403, {
|
|
406
457
|
message: "Access denied to update this tenant"
|
|
407
458
|
});
|
|
408
|
-
const d =
|
|
409
|
-
if (!await n.env.data.tenants.get(
|
|
459
|
+
const d = R.partial().parse(await n.req.json()), { id: o, ...i } = d;
|
|
460
|
+
if (!await n.env.data.tenants.get(a))
|
|
410
461
|
throw new p(404, {
|
|
411
462
|
message: "Tenant not found"
|
|
412
463
|
});
|
|
413
|
-
const
|
|
464
|
+
const l = {
|
|
414
465
|
adapters: n.env.data,
|
|
415
466
|
ctx: n
|
|
416
467
|
};
|
|
417
468
|
let f = i;
|
|
418
|
-
(T = e.tenants) != null && T.beforeUpdate && (f = await e.tenants.beforeUpdate(
|
|
419
|
-
const
|
|
420
|
-
if (!
|
|
469
|
+
(T = e.tenants) != null && T.beforeUpdate && (f = await e.tenants.beforeUpdate(l, a, i)), await n.env.data.tenants.update(a, f);
|
|
470
|
+
const u = await n.env.data.tenants.get(a);
|
|
471
|
+
if (!u)
|
|
421
472
|
throw new p(404, {
|
|
422
473
|
message: "Tenant not found after update"
|
|
423
474
|
});
|
|
424
|
-
return (_ = e.tenants) != null && _.afterUpdate && await e.tenants.afterUpdate(
|
|
475
|
+
return (_ = e.tenants) != null && _.afterUpdate && await e.tenants.afterUpdate(l, u), n.json(u);
|
|
425
476
|
}), s.delete("/:id", async (n) => {
|
|
426
|
-
var
|
|
427
|
-
const
|
|
428
|
-
if (
|
|
477
|
+
var o, i, r;
|
|
478
|
+
const a = n.req.param("id");
|
|
479
|
+
if (t.accessControl && a === t.accessControl.mainTenantId)
|
|
429
480
|
throw new p(400, {
|
|
430
481
|
message: "Cannot delete the main tenant"
|
|
431
482
|
});
|
|
432
|
-
if (
|
|
483
|
+
if (t.accessControl && await ((o = e.onTenantAccessValidation) == null ? void 0 : o.call(
|
|
433
484
|
e,
|
|
434
485
|
n,
|
|
435
|
-
|
|
486
|
+
t.accessControl.mainTenantId
|
|
436
487
|
)) === !1)
|
|
437
488
|
throw new p(403, {
|
|
438
489
|
message: "Access denied to delete tenants"
|
|
439
490
|
});
|
|
440
|
-
if (!await n.env.data.tenants.get(
|
|
491
|
+
if (!await n.env.data.tenants.get(a))
|
|
441
492
|
throw new p(404, {
|
|
442
493
|
message: "Tenant not found"
|
|
443
494
|
});
|
|
@@ -445,10 +496,10 @@ function S(a, e) {
|
|
|
445
496
|
adapters: n.env.data,
|
|
446
497
|
ctx: n
|
|
447
498
|
};
|
|
448
|
-
return (i = e.tenants) != null && i.beforeDelete && await e.tenants.beforeDelete(d,
|
|
499
|
+
return (i = e.tenants) != null && i.beforeDelete && await e.tenants.beforeDelete(d, a), await n.env.data.tenants.remove(a), (r = e.tenants) != null && r.afterDelete && await e.tenants.afterDelete(d, a), n.body(null, 204);
|
|
449
500
|
}), s;
|
|
450
501
|
}
|
|
451
|
-
function
|
|
502
|
+
function Y(t) {
|
|
452
503
|
const e = [
|
|
453
504
|
{
|
|
454
505
|
pattern: /\/api\/v2\/resource-servers\/([^/]+)$/,
|
|
@@ -458,25 +509,25 @@ function L(a) {
|
|
|
458
509
|
{ pattern: /\/api\/v2\/connections\/([^/]+)$/, type: "connection" }
|
|
459
510
|
];
|
|
460
511
|
for (const { pattern: s, type: n } of e) {
|
|
461
|
-
const
|
|
462
|
-
if (
|
|
463
|
-
return { type: n, id:
|
|
512
|
+
const a = t.match(s);
|
|
513
|
+
if (a && a[1])
|
|
514
|
+
return { type: n, id: a[1] };
|
|
464
515
|
}
|
|
465
516
|
return null;
|
|
466
517
|
}
|
|
467
|
-
async function
|
|
518
|
+
async function B(t, e, s) {
|
|
468
519
|
try {
|
|
469
520
|
switch (s.type) {
|
|
470
521
|
case "resource_server": {
|
|
471
|
-
const n = await
|
|
522
|
+
const n = await t.resourceServers.get(e, s.id);
|
|
472
523
|
return (n == null ? void 0 : n.is_system) === !0;
|
|
473
524
|
}
|
|
474
525
|
case "role": {
|
|
475
|
-
const n = await
|
|
526
|
+
const n = await t.roles.get(e, s.id);
|
|
476
527
|
return (n == null ? void 0 : n.is_system) === !0;
|
|
477
528
|
}
|
|
478
529
|
case "connection": {
|
|
479
|
-
const n = await
|
|
530
|
+
const n = await t.connections.get(e, s.id);
|
|
480
531
|
return (n == null ? void 0 : n.is_system) === !0;
|
|
481
532
|
}
|
|
482
533
|
default:
|
|
@@ -486,43 +537,43 @@ async function Y(a, e, s) {
|
|
|
486
537
|
return !1;
|
|
487
538
|
}
|
|
488
539
|
}
|
|
489
|
-
function
|
|
540
|
+
function Z(t) {
|
|
490
541
|
return {
|
|
491
542
|
resource_server: "resource server",
|
|
492
543
|
role: "role",
|
|
493
544
|
connection: "connection"
|
|
494
|
-
}[
|
|
545
|
+
}[t];
|
|
495
546
|
}
|
|
496
|
-
function
|
|
497
|
-
return async (
|
|
498
|
-
if (!["PATCH", "PUT", "DELETE"].includes(
|
|
547
|
+
function J() {
|
|
548
|
+
return async (t, e) => {
|
|
549
|
+
if (!["PATCH", "PUT", "DELETE"].includes(t.req.method))
|
|
499
550
|
return e();
|
|
500
|
-
const s =
|
|
551
|
+
const s = Y(t.req.path);
|
|
501
552
|
if (!s)
|
|
502
553
|
return e();
|
|
503
|
-
const n =
|
|
554
|
+
const n = t.var.tenant_id || t.req.header("x-tenant-id") || t.req.header("tenant-id");
|
|
504
555
|
if (!n)
|
|
505
556
|
return e();
|
|
506
|
-
if (await
|
|
557
|
+
if (await B(t.env.data, n, s))
|
|
507
558
|
throw new p(403, {
|
|
508
|
-
message: `This ${
|
|
559
|
+
message: `This ${Z(s.type)} is a system resource and cannot be modified. Make changes in the main tenant instead.`
|
|
509
560
|
});
|
|
510
561
|
return e();
|
|
511
562
|
};
|
|
512
563
|
}
|
|
513
|
-
function
|
|
564
|
+
function X(t) {
|
|
514
565
|
return async (e, s) => {
|
|
515
|
-
if (!
|
|
566
|
+
if (!t.accessControl)
|
|
516
567
|
return s();
|
|
517
|
-
const n = e.var.tenant_id,
|
|
568
|
+
const n = e.var.tenant_id, a = e.var.organization_id;
|
|
518
569
|
if (!n)
|
|
519
570
|
throw new p(400, {
|
|
520
571
|
message: "Tenant ID not found in request"
|
|
521
572
|
});
|
|
522
|
-
if (!
|
|
523
|
-
|
|
573
|
+
if (!E(
|
|
574
|
+
a,
|
|
524
575
|
n,
|
|
525
|
-
|
|
576
|
+
t.accessControl.mainTenantId
|
|
526
577
|
))
|
|
527
578
|
throw new p(403, {
|
|
528
579
|
message: `Access denied to tenant ${n}`
|
|
@@ -530,44 +581,44 @@ function G(a) {
|
|
|
530
581
|
return s();
|
|
531
582
|
};
|
|
532
583
|
}
|
|
533
|
-
function
|
|
584
|
+
function H(t) {
|
|
534
585
|
return async (e, s) => {
|
|
535
|
-
if (!
|
|
586
|
+
if (!t.subdomainRouting)
|
|
536
587
|
return s();
|
|
537
588
|
const {
|
|
538
589
|
baseDomain: n,
|
|
539
|
-
reservedSubdomains:
|
|
540
|
-
resolveSubdomain:
|
|
541
|
-
} =
|
|
542
|
-
let
|
|
590
|
+
reservedSubdomains: a = [],
|
|
591
|
+
resolveSubdomain: c
|
|
592
|
+
} = t.subdomainRouting, d = e.req.header("host") || "";
|
|
593
|
+
let o = null;
|
|
543
594
|
if (d.endsWith(n)) {
|
|
544
595
|
const r = d.slice(0, -(n.length + 1));
|
|
545
|
-
r && !r.includes(".") && (
|
|
596
|
+
r && !r.includes(".") && (o = r);
|
|
546
597
|
}
|
|
547
|
-
if (
|
|
548
|
-
return
|
|
598
|
+
if (o && a.includes(o) && (o = null), !o)
|
|
599
|
+
return t.accessControl && e.set("tenant_id", t.accessControl.mainTenantId), s();
|
|
549
600
|
let i = null;
|
|
550
|
-
if (
|
|
551
|
-
i = await o
|
|
552
|
-
else if (
|
|
601
|
+
if (c)
|
|
602
|
+
i = await c(o);
|
|
603
|
+
else if (t.subdomainRouting.useOrganizations !== !1 && t.accessControl)
|
|
553
604
|
try {
|
|
554
605
|
const r = await e.env.data.organizations.get(
|
|
555
|
-
|
|
556
|
-
|
|
606
|
+
t.accessControl.mainTenantId,
|
|
607
|
+
o
|
|
557
608
|
);
|
|
558
609
|
r && (i = r.id);
|
|
559
610
|
} catch {
|
|
560
611
|
}
|
|
561
612
|
if (!i)
|
|
562
613
|
throw new p(404, {
|
|
563
|
-
message: `Tenant not found for subdomain: ${
|
|
614
|
+
message: `Tenant not found for subdomain: ${o}`
|
|
564
615
|
});
|
|
565
616
|
return e.set("tenant_id", i), s();
|
|
566
617
|
};
|
|
567
618
|
}
|
|
568
|
-
function
|
|
619
|
+
function k(t) {
|
|
569
620
|
return async (e, s) => {
|
|
570
|
-
if (!
|
|
621
|
+
if (!t.databaseIsolation)
|
|
571
622
|
return s();
|
|
572
623
|
const n = e.var.tenant_id;
|
|
573
624
|
if (!n)
|
|
@@ -575,12 +626,12 @@ function X(a) {
|
|
|
575
626
|
message: "Tenant ID not found in request"
|
|
576
627
|
});
|
|
577
628
|
try {
|
|
578
|
-
const
|
|
579
|
-
e.env.data =
|
|
580
|
-
} catch (
|
|
629
|
+
const a = await t.databaseIsolation.getAdapters(n);
|
|
630
|
+
e.env.data = a;
|
|
631
|
+
} catch (a) {
|
|
581
632
|
throw console.error(
|
|
582
633
|
`Failed to resolve database for tenant ${n}:`,
|
|
583
|
-
|
|
634
|
+
a
|
|
584
635
|
), new p(500, {
|
|
585
636
|
message: "Failed to resolve tenant database"
|
|
586
637
|
});
|
|
@@ -588,66 +639,66 @@ function X(a) {
|
|
|
588
639
|
return s();
|
|
589
640
|
};
|
|
590
641
|
}
|
|
591
|
-
function
|
|
592
|
-
const e =
|
|
593
|
-
return async (
|
|
594
|
-
}), await s(
|
|
595
|
-
}), await n(
|
|
596
|
-
}),
|
|
642
|
+
function I(t) {
|
|
643
|
+
const e = H(t), s = X(t), n = k(t);
|
|
644
|
+
return async (a, c) => (await e(a, async () => {
|
|
645
|
+
}), await s(a, async () => {
|
|
646
|
+
}), await n(a, async () => {
|
|
647
|
+
}), c());
|
|
597
648
|
}
|
|
598
|
-
function
|
|
599
|
-
const e =
|
|
649
|
+
function re(t) {
|
|
650
|
+
const e = b(t);
|
|
600
651
|
return {
|
|
601
652
|
name: "multi-tenancy",
|
|
602
653
|
// Apply multi-tenancy middleware for subdomain routing, database resolution, etc.
|
|
603
|
-
middleware:
|
|
654
|
+
middleware: I(t),
|
|
604
655
|
// Provide lifecycle hooks
|
|
605
656
|
hooks: e,
|
|
606
657
|
// Mount tenant management routes
|
|
607
658
|
routes: [
|
|
608
659
|
{
|
|
609
660
|
path: "/management",
|
|
610
|
-
handler: S(
|
|
661
|
+
handler: S(t, e)
|
|
611
662
|
}
|
|
612
663
|
],
|
|
613
664
|
// Called when plugin is registered
|
|
614
665
|
onRegister: async () => {
|
|
615
|
-
console.log("Multi-tenancy plugin registered"),
|
|
616
|
-
` - Access control enabled (main tenant: ${
|
|
617
|
-
),
|
|
618
|
-
` - Subdomain routing enabled (base domain: ${
|
|
619
|
-
),
|
|
666
|
+
console.log("Multi-tenancy plugin registered"), t.accessControl && console.log(
|
|
667
|
+
` - Access control enabled (main tenant: ${t.accessControl.mainTenantId})`
|
|
668
|
+
), t.subdomainRouting && console.log(
|
|
669
|
+
` - Subdomain routing enabled (base domain: ${t.subdomainRouting.baseDomain})`
|
|
670
|
+
), t.databaseIsolation && console.log(" - Database isolation enabled");
|
|
620
671
|
}
|
|
621
672
|
};
|
|
622
673
|
}
|
|
623
|
-
function
|
|
624
|
-
const e =
|
|
674
|
+
function b(t) {
|
|
675
|
+
const e = t.accessControl ? O(t.accessControl) : {}, s = t.databaseIsolation ? K(t.databaseIsolation) : {}, n = N(t);
|
|
625
676
|
return {
|
|
626
677
|
...e,
|
|
627
678
|
...s,
|
|
628
679
|
tenants: n
|
|
629
680
|
};
|
|
630
681
|
}
|
|
631
|
-
function
|
|
632
|
-
const e = new $(), s =
|
|
633
|
-
return e.route("/tenants", S(
|
|
682
|
+
function x(t) {
|
|
683
|
+
const e = new $(), s = b(t);
|
|
684
|
+
return e.route("/tenants", S(t, s)), e;
|
|
634
685
|
}
|
|
635
|
-
function
|
|
686
|
+
function ie(t) {
|
|
636
687
|
return {
|
|
637
|
-
hooks:
|
|
638
|
-
middleware:
|
|
639
|
-
app:
|
|
640
|
-
config:
|
|
688
|
+
hooks: b(t),
|
|
689
|
+
middleware: I(t),
|
|
690
|
+
app: x(t),
|
|
691
|
+
config: t
|
|
641
692
|
};
|
|
642
693
|
}
|
|
643
|
-
function
|
|
694
|
+
function oe(t) {
|
|
644
695
|
const {
|
|
645
696
|
mainTenantId: e = "main",
|
|
646
697
|
syncResourceServers: s = !0,
|
|
647
698
|
multiTenancy: n,
|
|
648
|
-
entityHooks:
|
|
649
|
-
...
|
|
650
|
-
} =
|
|
699
|
+
entityHooks: a,
|
|
700
|
+
...c
|
|
701
|
+
} = t, d = {
|
|
651
702
|
...n,
|
|
652
703
|
accessControl: {
|
|
653
704
|
mainTenantId: e,
|
|
@@ -655,50 +706,50 @@ function re(a) {
|
|
|
655
706
|
defaultPermissions: ["tenant:admin"],
|
|
656
707
|
...n == null ? void 0 : n.accessControl
|
|
657
708
|
}
|
|
658
|
-
},
|
|
709
|
+
}, o = b(d);
|
|
659
710
|
let i, r;
|
|
660
|
-
s && (i =
|
|
711
|
+
s && (i = G({
|
|
661
712
|
mainTenantId: e,
|
|
662
|
-
getChildTenantIds: async () => (await
|
|
663
|
-
(y) =>
|
|
713
|
+
getChildTenantIds: async () => (await M(
|
|
714
|
+
(y) => t.dataAdapter.tenants.list(y),
|
|
664
715
|
"tenants",
|
|
665
716
|
{ cursorField: "id", pageSize: 100 }
|
|
666
717
|
)).filter((y) => y.id !== e).map((y) => y.id),
|
|
667
|
-
getAdapters: async (g) =>
|
|
668
|
-
}), r =
|
|
718
|
+
getAdapters: async (g) => t.dataAdapter
|
|
719
|
+
}), r = L({
|
|
669
720
|
mainTenantId: e,
|
|
670
|
-
getMainTenantAdapters: async () =>
|
|
671
|
-
getAdapters: async (g) =>
|
|
721
|
+
getMainTenantAdapters: async () => t.dataAdapter,
|
|
722
|
+
getAdapters: async (g) => t.dataAdapter
|
|
672
723
|
}));
|
|
673
|
-
const
|
|
724
|
+
const l = async (g, y, ...h) => {
|
|
674
725
|
const v = [];
|
|
675
726
|
if (g)
|
|
676
727
|
try {
|
|
677
728
|
await g(...h);
|
|
678
|
-
} catch (
|
|
679
|
-
v.push(
|
|
729
|
+
} catch (A) {
|
|
730
|
+
v.push(A instanceof Error ? A : new Error(String(A)));
|
|
680
731
|
}
|
|
681
732
|
if (y)
|
|
682
733
|
try {
|
|
683
734
|
await y(...h);
|
|
684
|
-
} catch (
|
|
685
|
-
v.push(
|
|
735
|
+
} catch (A) {
|
|
736
|
+
v.push(A instanceof Error ? A : new Error(String(A)));
|
|
686
737
|
}
|
|
687
738
|
if (v.length === 1)
|
|
688
739
|
throw v[0];
|
|
689
740
|
if (v.length > 1)
|
|
690
741
|
throw new AggregateError(
|
|
691
742
|
v,
|
|
692
|
-
`Multiple hook errors: ${v.map((
|
|
743
|
+
`Multiple hook errors: ${v.map((A) => A.message).join("; ")}`
|
|
693
744
|
);
|
|
694
745
|
}, f = {
|
|
695
|
-
...
|
|
746
|
+
...a,
|
|
696
747
|
resourceServers: i ? {
|
|
697
|
-
...
|
|
748
|
+
...a == null ? void 0 : a.resourceServers,
|
|
698
749
|
afterCreate: async (g, y) => {
|
|
699
750
|
var h;
|
|
700
|
-
await
|
|
701
|
-
(h =
|
|
751
|
+
await l(
|
|
752
|
+
(h = a == null ? void 0 : a.resourceServers) == null ? void 0 : h.afterCreate,
|
|
702
753
|
i == null ? void 0 : i.afterCreate,
|
|
703
754
|
g,
|
|
704
755
|
y
|
|
@@ -706,8 +757,8 @@ function re(a) {
|
|
|
706
757
|
},
|
|
707
758
|
afterUpdate: async (g, y, h) => {
|
|
708
759
|
var v;
|
|
709
|
-
await
|
|
710
|
-
(v =
|
|
760
|
+
await l(
|
|
761
|
+
(v = a == null ? void 0 : a.resourceServers) == null ? void 0 : v.afterUpdate,
|
|
711
762
|
i == null ? void 0 : i.afterUpdate,
|
|
712
763
|
g,
|
|
713
764
|
y,
|
|
@@ -716,61 +767,62 @@ function re(a) {
|
|
|
716
767
|
},
|
|
717
768
|
afterDelete: async (g, y) => {
|
|
718
769
|
var h;
|
|
719
|
-
await
|
|
720
|
-
(h =
|
|
770
|
+
await l(
|
|
771
|
+
(h = a == null ? void 0 : a.resourceServers) == null ? void 0 : h.afterDelete,
|
|
721
772
|
i == null ? void 0 : i.afterDelete,
|
|
722
773
|
g,
|
|
723
774
|
y
|
|
724
775
|
);
|
|
725
776
|
}
|
|
726
|
-
} :
|
|
777
|
+
} : a == null ? void 0 : a.resourceServers,
|
|
727
778
|
tenants: r ? {
|
|
728
|
-
...
|
|
779
|
+
...a == null ? void 0 : a.tenants,
|
|
729
780
|
afterCreate: async (g, y) => {
|
|
730
781
|
var h;
|
|
731
|
-
await
|
|
732
|
-
(h =
|
|
782
|
+
await l(
|
|
783
|
+
(h = a == null ? void 0 : a.tenants) == null ? void 0 : h.afterCreate,
|
|
733
784
|
r == null ? void 0 : r.afterCreate,
|
|
734
785
|
g,
|
|
735
786
|
y
|
|
736
787
|
);
|
|
737
788
|
}
|
|
738
|
-
} :
|
|
739
|
-
},
|
|
740
|
-
...
|
|
789
|
+
} : a == null ? void 0 : a.tenants
|
|
790
|
+
}, u = P({
|
|
791
|
+
...c,
|
|
741
792
|
entityHooks: f
|
|
742
|
-
}), { app: m, managementApp: T, ..._ } =
|
|
743
|
-
w.use("/api/v2/*",
|
|
744
|
-
const
|
|
793
|
+
}), { app: m, managementApp: T, ..._ } = u, w = new $();
|
|
794
|
+
w.use("/api/v2/*", J()), w.route("/", m);
|
|
795
|
+
const z = S(
|
|
745
796
|
d,
|
|
746
|
-
|
|
797
|
+
o
|
|
747
798
|
);
|
|
748
|
-
return w.route("/api/v2/tenants",
|
|
799
|
+
return w.route("/api/v2/tenants", z), {
|
|
749
800
|
app: w,
|
|
750
801
|
managementApp: T,
|
|
751
802
|
..._,
|
|
752
803
|
multiTenancyConfig: d,
|
|
753
|
-
multiTenancyHooks:
|
|
804
|
+
multiTenancyHooks: o
|
|
754
805
|
};
|
|
755
806
|
}
|
|
756
807
|
export {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
808
|
+
le as MANAGEMENT_API_SCOPES,
|
|
809
|
+
O as createAccessControlHooks,
|
|
810
|
+
X as createAccessControlMiddleware,
|
|
811
|
+
K as createDatabaseHooks,
|
|
812
|
+
k as createDatabaseMiddleware,
|
|
813
|
+
x as createMultiTenancy,
|
|
814
|
+
b as createMultiTenancyHooks,
|
|
815
|
+
I as createMultiTenancyMiddleware,
|
|
816
|
+
re as createMultiTenancyPlugin,
|
|
817
|
+
J as createProtectSyncedMiddleware,
|
|
818
|
+
N as createProvisioningHooks,
|
|
819
|
+
G as createResourceServerSyncHooks,
|
|
820
|
+
H as createSubdomainMiddleware,
|
|
821
|
+
L as createTenantResourceServerSyncHooks,
|
|
770
822
|
S as createTenantsRouter,
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
823
|
+
M as fetchAll,
|
|
824
|
+
oe as init,
|
|
825
|
+
ue as seed,
|
|
826
|
+
ie as setupMultiTenancy,
|
|
827
|
+
E as validateTenantAccess
|
|
776
828
|
};
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "https://github.com/markusahlstrand/authhero"
|
|
13
13
|
},
|
|
14
|
-
"version": "13.
|
|
14
|
+
"version": "13.5.0",
|
|
15
15
|
"description": "Multi-tenancy support for AuthHero with organization-based access control and per-tenant database isolation",
|
|
16
16
|
"files": [
|
|
17
17
|
"dist"
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"zod": "^3.24.0",
|
|
44
44
|
"@authhero/adapter-interfaces": "0.112.0",
|
|
45
|
-
"authhero": "0.
|
|
45
|
+
"authhero": "0.303.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@hono/zod-openapi": "^0.19.10",
|