@authhero/multi-tenancy 13.4.0 → 13.6.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.
@@ -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=(e,n,s)=>n in e?W(e,n,{enumerable:!0,configurable:!0,writable:!0,value:s}):e[n]=s;var z=(e,n,s)=>G(e,typeof n!="symbol"?n+"":n,s);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const I=require("hono"),S=require("authhero"),Q=require("zod"),$=require("@authhero/adapter-interfaces");function q(e){const{mainTenantId:n,requireOrganizationMatch:s=!0}=e;return{async onTenantAccessValidation(t,a){if(a===n)return!0;if(s){const o=t.var.organization_id;return o?o===a:!1}return!0}}}function P(e,n,s){return n===s?!0:e?e===n:!1}function D(e){return{async resolveDataAdapters(n){try{return await e.getAdapters(n)}catch(s){console.error(`Failed to resolve data adapters for tenant ${n}:`,s);return}}}}function F(e){return{async afterCreate(n,s){const{accessControl:t,databaseIsolation:a,settingsInheritance:o}=e;t&&n.ctx&&await V(n,s,t),a!=null&&a.onProvision&&await a.onProvision(s.id),(o==null?void 0:o.inheritFromMain)!==!1&&n.ctx&&await Y(n,s,e)},async beforeDelete(n,s){const{accessControl:t,databaseIsolation:a}=e;if(t)try{const d=(await n.adapters.organizations.list(t.mainTenantId)).organizations.find(i=>i.name===s);d&&await n.adapters.organizations.remove(t.mainTenantId,d.id)}catch(o){console.warn(`Failed to remove organization for tenant ${s}:`,o)}if(a!=null&&a.onDeprovision)try{await a.onDeprovision(s)}catch(o){console.warn(`Failed to deprovision database for tenant ${s}:`,o)}}}}async function V(e,n,s){const{mainTenantId:t,defaultPermissions:a,defaultRoles:o,issuer:d,adminRoleName:i="Tenant Admin",adminRoleDescription:c="Full access to all tenant management operations",addCreatorToOrganization:r=!0}=s;await e.adapters.organizations.create(t,{id:n.id,name:n.id,display_name:n.friendly_name||n.id});let l=null;if(d&&(l=await L(e,t,d,i,c)),r&&e.ctx){const f=e.ctx.var.user;if(f!=null&&f.sub)try{await e.adapters.userOrganizations.create(t,{user_id:f.sub,organization_id:n.id}),l&&await e.adapters.userRoles.create(t,f.sub,l,n.id)}catch(u){console.warn(`Failed to add creator ${f.sub} to organization ${n.id}:`,u)}}o&&o.length>0&&console.log(`Would assign roles ${o.join(", ")} to organization ${n.id}`),a&&a.length>0&&console.log(`Would grant permissions ${a.join(", ")} to organization ${n.id}`)}async function L(e,n,s,t,a){const d=(await e.adapters.roles.list(n,{})).roles.find(l=>l.name===t);if(d)return d.id;const i=await e.adapters.roles.create(n,{name:t,description:a}),c=`${s}api/v2/`,r=S.MANAGEMENT_API_SCOPES.map(l=>({role_id:i.id,resource_server_identifier:c,permission_name:l.value}));return await e.adapters.rolePermissions.assign(n,i.id,r),i.id}async function Y(e,n,s){const{accessControl:t,settingsInheritance:a}=s;if(!t)return;const o=await e.adapters.tenants.get(t.mainTenantId);if(!o)return;let d={...o};const i=["id","created_at","updated_at","friendly_name","audience","sender_email","sender_name"];for(const c of i)delete d[c];if(a!=null&&a.inheritedKeys){const c={};for(const r of a.inheritedKeys)r in o&&!i.includes(r)&&(c[r]=o[r]);d=c}if(a!=null&&a.excludedKeys)for(const c of a.excludedKeys)delete d[c];a!=null&&a.transformSettings&&(d=a.transformSettings(d,n.id)),Object.keys(d).length>0&&await e.adapters.tenants.update(n.id,d)}async function R(e,n,s={}){const{cursorField:t="id",sortOrder:a="asc",pageSize:o=100,maxItems:d=1e4,q:i}=s,c=[];let r,l=!0;for(;l;){let f=i||"";if(r){const w=`${t}:${a==="asc"?">":"<"}${r}`;f=f?`(${f}) AND ${w}`:w}const u={per_page:o,page:0,sort:{sort_by:t,sort_order:a},...f&&{q:f}},_=(await e(u))[n]||[];if(_.length===0)l=!1;else{c.push(..._);const g=_[_.length-1];if(g&&typeof g=="object"){const w=g[t];w!=null&&(r=String(w))}_.length<o&&(l=!1),d!==-1&&c.length>=d&&(console.warn(`fetchAll: Reached maxItems limit (${d}). There may be more items.`),l=!1)}}return c}function j(e){const{mainTenantId:n,getChildTenantIds:s,getAdapters:t,shouldSync:a=()=>!0,transformForSync:o}=e;async function d(r,l,f){return(await r.resourceServers.list(l,{q:`identifier:${f}`,per_page:1})).resource_servers[0]??null}async function i(r,l){const f=await s();await Promise.all(f.map(async u=>{try{const m=await t(u),g={...o?o(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,g):await m.resourceServers.create(u,g)}else{const w=await d(m,u,r.identifier);w&&w.id&&await m.resourceServers.update(u,w.id,g)}}catch(m){console.error(`Failed to sync resource server "${r.identifier}" to tenant "${u}":`,m)}}))}async function c(r){const l=await s();await Promise.all(l.map(async f=>{try{const u=await t(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===n&&a(l)&&await i(l,"create")},afterUpdate:async(r,l,f)=>{r.tenantId===n&&a(f)&&await i(f,"update")},afterDelete:async(r,l)=>{r.tenantId===n&&await c(l)}}}function U(e){const{mainTenantId:n,getMainTenantAdapters:s,getAdapters:t,shouldSync:a=()=>!0,transformForSync:o}=e;return{async afterCreate(d,i){if(i.id!==n)try{const c=await s(),r=await t(i.id),l=await R(f=>c.resourceServers.list(n,f),"resource_servers",{cursorField:"id",pageSize:100});await Promise.all(l.filter(f=>a(f)).map(async f=>{const u=f;try{const m=o?o(u,i.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(i.id,{...m,is_system:!0})}catch(m){console.error(`Failed to sync resource server "${u.identifier}" to new tenant "${i.id}":`,m)}}))}catch(c){console.error(`Failed to sync resource servers to new tenant "${i.id}":`,c)}}}}var p=class extends Error{constructor(n=500,s){super(s==null?void 0:s.message,{cause:s==null?void 0:s.cause});z(this,"res");z(this,"status");this.res=s==null?void 0:s.res,this.status=n}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(e,n){const s=new I.Hono;return s.get("/",async t=>{var f,u;const a=$.auth0QuerySchema.parse(t.req.query()),{page:o,per_page:d,include_totals:i,q:c}=a,r=t.var.user;if(e.accessControl&&(r!=null&&r.sub)){const m=e.accessControl.mainTenantId,g=(await t.env.data.userOrganizations.listUserOrganizations(m,r.sub,{})).organizations.map(y=>y.id);g.includes(m)||g.push(m);const w=await t.env.data.tenants.list({page:o,per_page:d,include_totals:i,q:c}),A=w.tenants.filter(y=>g.includes(y.id));return i?t.json({tenants:A,start:((f=w.totals)==null?void 0:f.start)??0,limit:((u=w.totals)==null?void 0:u.limit)??d,length:A.length}):t.json(A)}const l=await t.env.data.tenants.list({page:o,per_page:d,include_totals:i,q:c});return i?t.json(l):t.json(l.tenants)}),s.get("/:id",async t=>{const a=t.req.param("id");if(e.accessControl){const d=t.var.user,i=e.accessControl.mainTenantId;if(a!==i){if(!(d!=null&&d.sub))throw new p(401,{message:"Authentication required"});if(!(await t.env.data.userOrganizations.listUserOrganizations(i,d.sub,{})).organizations.some(l=>l.id===a))throw new p(403,{message:"Access denied to this tenant"})}}const o=await t.env.data.tenants.get(a);if(!o)throw new p(404,{message:"Tenant not found"});return t.json(o)}),s.post("/",async t=>{var a,o,d;try{const i=t.var.user;if(!(i!=null&&i.sub))throw new p(401,{message:"Authentication required to create tenants"});let c=$.tenantInsertSchema.parse(await t.req.json());const r={adapters:t.env.data,ctx:t};(a=n.tenants)!=null&&a.beforeCreate&&(c=await n.tenants.beforeCreate(r,c));const l=await t.env.data.tenants.create(c);return(o=n.tenants)!=null&&o.afterCreate&&await n.tenants.afterCreate(r,l),t.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"||(d=i.message)!=null&&d.includes("UNIQUE constraint failed"))?new p(409,{message:"Tenant with this ID already exists"}):i}}),s.patch("/:id",async t=>{var u,m;const a=t.req.param("id");if(e.accessControl){const _=t.var.user;if(!(_!=null&&_.sub))throw new p(401,{message:"Authentication required to update tenants"});const g=e.accessControl.mainTenantId;if(a!==g&&!(await t.env.data.userOrganizations.listUserOrganizations(g,_.sub,{})).organizations.some(y=>y.id===a))throw new p(403,{message:"Access denied to update this tenant"})}const o=$.tenantInsertSchema.partial().parse(await t.req.json()),{id:d,...i}=o;if(!await t.env.data.tenants.get(a))throw new p(404,{message:"Tenant not found"});const r={adapters:t.env.data,ctx:t};let l=i;(u=n.tenants)!=null&&u.beforeUpdate&&(l=await n.tenants.beforeUpdate(r,a,i)),await t.env.data.tenants.update(a,l);const f=await t.env.data.tenants.get(a);if(!f)throw new p(404,{message:"Tenant not found after update"});return(m=n.tenants)!=null&&m.afterUpdate&&await n.tenants.afterUpdate(r,f),t.json(f)}),s.delete("/:id",async t=>{var i,c;const a=t.req.param("id");if(e.accessControl&&a===e.accessControl.mainTenantId)throw new p(400,{message:"Cannot delete the main tenant"});if(e.accessControl){const r=t.var.user;if(!(r!=null&&r.sub))throw new p(401,{message:"Authentication required to delete tenants"});const l=e.accessControl.mainTenantId;if(!(await t.env.data.userOrganizations.listUserOrganizations(l,r.sub,{})).organizations.some(m=>m.id===a))throw new p(403,{message:"Access denied to delete this tenant"})}if(!await t.env.data.tenants.get(a))throw new p(404,{message:"Tenant not found"});const d={adapters:t.env.data,ctx:t};return(i=n.tenants)!=null&&i.beforeDelete&&await n.tenants.beforeDelete(d,a),await t.env.data.tenants.remove(a),(c=n.tenants)!=null&&c.afterDelete&&await n.tenants.afterDelete(d,a),t.body(null,204)}),s}function B(e){const n=[{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:t}of n){const a=e.match(s);if(a&&a[1])return{type:t,id:a[1]}}return null}async function Z(e,n,s){try{switch(s.type){case"resource_server":{const t=await e.resourceServers.get(n,s.id);return(t==null?void 0:t.is_system)===!0}case"role":{const t=await e.roles.get(n,s.id);return(t==null?void 0:t.is_system)===!0}case"connection":{const t=await e.connections.get(n,s.id);return(t==null?void 0:t.is_system)===!0}default:return!1}}catch{return!1}}function J(e){return{resource_server:"resource server",role:"role",connection:"connection"}[e]}function E(){return async(e,n)=>{if(!["PATCH","PUT","DELETE"].includes(e.req.method))return n();const s=B(e.req.path);if(!s)return n();const t=e.var.tenant_id||e.req.header("x-tenant-id")||e.req.header("tenant-id");if(!t)return n();if(await Z(e.env.data,t,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 n()}}function N(e){return async(n,s)=>{if(!e.accessControl)return s();const t=n.var.tenant_id,a=n.var.organization_id;if(!t)throw new p(400,{message:"Tenant ID not found in request"});if(!P(a,t,e.accessControl.mainTenantId))throw new p(403,{message:`Access denied to tenant ${t}`});return s()}}function k(e){return async(n,s)=>{if(!e.subdomainRouting)return s();const{baseDomain:t,reservedSubdomains:a=[],resolveSubdomain:o}=e.subdomainRouting,d=n.req.header("host")||"";let i=null;if(d.endsWith(t)){const r=d.slice(0,-(t.length+1));r&&!r.includes(".")&&(i=r)}if(i&&a.includes(i)&&(i=null),!i)return e.accessControl&&n.set("tenant_id",e.accessControl.mainTenantId),s();let c=null;if(o)c=await o(i);else if(e.subdomainRouting.useOrganizations!==!1&&e.accessControl)try{const r=await n.env.data.organizations.get(e.accessControl.mainTenantId,i);r&&(c=r.id)}catch{}if(!c)throw new p(404,{message:`Tenant not found for subdomain: ${i}`});return n.set("tenant_id",c),s()}}function K(e){return async(n,s)=>{if(!e.databaseIsolation)return s();const t=n.var.tenant_id;if(!t)throw new p(400,{message:"Tenant ID not found in request"});try{const a=await e.databaseIsolation.getAdapters(t);n.env.data=a}catch(a){throw console.error(`Failed to resolve database for tenant ${t}:`,a),new p(500,{message:"Failed to resolve tenant database"})}return s()}}function O(e){const n=k(e),s=N(e),t=K(e);return async(a,o)=>(await n(a,async()=>{}),await s(a,async()=>{}),await t(a,async()=>{}),o())}function X(e){const n=C(e);return{name:"multi-tenancy",middleware:O(e),hooks:n,routes:[{path:"/management",handler:M(e,n)}],onRegister:async()=>{console.log("Multi-tenancy plugin registered"),e.accessControl&&console.log(` - Access control enabled (main tenant: ${e.accessControl.mainTenantId})`),e.subdomainRouting&&console.log(` - Subdomain routing enabled (base domain: ${e.subdomainRouting.baseDomain})`),e.databaseIsolation&&console.log(" - Database isolation enabled")}}}function C(e){const n=e.accessControl?q(e.accessControl):{},s=e.databaseIsolation?D(e.databaseIsolation):{},t=F(e);return{...n,...s,tenants:t}}function H(e){const n=new I.Hono,s=C(e);return n.route("/tenants",M(e,s)),n}function x(e){return{hooks:C(e),middleware:O(e),app:H(e),config:e}}function ee(e){const{mainTenantId:n="main",syncResourceServers:s=!0,multiTenancy:t,entityHooks:a,...o}=e,d={...t,accessControl:{mainTenantId:n,requireOrganizationMatch:!1,defaultPermissions:["tenant:admin"],...t==null?void 0:t.accessControl}},i=C(d);let c,r;s&&(c=j({mainTenantId:n,getChildTenantIds:async()=>(await R(h=>e.dataAdapter.tenants.list(h),"tenants",{cursorField:"id",pageSize:100})).filter(h=>h.id!==n).map(h=>h.id),getAdapters:async y=>e.dataAdapter}),r=U({mainTenantId:n,getMainTenantAdapters:async()=>e.dataAdapter,getAdapters:async y=>e.dataAdapter}));const l=async(y,h,...T)=>{const v=[];if(y)try{await y(...T)}catch(b){v.push(b instanceof Error?b:new Error(String(b)))}if(h)try{await h(...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={...a,resourceServers:c?{...a==null?void 0:a.resourceServers,afterCreate:async(y,h)=>{var T;await l((T=a==null?void 0:a.resourceServers)==null?void 0:T.afterCreate,c==null?void 0:c.afterCreate,y,h)},afterUpdate:async(y,h,T)=>{var v;await l((v=a==null?void 0:a.resourceServers)==null?void 0:v.afterUpdate,c==null?void 0:c.afterUpdate,y,h,T)},afterDelete:async(y,h)=>{var T;await l((T=a==null?void 0:a.resourceServers)==null?void 0:T.afterDelete,c==null?void 0:c.afterDelete,y,h)}}:a==null?void 0:a.resourceServers,tenants:r?{...a==null?void 0:a.tenants,afterCreate:async(y,h)=>{var T;await l((T=a==null?void 0:a.tenants)==null?void 0:T.afterCreate,r==null?void 0:r.afterCreate,y,h)}}:a==null?void 0:a.tenants},u=S.init({...o,entityHooks:f}),{app:m,managementApp:_,...g}=u,w=new I.Hono;w.use("/api/v2/*",E()),w.route("/",m);const A=M(d,i);return w.route("/api/v2/tenants",A),{app:w,managementApp:_,...g,multiTenancyConfig:d,multiTenancyHooks:i}}Object.defineProperty(exports,"MANAGEMENT_API_SCOPES",{enumerable:!0,get:()=>S.MANAGEMENT_API_SCOPES});Object.defineProperty(exports,"seed",{enumerable:!0,get:()=>S.seed});exports.createAccessControlHooks=q;exports.createAccessControlMiddleware=N;exports.createDatabaseHooks=D;exports.createDatabaseMiddleware=K;exports.createMultiTenancy=H;exports.createMultiTenancyHooks=C;exports.createMultiTenancyMiddleware=O;exports.createMultiTenancyPlugin=X;exports.createProtectSyncedMiddleware=E;exports.createProvisioningHooks=F;exports.createResourceServerSyncHooks=j;exports.createSubdomainMiddleware=k;exports.createTenantResourceServerSyncHooks=U;exports.createTenantsRouter=M;exports.fetchAll=R;exports.init=ee;exports.setupMultiTenancy=x;exports.validateTenantAccess=P;
@@ -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,12 +9703,17 @@ 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;
9702
9713
  userId: string;
9703
9714
  email: string;
9704
9715
  clientId: string;
9716
+ clientSecret: string;
9705
9717
  }
9706
9718
  /**
9707
9719
  * Seed the AuthHero database with initial data.
@@ -18998,6 +19010,30 @@ export interface AccessControlConfig {
18998
19010
  * These roles should exist on the main tenant.
18999
19011
  */
19000
19012
  defaultRoles?: string[];
19013
+ /**
19014
+ * The issuer URL used to construct the Management API identifier.
19015
+ * If provided, a "Tenant Admin" role will be created with all Management API
19016
+ * permissions, and the user creating a new tenant will be assigned this role.
19017
+ * @example "https://auth.example.com/"
19018
+ */
19019
+ issuer?: string;
19020
+ /**
19021
+ * The name of the admin role to create/use for tenant administrators.
19022
+ * This role will have full Management API permissions.
19023
+ * @default "Tenant Admin"
19024
+ */
19025
+ adminRoleName?: string;
19026
+ /**
19027
+ * Description for the admin role.
19028
+ * @default "Full access to all tenant management operations"
19029
+ */
19030
+ adminRoleDescription?: string;
19031
+ /**
19032
+ * If true, the user creating a tenant will be added to the organization
19033
+ * and assigned the admin role.
19034
+ * @default true
19035
+ */
19036
+ addCreatorToOrganization?: boolean;
19001
19037
  }
19002
19038
  /**
19003
19039
  * Configuration for per-tenant database isolation.