@authhero/multi-tenancy 14.0.0 → 14.1.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 ee=Object.defineProperty;var te=(t,e,n)=>e in t?ee(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var k=(t,e,n)=>te(t,typeof e!="symbol"?e+"":e,n);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const ne=require("hono"),P=require("authhero"),C=require("@hono/zod-openapi"),I=require("@authhero/adapter-interfaces");function U(t){const{controlPlaneTenantId:e,requireOrganizationMatch:n=!0}=t;return{async onTenantAccessValidation(s,r){if(r===e)return!0;if(n){const i=s.var.org_name,o=s.var.organization_id,a=i||o;return a?a.toLowerCase()===r.toLowerCase():!1}return!0}}}function L(t,e,n,s){if(e===n)return!0;const r=s||t;return r?r.toLowerCase()===e.toLowerCase():!1}function B(t){return{async resolveDataAdapters(e){try{return await t.getAdapters(e)}catch(n){console.error(`Failed to resolve data adapters for tenant ${e}:`,n);return}}}}function se(t){return`urn:authhero:tenant:${t.toLowerCase()}`}function K(t){return{async beforeCreate(e,n){return!n.audience&&n.id?{...n,audience:se(n.id)}:n},async afterCreate(e,n){const{accessControl:s,databaseIsolation:r}=t;s&&e.ctx&&await ae(e,n,s),r!=null&&r.onProvision&&await r.onProvision(n.id)},async beforeDelete(e,n){const{accessControl:s,databaseIsolation:r}=t;if(s)try{const o=(await e.adapters.organizations.list(s.controlPlaneTenantId)).organizations.find(a=>a.name===n);o&&await e.adapters.organizations.remove(s.controlPlaneTenantId,o.id)}catch(i){console.warn(`Failed to remove organization for tenant ${n}:`,i)}if(r!=null&&r.onDeprovision)try{await r.onDeprovision(n)}catch(i){console.warn(`Failed to deprovision database for tenant ${n}:`,i)}}}}async function ae(t,e,n){const{controlPlaneTenantId:s,defaultPermissions:r,defaultRoles:i,issuer:o,adminRoleName:a="Tenant Admin",adminRoleDescription:u="Full access to all tenant management operations",addCreatorToOrganization:d=!0}=n,c=await t.adapters.organizations.create(s,{name:e.id,display_name:e.friendly_name||e.id});let p;if(o&&(p=await oe(t,s,a,u)),d&&t.ctx){const l=t.ctx.var.user;if(l!=null&&l.sub&&!await re(t,s,l.sub))try{await t.adapters.userOrganizations.create(s,{user_id:l.sub,organization_id:c.id}),p&&await t.adapters.userRoles.create(s,l.sub,p,c.id)}catch(w){console.warn(`Failed to add creator ${l.sub} to organization ${c.id}:`,w)}}i&&i.length>0&&console.log(`Would assign roles ${i.join(", ")} to organization ${c.id}`),r&&r.length>0&&console.log(`Would grant permissions ${r.join(", ")} to organization ${c.id}`)}async function re(t,e,n){const s=await t.adapters.userRoles.list(e,n,void 0,"");for(const r of s)if((await t.adapters.rolePermissions.list(e,r.id,{per_page:1e3})).some(a=>a.permission_name==="admin:organizations"))return!0;return!1}async function oe(t,e,n,s){const i=(await t.adapters.roles.list(e,{})).roles.find(d=>d.name===n);if(i)return i.id;const o=await t.adapters.roles.create(e,{name:n,description:s}),a=P.MANAGEMENT_API_AUDIENCE,u=P.MANAGEMENT_API_SCOPES.map(d=>({role_id:o.id,resource_server_identifier:a,permission_name:d.value}));return await t.adapters.rolePermissions.assign(e,o.id,u),o.id}const ie=["client_id","client_secret","app_secret","kid","team_id","twilio_sid","twilio_token"];function q(t,e,n=()=>!0){const{controlPlaneTenantId:s,getChildTenantIds:r,getAdapters:i}=t,o=new Map;async function a(c,p,l){return(await e(c).list(p,{q:`name:${l}`,per_page:1}))[0]??null}async function u(c){const p=await r(),l=e(await i(s));await Promise.all(p.map(async m=>{try{const w=await i(m),g=e(w),h={...l.transform(c),is_system:!0},y=await a(w,m,c.name),_=y?g.getId(y):void 0;if(y&&_){const T=g.preserveOnUpdate?g.preserveOnUpdate(y,h):h;await g.update(m,_,T)}else await g.create(m,h)}catch(w){console.error(`Failed to sync ${l.listKey} "${c.name}" to tenant "${m}":`,w)}}))}async function d(c){const p=await r();await Promise.all(p.map(async l=>{try{const m=await i(l),w=e(m),g=await a(m,l,c),f=g?w.getId(g):void 0;g&&f&&await w.remove(l,f)}catch(m){console.error(`Failed to delete entity "${c}" from tenant "${l}":`,m)}}))}return{afterCreate:async(c,p)=>{c.tenantId===s&&n(p)&&await u(p)},afterUpdate:async(c,p,l)=>{c.tenantId===s&&n(l)&&await u(l)},beforeDelete:async(c,p)=>{if(c.tenantId!==s)return;const m=await e(c.adapters).get(c.tenantId,p);m&&n(m)&&o.set(p,m)},afterDelete:async(c,p)=>{if(c.tenantId!==s)return;const l=o.get(p);l&&(o.delete(p),await d(l.name))}}}function D(t,e,n=()=>!0){const{controlPlaneTenantId:s,getControlPlaneAdapters:r,getAdapters:i}=t;return{async afterCreate(o,a){if(a.id!==s)try{const u=await r(),d=await i(a.id),c=e(u),p=e(d),l=await P.fetchAll(m=>c.listPaginated(s,m),c.listKey,{cursorField:"id",pageSize:100});await Promise.all(l.filter(m=>n(m)).map(async m=>{try{const w=c.transform(m);await p.create(a.id,{...w,is_system:!0})}catch(w){console.error(`Failed to sync entity to new tenant "${a.id}":`,w)}}))}catch(u){console.error(`Failed to sync entities to new tenant "${a.id}":`,u)}}}}const N=t=>({list:async(e,n)=>(await t.resourceServers.list(e,n)).resource_servers,listPaginated:(e,n)=>t.resourceServers.list(e,n),get:(e,n)=>t.resourceServers.get(e,n),create:(e,n)=>t.resourceServers.create(e,n),update:(e,n,s)=>t.resourceServers.update(e,n,s),remove:(e,n)=>t.resourceServers.remove(e,n),listKey:"resource_servers",getId:e=>e.id,transform:e=>({id:e.id,name:e.name,identifier:e.identifier,scopes:e.scopes,signing_alg:e.signing_alg,token_lifetime:e.token_lifetime,token_lifetime_for_web:e.token_lifetime_for_web})}),H=t=>({list:async(e,n)=>(await t.roles.list(e,n)).roles,listPaginated:(e,n)=>t.roles.list(e,n),get:(e,n)=>t.roles.get(e,n),create:(e,n)=>t.roles.create(e,n),update:(e,n,s)=>t.roles.update(e,n,s),remove:(e,n)=>t.roles.remove(e,n),listKey:"roles",getId:e=>e.id,transform:e=>({id:e.id,name:e.name,description:e.description})}),G=t=>({list:async(e,n)=>(await t.connections.list(e,n)).connections,listPaginated:(e,n)=>t.connections.list(e,n),get:(e,n)=>t.connections.get(e,n),create:(e,n)=>t.connections.create(e,n),update:(e,n,s)=>t.connections.update(e,n,s),remove:(e,n)=>t.connections.remove(e,n),listKey:"connections",getId:e=>e.id,transform:e=>{const n=e.options?{...e.options}:{};for(const s of ie)delete n[s];return{id:e.id,name:e.name,display_name:e.display_name,strategy:e.strategy,options:n,response_type:e.response_type,response_mode:e.response_mode,is_domain_connection:e.is_domain_connection,show_as_button:e.show_as_button,metadata:e.metadata}},preserveOnUpdate:(e,n)=>{const s=e.options||{};return{...n,options:{...n.options,client_id:s.client_id,client_secret:s.client_secret,app_secret:s.app_secret,kid:s.kid,team_id:s.team_id,twilio_sid:s.twilio_sid,twilio_token:s.twilio_token}}}});function V(t){const{sync:e={},filters:n={}}=t,s=e.resourceServers??!0,r=e.roles??!0,i=e.connections??!0,o=s?q(t,N,n.resourceServers):void 0,a=r?q(t,H,n.roles):void 0,u=i?q(t,G,n.connections):void 0,d=s?D(t,N,n.resourceServers):void 0,c=r?D(t,H,n.roles):void 0,p=i?D(t,G,n.connections):void 0,l=r?{async afterCreate(g,f){var h;if(f.id!==t.controlPlaneTenantId){await((h=c==null?void 0:c.afterCreate)==null?void 0:h.call(c,g,f));try{const y=await t.getControlPlaneAdapters(),_=await t.getAdapters(f.id),T=await P.fetchAll(v=>y.roles.list(t.controlPlaneTenantId,v),"roles",{cursorField:"id",pageSize:100}),z=new Map;for(const v of T.filter(A=>{var b;return((b=n.roles)==null?void 0:b.call(n,A))??!0})){const A=await m(_,f.id,v.name);A&&z.set(v.name,A.id)}for(const v of T.filter(A=>{var b;return((b=n.roles)==null?void 0:b.call(n,A))??!0})){const A=z.get(v.name);if(A)try{const b=await y.rolePermissions.list(t.controlPlaneTenantId,v.id,{});b.length>0&&await _.rolePermissions.assign(f.id,A,b.map(M=>({role_id:A,resource_server_identifier:M.resource_server_identifier,permission_name:M.permission_name})))}catch(b){console.error(`Failed to sync permissions for role "${v.name}" to tenant "${f.id}":`,b)}}}catch(y){console.error(`Failed to sync role permissions to tenant "${f.id}":`,y)}}}}:void 0;async function m(g,f,h){return(await g.roles.list(f,{q:`name:${h}`,per_page:1})).roles[0]??null}return{entityHooks:{resourceServers:o,roles:a,connections:u},tenantHooks:{async afterCreate(g,f){const h=[d==null?void 0:d.afterCreate,(l==null?void 0:l.afterCreate)??(c==null?void 0:c.afterCreate),p==null?void 0:p.afterCreate],y=[];for(const _ of h)if(_)try{await _(g,f)}catch(T){y.push(T instanceof Error?T:new Error(String(T)))}if(y.length===1)throw y[0];if(y.length>1)throw new AggregateError(y,y.map(_=>_.message).join("; "))}}}}var S=class extends Error{constructor(e=500,n){super(n==null?void 0:n.message,{cause:n==null?void 0:n.cause});k(this,"res");k(this,"status");this.res=n==null?void 0:n.res,this.status=e}getResponse(){return this.res?new Response(this.res.body,{status:this.status,headers:this.res.headers}):new Response(this.message,{status:this.status})}};function $(t,e){const n=new C.OpenAPIHono;return n.openapi(C.createRoute({tags:["tenants"],method:"get",path:"/",request:{query:I.auth0QuerySchema},security:[{Bearer:[]}],responses:{200:{content:{"application/json":{schema:C.z.object({tenants:C.z.array(I.tenantSchema),start:C.z.number().optional(),limit:C.z.number().optional(),length:C.z.number().optional()})}},description:"List of tenants"}}}),async s=>{var m,w,g,f;const r=s.req.valid("query"),{page:i,per_page:o,include_totals:a,q:u}=r,d=s.var.user,c=(d==null?void 0:d.permissions)||[];if(c.includes("auth:read")||c.includes("admin:organizations")){const h=await s.env.data.tenants.list({page:i,per_page:o,include_totals:a,q:u});return a?s.json({tenants:h.tenants,start:((m=h.totals)==null?void 0:m.start)??0,limit:((w=h.totals)==null?void 0:w.limit)??o,length:h.tenants.length}):s.json({tenants:h.tenants})}if(t.accessControl&&(d!=null&&d.sub)){const h=t.accessControl.controlPlaneTenantId,_=(await P.fetchAll(O=>s.env.data.userOrganizations.listUserOrganizations(h,d.sub,O),"organizations")).map(O=>O.name);if(_.length===0)return a?s.json({tenants:[],start:0,limit:o??50,length:0}):s.json({tenants:[]});const T=_.length,z=i??0,v=o??50,A=z*v,b=_.slice(A,A+v);if(b.length===0)return a?s.json({tenants:[],start:A,limit:v,length:T}):s.json({tenants:[]});const M=b.map(O=>`id:${O}`).join(" OR "),x=u?`(${M}) AND (${u})`:M,j=await s.env.data.tenants.list({q:x,per_page:v,include_totals:!1});return a?s.json({tenants:j.tenants,start:A,limit:v,length:T}):s.json({tenants:j.tenants})}const l=await s.env.data.tenants.list({page:i,per_page:o,include_totals:a,q:u});return a?s.json({tenants:l.tenants,start:((g=l.totals)==null?void 0:g.start)??0,limit:((f=l.totals)==null?void 0:f.limit)??o,length:l.tenants.length}):s.json({tenants:l.tenants})}),n.openapi(C.createRoute({tags:["tenants"],method:"post",path:"/",request:{body:{content:{"application/json":{schema:I.tenantInsertSchema}}}},security:[{Bearer:[]}],responses:{201:{content:{"application/json":{schema:I.tenantSchema}},description:"Tenant created"},400:{description:"Validation error"},409:{description:"Tenant with this ID already exists"}}}),async s=>{var u,d;const r=s.var.user;if(!(r!=null&&r.sub))throw new S(401,{message:"Authentication required to create tenants"});let i=s.req.valid("json");const o={adapters:s.env.data,ctx:s};(u=e.tenants)!=null&&u.beforeCreate&&(i=await e.tenants.beforeCreate(o,i));const a=await s.env.data.tenants.create(i);return(d=e.tenants)!=null&&d.afterCreate&&await e.tenants.afterCreate(o,a),s.json(a,201)}),n.openapi(C.createRoute({tags:["tenants"],method:"delete",path:"/{id}",request:{params:C.z.object({id:C.z.string()})},security:[{Bearer:["delete:tenants"]}],responses:{204:{description:"Tenant deleted"},403:{description:"Access denied or cannot delete the control plane"},404:{description:"Tenant not found"}}}),async s=>{var a,u;const{id:r}=s.req.valid("param");if(t.accessControl){const d=s.var.user,c=t.accessControl.controlPlaneTenantId;if(!(d!=null&&d.sub))throw new S(401,{message:"Authentication required"});if(r===c)throw new S(403,{message:"Cannot delete the control plane"});if(!(await P.fetchAll(m=>s.env.data.userOrganizations.listUserOrganizations(c,d.sub,m),"organizations")).some(m=>m.name===r))throw new S(403,{message:"Access denied to this tenant"})}if(!await s.env.data.tenants.get(r))throw new S(404,{message:"Tenant not found"});const o={adapters:s.env.data,ctx:s};return(a=e.tenants)!=null&&a.beforeDelete&&await e.tenants.beforeDelete(o,r),await s.env.data.tenants.remove(r),(u=e.tenants)!=null&&u.afterDelete&&await e.tenants.afterDelete(o,r),s.body(null,204)}),n}function ce(t){const e=[{pattern:/\/api\/v2\/resource-servers\/([^/]+)$/,type:"resource_server"},{pattern:/\/api\/v2\/roles\/([^/]+)$/,type:"role"},{pattern:/\/api\/v2\/connections\/([^/]+)$/,type:"connection"}];for(const{pattern:n,type:s}of e){const r=t.match(n);if(r&&r[1])return{type:s,id:r[1]}}return null}async function le(t,e,n){try{switch(n.type){case"resource_server":{const s=await t.resourceServers.get(e,n.id);return(s==null?void 0:s.is_system)===!0}case"role":{const s=await t.roles.get(e,n.id);return(s==null?void 0:s.is_system)===!0}case"connection":{const s=await t.connections.get(e,n.id);return(s==null?void 0:s.is_system)===!0}default:return!1}}catch{return!1}}function de(t){return{resource_server:"resource server",role:"role",connection:"connection"}[t]}function W(){return async(t,e)=>{if(!["PATCH","PUT","DELETE"].includes(t.req.method))return e();const n=ce(t.req.path);if(!n)return e();const s=t.var.tenant_id||t.req.header("x-tenant-id")||t.req.header("tenant-id");if(!s)return e();if(await le(t.env.data,s,n))throw new S(403,{message:`This ${de(n.type)} is a system resource and cannot be modified. Make changes in the control plane instead.`});return e()}}function F(t,e){const{controlPlaneTenantId:n,controlPlaneClientId:s}=e;return{...t,legacyClients:{...t.legacyClients,get:async r=>{var p;const i=await t.legacyClients.get(r);if(!i)return null;const o=s?await t.legacyClients.get(s):void 0,a=await t.connections.list(i.tenant.id),u=n?await t.connections.list(n):{connections:[]},d=a.connections.map(l=>{var g;const m=(g=u.connections)==null?void 0:g.find(f=>f.name===l.name);if(!(m!=null&&m.options))return l;const w=I.connectionSchema.parse({...m||{},...l});return w.options=I.connectionOptionsSchema.parse({...m.options||{},...l.options}),w}).filter(l=>l),c={...(o==null?void 0:o.tenant)||{},...i.tenant};return!i.tenant.audience&&((p=o==null?void 0:o.tenant)!=null&&p.audience)&&(c.audience=o.tenant.audience),{...i,web_origins:[...(o==null?void 0:o.web_origins)||[],...i.web_origins||[]],allowed_logout_urls:[...(o==null?void 0:o.allowed_logout_urls)||[],...i.allowed_logout_urls||[]],callbacks:[...(o==null?void 0:o.callbacks)||[],...i.callbacks||[]],connections:d,tenant:c}}},connections:{...t.connections,get:async(r,i)=>{const o=await t.connections.get(r,i);if(!o||!n)return o;const a=await t.connections.get(n,i);if(!a)return o;const u=I.connectionSchema.parse({...a,...o});return u.options=I.connectionOptionsSchema.parse({...a.options||{},...o.options}),u},list:async(r,i)=>{const o=await t.connections.list(r,i);if(!n||r===n)return o;const a=await t.connections.list(n),u=o.connections.map(d=>{var l;const c=(l=a.connections)==null?void 0:l.find(m=>m.name===d.name);if(!(c!=null&&c.options))return d;const p=I.connectionSchema.parse({...c,...d});return p.options=I.connectionOptionsSchema.parse({...c.options||{},...d.options}),p});return{...o,connections:u}}}}}function Q(t,e){return F(t,e)}const ue=F,me=Q;function J(t){return async(e,n)=>{if(!t.accessControl)return n();const{controlPlaneTenantId:s}=t.accessControl,r=e.var.org_name,i=e.var.organization_id,o=r||i;let a=e.var.tenant_id;const u=e.var.user,c=(u!=null&&u.aud?Array.isArray(u.aud)?u.aud:[u.aud]:[]).includes(P.MANAGEMENT_API_AUDIENCE);if(!a&&o&&c&&(e.set("tenant_id",o),a=o),!a)throw new S(400,{message:"Tenant ID not found in request"});if(!L(i,a,s,r))throw new S(403,{message:`Access denied to tenant ${a}`});return n()}}function X(t){return async(e,n)=>{if(!t.subdomainRouting)return n();const{baseDomain:s,reservedSubdomains:r=[],resolveSubdomain:i}=t.subdomainRouting,o=e.req.header("host")||"";let a=null;if(o.endsWith(s)){const d=o.slice(0,-(s.length+1));d&&!d.includes(".")&&(a=d)}if(a&&r.includes(a)&&(a=null),!a)return t.accessControl&&e.set("tenant_id",t.accessControl.controlPlaneTenantId),n();let u=null;if(i)u=await i(a);else if(t.subdomainRouting.useOrganizations!==!1&&t.accessControl)try{const d=await e.env.data.organizations.get(t.accessControl.controlPlaneTenantId,a);d&&(u=d.id)}catch{}if(!u)throw new S(404,{message:`Tenant not found for subdomain: ${a}`});return e.set("tenant_id",u),n()}}function Y(t){return async(e,n)=>{if(!t.databaseIsolation)return n();const s=e.var.tenant_id;if(!s)throw new S(400,{message:"Tenant ID not found in request"});try{const r=await t.databaseIsolation.getAdapters(s);e.env.data=r}catch(r){throw console.error(`Failed to resolve database for tenant ${s}:`,r),new S(500,{message:"Failed to resolve tenant database"})}return n()}}function E(t){const e=X(t),n=J(t),s=Y(t);return async(r,i)=>(await e(r,async()=>{}),await n(r,async()=>{}),await s(r,async()=>{}),i())}function pe(t){const{dataAdapter:e,controlPlaneTenantId:n="control_plane",sync:s={resourceServers:!0,roles:!0,connections:!0},defaultPermissions:r=["tenant:admin"],requireOrganizationMatch:i=!1,managementApiExtensions:o=[],entityHooks:a,getChildTenantIds:u,getAdapters:d,...c}=t,p=s!==!1,l=p?{resourceServers:s.resourceServers??!0,roles:s.roles??!0,connections:s.connections??!0}:{resourceServers:!1,roles:!1,connections:!1},g={controlPlaneTenantId:n,getChildTenantIds:u??(async()=>(await P.fetchAll(v=>e.tenants.list(v),"tenants",{cursorField:"id",pageSize:100})).filter(v=>v.id!==n).map(v=>v.id)),getAdapters:d??(async()=>e),getControlPlaneAdapters:async()=>e,sync:l},{entityHooks:f,tenantHooks:h}=V(g),y={resourceServers:[f.resourceServers,...(a==null?void 0:a.resourceServers)??[]],roles:[f.roles,...(a==null?void 0:a.roles)??[]],connections:[f.connections,...(a==null?void 0:a.connections)??[]],tenants:(a==null?void 0:a.tenants)??[],rolePermissions:(a==null?void 0:a.rolePermissions)??[]},_=$({accessControl:{controlPlaneTenantId:n,requireOrganizationMatch:i,defaultPermissions:r}},{tenants:h}),{app:T}=P.init({dataAdapter:e,...c,entityHooks:y,managementApiExtensions:[...o,{path:"/tenants",router:_}]});return p&&T.use("/api/v2/*",W()),{app:T,controlPlaneTenantId:n}}function fe(t){const e=R(t);return{name:"multi-tenancy",middleware:E(t),hooks:e,routes:[{path:"/management",handler:$(t,e)}],onRegister:async()=>{console.log("Multi-tenancy plugin registered"),t.accessControl&&console.log(` - Access control enabled (control plane: ${t.accessControl.controlPlaneTenantId})`),t.subdomainRouting&&console.log(` - Subdomain routing enabled (base domain: ${t.subdomainRouting.baseDomain})`),t.databaseIsolation&&console.log(" - Database isolation enabled")}}}function R(t){const e=t.accessControl?U(t.accessControl):{},n=t.databaseIsolation?B(t.databaseIsolation):{},s=K(t);return{...e,...n,tenants:s}}function Z(t){const e=new ne.Hono,n=R(t);return e.route("/tenants",$(t,n)),e}function ge(t){return{hooks:R(t),middleware:E(t),app:Z(t),config:t}}exports.createAccessControlHooks=U;exports.createAccessControlMiddleware=J;exports.createDatabaseHooks=B;exports.createDatabaseMiddleware=Y;exports.createMultiTenancy=Z;exports.createMultiTenancyHooks=R;exports.createMultiTenancyMiddleware=E;exports.createMultiTenancyPlugin=fe;exports.createProtectSyncedMiddleware=W;exports.createProvisioningHooks=K;exports.createRuntimeFallbackAdapter=F;exports.createSettingsInheritanceAdapter=ue;exports.createSubdomainMiddleware=X;exports.createSyncHooks=V;exports.createTenantsOpenAPIRouter=$;exports.initMultiTenant=pe;exports.setupMultiTenancy=ge;exports.validateTenantAccess=L;exports.withRuntimeFallback=Q;exports.withSettingsInheritance=me;
1
+ "use strict";var x=Object.defineProperty;var ee=(t,e,n)=>e in t?x(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var $=(t,e,n)=>ee(t,typeof e!="symbol"?e+"":e,n);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const te=require("hono"),I=require("authhero"),_=require("@hono/zod-openapi"),S=require("@authhero/adapter-interfaces");function G(t){const{controlPlaneTenantId:e,requireOrganizationMatch:n=!0}=t;return{async onTenantAccessValidation(a,r){if(r===e)return!0;if(n){const i=a.var.org_name,o=a.var.organization_id,s=i||o;return s?s.toLowerCase()===r.toLowerCase():!1}return!0}}}function U(t,e,n,a){if(e===n)return!0;const r=a||t;return r?r.toLowerCase()===e.toLowerCase():!1}function L(t){return{async resolveDataAdapters(e){try{return await t.getAdapters(e)}catch(n){console.error(`Failed to resolve data adapters for tenant ${e}:`,n);return}}}}function ne(t){return`urn:authhero:tenant:${t.toLowerCase()}`}function B(t){return{async beforeCreate(e,n){return!n.audience&&n.id?{...n,audience:ne(n.id)}:n},async afterCreate(e,n){const{accessControl:a,databaseIsolation:r}=t;a&&e.ctx&&await ae(e,n,a),r!=null&&r.onProvision&&await r.onProvision(n.id)},async beforeDelete(e,n){const{accessControl:a,databaseIsolation:r}=t;if(a)try{const o=(await e.adapters.organizations.list(a.controlPlaneTenantId)).organizations.find(s=>s.name===n);o&&await e.adapters.organizations.remove(a.controlPlaneTenantId,o.id)}catch(i){console.warn(`Failed to remove organization for tenant ${n}:`,i)}if(r!=null&&r.onDeprovision)try{await r.onDeprovision(n)}catch(i){console.warn(`Failed to deprovision database for tenant ${n}:`,i)}}}}async function ae(t,e,n){const{controlPlaneTenantId:a,defaultPermissions:r,defaultRoles:i,issuer:o,adminRoleName:s="Tenant Admin",adminRoleDescription:c="Full access to all tenant management operations",addCreatorToOrganization:m=!0}=n,l=await t.adapters.organizations.create(a,{name:e.id,display_name:e.friendly_name||e.id});let f;if(o&&(f=await re(t,a,s,c)),m&&t.ctx){const d=t.ctx.var.user;if(d!=null&&d.sub&&!await se(t,a,d.sub))try{await t.adapters.userOrganizations.create(a,{user_id:d.sub,organization_id:l.id}),f&&await t.adapters.userRoles.create(a,d.sub,f,l.id)}catch(p){console.warn(`Failed to add creator ${d.sub} to organization ${l.id}:`,p)}}i&&i.length>0&&console.log(`Would assign roles ${i.join(", ")} to organization ${l.id}`),r&&r.length>0&&console.log(`Would grant permissions ${r.join(", ")} to organization ${l.id}`)}async function se(t,e,n){const a=await t.adapters.userRoles.list(e,n,void 0,"");for(const r of a)if((await t.adapters.rolePermissions.list(e,r.id,{per_page:1e3})).some(s=>s.permission_name==="admin:organizations"))return!0;return!1}async function re(t,e,n,a){const i=(await t.adapters.roles.list(e,{})).roles.find(m=>m.name===n);if(i)return i.id;const o=await t.adapters.roles.create(e,{name:n,description:a}),s=I.MANAGEMENT_API_AUDIENCE,c=I.MANAGEMENT_API_SCOPES.map(m=>({role_id:o.id,resource_server_identifier:s,permission_name:m.value}));return await t.adapters.rolePermissions.assign(e,o.id,c),o.id}function j(t,e,n=()=>!0){const{controlPlaneTenantId:a,getChildTenantIds:r,getAdapters:i}=t,o=new Map;async function s(l,f,d){return(await e(l).list(f,{q:`name:${d}`,per_page:1}))[0]??null}async function c(l){const f=await r(),d=e(await i(a));await Promise.all(f.map(async u=>{try{const p=await i(u),g=e(p),w={...d.transform(l),is_system:!0},C=await s(p,u,l.name),v=C?g.getId(C):void 0;if(C&&v){const y=g.preserveOnUpdate?g.preserveOnUpdate(C,w):w;await g.update(u,v,y)}else await g.create(u,w)}catch(p){console.error(`Failed to sync ${d.listKey} "${l.name}" to tenant "${u}":`,p)}}))}async function m(l){const f=await r();await Promise.all(f.map(async d=>{try{const u=await i(d),p=e(u),g=await s(u,d,l),h=g?p.getId(g):void 0;g&&h&&await p.remove(d,h)}catch(u){console.error(`Failed to delete entity "${l}" from tenant "${d}":`,u)}}))}return{afterCreate:async(l,f)=>{l.tenantId===a&&n(f)&&await c(f)},afterUpdate:async(l,f,d)=>{l.tenantId===a&&n(d)&&await c(d)},beforeDelete:async(l,f)=>{if(l.tenantId!==a)return;const u=await e(l.adapters).get(l.tenantId,f);u&&n(u)&&o.set(f,u)},afterDelete:async(l,f)=>{if(l.tenantId!==a)return;const d=o.get(f);d&&(o.delete(f),await m(d.name))}}}function E(t,e,n=()=>!0){const{controlPlaneTenantId:a,getControlPlaneAdapters:r,getAdapters:i}=t;return{async afterCreate(o,s){if(s.id!==a)try{const c=await r(),m=await i(s.id),l=e(c),f=e(m),d=await I.fetchAll(u=>l.listPaginated(a,u),l.listKey,{cursorField:"id",pageSize:100});await Promise.all(d.filter(u=>n(u)).map(async u=>{try{const p=l.transform(u);await f.create(s.id,{...p,is_system:!0})}catch(p){console.error(`Failed to sync entity to new tenant "${s.id}":`,p)}}))}catch(c){console.error(`Failed to sync entities to new tenant "${s.id}":`,c)}}}}const N=t=>({list:async(e,n)=>(await t.resourceServers.list(e,n)).resource_servers,listPaginated:(e,n)=>t.resourceServers.list(e,n),get:(e,n)=>t.resourceServers.get(e,n),create:(e,n)=>t.resourceServers.create(e,n),update:(e,n,a)=>t.resourceServers.update(e,n,a),remove:(e,n)=>t.resourceServers.remove(e,n),listKey:"resource_servers",getId:e=>e.id,transform:e=>({id:e.id,name:e.name,identifier:e.identifier,scopes:e.scopes,signing_alg:e.signing_alg,token_lifetime:e.token_lifetime,token_lifetime_for_web:e.token_lifetime_for_web})}),H=t=>({list:async(e,n)=>(await t.roles.list(e,n)).roles,listPaginated:(e,n)=>t.roles.list(e,n),get:(e,n)=>t.roles.get(e,n),create:(e,n)=>t.roles.create(e,n),update:(e,n,a)=>t.roles.update(e,n,a),remove:(e,n)=>t.roles.remove(e,n),listKey:"roles",getId:e=>e.id,transform:e=>({id:e.id,name:e.name,description:e.description})});function K(t){const{sync:e={},filters:n={}}=t,a=e.resourceServers??!0,r=e.roles??!0,i=a?j(t,N,n.resourceServers):void 0,o=r?j(t,H,n.roles):void 0,s=a?E(t,N,n.resourceServers):void 0,c=r?E(t,H,n.roles):void 0,m=r?{async afterCreate(d,u){var p;if(u.id!==t.controlPlaneTenantId){await((p=c==null?void 0:c.afterCreate)==null?void 0:p.call(c,d,u));try{const g=await t.getControlPlaneAdapters(),h=await t.getAdapters(u.id),w=await I.fetchAll(v=>g.roles.list(t.controlPlaneTenantId,v),"roles",{cursorField:"id",pageSize:100}),C=new Map;for(const v of w.filter(y=>{var T;return((T=n.roles)==null?void 0:T.call(n,y))??!0})){const y=await l(h,u.id,v.name);y&&C.set(v.name,y.id)}for(const v of w.filter(y=>{var T;return((T=n.roles)==null?void 0:T.call(n,y))??!0})){const y=C.get(v.name);if(y)try{const T=await g.rolePermissions.list(t.controlPlaneTenantId,v.id,{});T.length>0&&await h.rolePermissions.assign(u.id,y,T.map(A=>({role_id:y,resource_server_identifier:A.resource_server_identifier,permission_name:A.permission_name})))}catch(T){console.error(`Failed to sync permissions for role "${v.name}" to tenant "${u.id}":`,T)}}}catch(g){console.error(`Failed to sync role permissions to tenant "${u.id}":`,g)}}}}:void 0;async function l(d,u,p){return(await d.roles.list(u,{q:`name:${p}`,per_page:1})).roles[0]??null}return{entityHooks:{resourceServers:i,roles:o},tenantHooks:{async afterCreate(d,u){const p=[s==null?void 0:s.afterCreate,(m==null?void 0:m.afterCreate)??(c==null?void 0:c.afterCreate)],g=[];for(const h of p)if(h)try{await h(d,u)}catch(w){g.push(w instanceof Error?w:new Error(String(w)))}if(g.length===1)throw g[0];if(g.length>1)throw new AggregateError(g,g.map(h=>h.message).join("; "))}}}}var b=class extends Error{constructor(e=500,n){super(n==null?void 0:n.message,{cause:n==null?void 0:n.cause});$(this,"res");$(this,"status");this.res=n==null?void 0:n.res,this.status=e}getResponse(){return this.res?new Response(this.res.body,{status:this.status,headers:this.res.headers}):new Response(this.message,{status:this.status})}};function M(t,e){const n=new _.OpenAPIHono;return n.openapi(_.createRoute({tags:["tenants"],method:"get",path:"/",request:{query:S.auth0QuerySchema},security:[{Bearer:[]}],responses:{200:{content:{"application/json":{schema:_.z.object({tenants:_.z.array(S.tenantSchema),start:_.z.number().optional(),limit:_.z.number().optional(),length:_.z.number().optional()})}},description:"List of tenants"}}}),async a=>{var u,p,g,h;const r=a.req.valid("query"),{page:i,per_page:o,include_totals:s,q:c}=r,m=a.var.user,l=(m==null?void 0:m.permissions)||[];if(l.includes("auth:read")||l.includes("admin:organizations")){const w=await a.env.data.tenants.list({page:i,per_page:o,include_totals:s,q:c});return s?a.json({tenants:w.tenants,start:((u=w.totals)==null?void 0:u.start)??0,limit:((p=w.totals)==null?void 0:p.limit)??o,length:w.tenants.length}):a.json({tenants:w.tenants})}if(t.accessControl&&(m!=null&&m.sub)){const w=t.accessControl.controlPlaneTenantId,v=(await I.fetchAll(P=>a.env.data.userOrganizations.listUserOrganizations(w,m.sub,P),"organizations")).map(P=>P.name);if(v.length===0)return s?a.json({tenants:[],start:0,limit:o??50,length:0}):a.json({tenants:[]});const y=v.length,T=i??0,A=o??50,z=T*A,D=v.slice(z,z+A);if(D.length===0)return s?a.json({tenants:[],start:z,limit:A,length:y}):a.json({tenants:[]});const F=D.map(P=>`id:${P}`).join(" OR "),Z=c?`(${F}) AND (${c})`:F,k=await a.env.data.tenants.list({q:Z,per_page:A,include_totals:!1});return s?a.json({tenants:k.tenants,start:z,limit:A,length:y}):a.json({tenants:k.tenants})}const d=await a.env.data.tenants.list({page:i,per_page:o,include_totals:s,q:c});return s?a.json({tenants:d.tenants,start:((g=d.totals)==null?void 0:g.start)??0,limit:((h=d.totals)==null?void 0:h.limit)??o,length:d.tenants.length}):a.json({tenants:d.tenants})}),n.openapi(_.createRoute({tags:["tenants"],method:"post",path:"/",request:{body:{content:{"application/json":{schema:S.tenantInsertSchema}}}},security:[{Bearer:[]}],responses:{201:{content:{"application/json":{schema:S.tenantSchema}},description:"Tenant created"},400:{description:"Validation error"},409:{description:"Tenant with this ID already exists"}}}),async a=>{var c,m;const r=a.var.user;if(!(r!=null&&r.sub))throw new b(401,{message:"Authentication required to create tenants"});let i=a.req.valid("json");const o={adapters:a.env.data,ctx:a};(c=e.tenants)!=null&&c.beforeCreate&&(i=await e.tenants.beforeCreate(o,i));const s=await a.env.data.tenants.create(i);return(m=e.tenants)!=null&&m.afterCreate&&await e.tenants.afterCreate(o,s),a.json(s,201)}),n.openapi(_.createRoute({tags:["tenants"],method:"delete",path:"/{id}",request:{params:_.z.object({id:_.z.string()})},security:[{Bearer:["delete:tenants"]}],responses:{204:{description:"Tenant deleted"},403:{description:"Access denied or cannot delete the control plane"},404:{description:"Tenant not found"}}}),async a=>{var s,c;const{id:r}=a.req.valid("param");if(t.accessControl){const m=a.var.user,l=t.accessControl.controlPlaneTenantId;if(!(m!=null&&m.sub))throw new b(401,{message:"Authentication required"});if(r===l)throw new b(403,{message:"Cannot delete the control plane"});if(!(await I.fetchAll(u=>a.env.data.userOrganizations.listUserOrganizations(l,m.sub,u),"organizations")).some(u=>u.name===r))throw new b(403,{message:"Access denied to this tenant"})}if(!await a.env.data.tenants.get(r))throw new b(404,{message:"Tenant not found"});const o={adapters:a.env.data,ctx:a};return(s=e.tenants)!=null&&s.beforeDelete&&await e.tenants.beforeDelete(o,r),await a.env.data.tenants.remove(r),(c=e.tenants)!=null&&c.afterDelete&&await e.tenants.afterDelete(o,r),a.body(null,204)}),n}function oe(t){const e=[{pattern:/\/api\/v2\/resource-servers\/([^/]+)$/,type:"resource_server"},{pattern:/\/api\/v2\/roles\/([^/]+)$/,type:"role"},{pattern:/\/api\/v2\/connections\/([^/]+)$/,type:"connection"}];for(const{pattern:n,type:a}of e){const r=t.match(n);if(r&&r[1])return{type:a,id:r[1]}}return null}async function ie(t,e,n){try{switch(n.type){case"resource_server":{const a=await t.resourceServers.get(e,n.id);return(a==null?void 0:a.is_system)===!0}case"role":{const a=await t.roles.get(e,n.id);return(a==null?void 0:a.is_system)===!0}case"connection":{const a=await t.connections.get(e,n.id);return(a==null?void 0:a.is_system)===!0}default:return!1}}catch{return!1}}function ce(t){return{resource_server:"resource server",role:"role",connection:"connection"}[t]}function W(){return async(t,e)=>{if(!["PATCH","PUT","DELETE"].includes(t.req.method))return e();const n=oe(t.req.path);if(!n)return e();const a=t.var.tenant_id||t.req.header("x-tenant-id")||t.req.header("tenant-id");if(!a)return e();if(await ie(t.env.data,a,n))throw new b(403,{message:`This ${ce(n.type)} is a system resource and cannot be modified. Make changes in the control plane instead.`});return e()}}function O(t,e){const{controlPlaneTenantId:n,controlPlaneClientId:a}=e;return{...t,legacyClients:{...t.legacyClients,get:async r=>{var f;const i=await t.legacyClients.get(r);if(!i)return null;const o=a?await t.legacyClients.get(a):void 0,s=await t.connections.list(i.tenant.id),c=n?await t.connections.list(n):{connections:[]},m=s.connections.map(d=>{var g;const u=(g=c.connections)==null?void 0:g.find(h=>h.strategy===d.strategy);if(!(u!=null&&u.options))return d;const p=S.connectionSchema.parse({...u||{},...d});return p.options=S.connectionOptionsSchema.parse({...u.options||{},...d.options}),p}).filter(d=>d),l={...(o==null?void 0:o.tenant)||{},...i.tenant};return!i.tenant.audience&&((f=o==null?void 0:o.tenant)!=null&&f.audience)&&(l.audience=o.tenant.audience),{...i,web_origins:[...(o==null?void 0:o.web_origins)||[],...i.web_origins||[]],allowed_logout_urls:[...(o==null?void 0:o.allowed_logout_urls)||[],...i.allowed_logout_urls||[]],callbacks:[...(o==null?void 0:o.callbacks)||[],...i.callbacks||[]],connections:m,tenant:l}}},connections:{...t.connections,get:async(r,i)=>{var l;const o=await t.connections.get(r,i);if(!o||!n||r===n)return o;const c=(l=(await t.connections.list(n)).connections)==null?void 0:l.find(f=>f.strategy===o.strategy);if(!(c!=null&&c.options))return o;const m=S.connectionSchema.parse({...c,...o});return m.options=S.connectionOptionsSchema.parse({...c.options||{},...o.options}),m},list:async(r,i)=>{const o=await t.connections.list(r,i);if(!n||r===n)return o;const s=await t.connections.list(n),c=o.connections.map(m=>{var d;const l=(d=s.connections)==null?void 0:d.find(u=>u.strategy===m.strategy);if(!(l!=null&&l.options))return m;const f=S.connectionSchema.parse({...l,...m});return f.options=S.connectionOptionsSchema.parse({...l.options||{},...m.options}),f});return{...o,connections:c}}}}}function Q(t,e){return O(t,e)}const le=O,de=Q;function V(t){return async(e,n)=>{if(!t.accessControl)return n();const{controlPlaneTenantId:a}=t.accessControl,r=e.var.org_name,i=e.var.organization_id,o=r||i;let s=e.var.tenant_id;const c=e.var.user,l=(c!=null&&c.aud?Array.isArray(c.aud)?c.aud:[c.aud]:[]).includes(I.MANAGEMENT_API_AUDIENCE);if(!s&&o&&l&&(e.set("tenant_id",o),s=o),!s)throw new b(400,{message:"Tenant ID not found in request"});if(!U(i,s,a,r))throw new b(403,{message:`Access denied to tenant ${s}`});return n()}}function J(t){return async(e,n)=>{if(!t.subdomainRouting)return n();const{baseDomain:a,reservedSubdomains:r=[],resolveSubdomain:i}=t.subdomainRouting,o=e.req.header("host")||"";let s=null;if(o.endsWith(a)){const m=o.slice(0,-(a.length+1));m&&!m.includes(".")&&(s=m)}if(s&&r.includes(s)&&(s=null),!s)return t.accessControl&&e.set("tenant_id",t.accessControl.controlPlaneTenantId),n();let c=null;if(i)c=await i(s);else if(t.subdomainRouting.useOrganizations!==!1&&t.accessControl)try{const m=await e.env.data.organizations.get(t.accessControl.controlPlaneTenantId,s);m&&(c=m.id)}catch{}if(!c)throw new b(404,{message:`Tenant not found for subdomain: ${s}`});return e.set("tenant_id",c),n()}}function X(t){return async(e,n)=>{if(!t.databaseIsolation)return n();const a=e.var.tenant_id;if(!a)throw new b(400,{message:"Tenant ID not found in request"});try{const r=await t.databaseIsolation.getAdapters(a);e.env.data=r}catch(r){throw console.error(`Failed to resolve database for tenant ${a}:`,r),new b(500,{message:"Failed to resolve tenant database"})}return n()}}function q(t){const e=J(t),n=V(t),a=X(t);return async(r,i)=>(await e(r,async()=>{}),await n(r,async()=>{}),await a(r,async()=>{}),i())}function ue(t){const{dataAdapter:e,controlPlaneTenantId:n="control_plane",sync:a={resourceServers:!0,roles:!0},defaultPermissions:r=["tenant:admin"],requireOrganizationMatch:i=!1,managementApiExtensions:o=[],entityHooks:s,getChildTenantIds:c,getAdapters:m,...l}=t,f=a!==!1,d=f?{resourceServers:a.resourceServers??!0,roles:a.roles??!0}:{resourceServers:!1,roles:!1},g={controlPlaneTenantId:n,getChildTenantIds:c??(async()=>(await I.fetchAll(A=>e.tenants.list(A),"tenants",{cursorField:"id",pageSize:100})).filter(A=>A.id!==n).map(A=>A.id)),getAdapters:m??(async()=>e),getControlPlaneAdapters:async()=>e,sync:d},{entityHooks:h,tenantHooks:w}=K(g),C={resourceServers:[h.resourceServers,...(s==null?void 0:s.resourceServers)??[]],roles:[h.roles,...(s==null?void 0:s.roles)??[]],connections:(s==null?void 0:s.connections)??[],tenants:(s==null?void 0:s.tenants)??[],rolePermissions:(s==null?void 0:s.rolePermissions)??[]},v=M({accessControl:{controlPlaneTenantId:n,requireOrganizationMatch:i,defaultPermissions:r}},{tenants:w}),{app:y}=I.init({dataAdapter:e,...l,entityHooks:C,managementApiExtensions:[...o,{path:"/tenants",router:v}]});return f&&y.use("/api/v2/*",W()),{app:y,controlPlaneTenantId:n}}function me(t){const e=R(t);return{name:"multi-tenancy",middleware:q(t),hooks:e,routes:[{path:"/management",handler:M(t,e)}],onRegister:async()=>{console.log("Multi-tenancy plugin registered"),t.accessControl&&console.log(` - Access control enabled (control plane: ${t.accessControl.controlPlaneTenantId})`),t.subdomainRouting&&console.log(` - Subdomain routing enabled (base domain: ${t.subdomainRouting.baseDomain})`),t.databaseIsolation&&console.log(" - Database isolation enabled")}}}function R(t){const e=t.accessControl?G(t.accessControl):{},n=t.databaseIsolation?L(t.databaseIsolation):{},a=B(t);return{...e,...n,tenants:a}}function Y(t){const e=new te.Hono,n=R(t);return e.route("/tenants",M(t,n)),e}function fe(t){return{hooks:R(t),middleware:q(t),app:Y(t),config:t}}exports.createAccessControlHooks=G;exports.createAccessControlMiddleware=V;exports.createDatabaseHooks=L;exports.createDatabaseMiddleware=X;exports.createMultiTenancy=Y;exports.createMultiTenancyHooks=R;exports.createMultiTenancyMiddleware=q;exports.createMultiTenancyPlugin=me;exports.createProtectSyncedMiddleware=W;exports.createProvisioningHooks=B;exports.createRuntimeFallbackAdapter=O;exports.createSettingsInheritanceAdapter=le;exports.createSubdomainMiddleware=J;exports.createSyncHooks=K;exports.createTenantsOpenAPIRouter=M;exports.initMultiTenant=ue;exports.setupMultiTenancy=fe;exports.validateTenantAccess=U;exports.withRuntimeFallback=Q;exports.withSettingsInheritance=de;
@@ -40678,12 +40678,11 @@ export interface MultiTenantConfig extends Omit<AuthHeroConfig, "entityHooks" |
40678
40678
  /**
40679
40679
  * Control which entities to sync from control plane to child tenants.
40680
40680
  * Set to `false` to disable all syncing.
40681
- * @default { resourceServers: true, roles: true, connections: true }
40681
+ * @default { resourceServers: true, roles: true }
40682
40682
  */
40683
40683
  sync?: {
40684
40684
  resourceServers?: boolean;
40685
40685
  roles?: boolean;
40686
- connections?: boolean;
40687
40686
  } | false;
40688
40687
  /**
40689
40688
  * Default permissions to grant when creating a tenant organization.
@@ -40730,7 +40729,7 @@ export interface MultiTenantResult {
40730
40729
  * Initialize a multi-tenant AuthHero application with sensible defaults.
40731
40730
  *
40732
40731
  * This is the easiest way to set up multi-tenancy. It automatically:
40733
- * - Creates sync hooks for resource servers, roles, and connections
40732
+ * - Creates sync hooks for resource servers and roles
40734
40733
  * - Mounts the tenants management API at `/tenants`
40735
40734
  * - Adds middleware to protect synced entities on child tenants
40736
40735
  * - Sets up organization-based access control
@@ -40760,8 +40759,7 @@ export interface MultiTenantResult {
40760
40759
  * controlPlaneTenantId: "main",
40761
40760
  * sync: {
40762
40761
  * resourceServers: true,
40763
- * roles: true,
40764
- * connections: false, // Don't sync connections
40762
+ * roles: false, // Don't sync roles
40765
40763
  * },
40766
40764
  * defaultPermissions: ["tenant:admin", "tenant:read"],
40767
40765
  * });
@@ -40798,11 +40796,11 @@ export interface EntitySyncConfig {
40798
40796
  getControlPlaneAdapters: () => Promise<DataAdapters>;
40799
40797
  /**
40800
40798
  * Which entities to sync. All default to true.
40799
+ * Note: Connections are NOT synced - they use runtime fallback by strategy instead.
40801
40800
  */
40802
40801
  sync?: {
40803
40802
  resourceServers?: boolean;
40804
40803
  roles?: boolean;
40805
- connections?: boolean;
40806
40804
  };
40807
40805
  /**
40808
40806
  * Optional filters for each entity type
@@ -40810,27 +40808,33 @@ export interface EntitySyncConfig {
40810
40808
  filters?: {
40811
40809
  resourceServers?: (entity: ResourceServer) => boolean;
40812
40810
  roles?: (entity: Role) => boolean;
40813
- connections?: (entity: Connection) => boolean;
40814
40811
  };
40815
40812
  }
40816
40813
  /**
40817
40814
  * Result from createSyncHooks containing all entity and tenant hooks
40815
+ *
40816
+ * Note: Connections are NOT synced - they use runtime fallback by strategy instead.
40817
+ * Tenants can define a connection with a strategy (e.g., "google") and leave keys blank,
40818
+ * and the system will fallback to the control plane's connection with the same strategy.
40818
40819
  */
40819
40820
  export interface SyncHooksResult {
40820
40821
  entityHooks: {
40821
40822
  resourceServers?: EntityHooks<ResourceServer, ResourceServerInsert>;
40822
40823
  roles?: EntityHooks<Role, RoleInsert>;
40823
- connections?: EntityHooks<Connection, ConnectionInsert>;
40824
40824
  };
40825
40825
  tenantHooks: TenantEntityHooks;
40826
40826
  }
40827
40827
  /**
40828
- * Creates all sync hooks for resource servers, roles, and connections.
40828
+ * Creates all sync hooks for resource servers and roles.
40829
40829
  *
40830
40830
  * This is the main entry point for entity synchronization. It creates hooks that:
40831
40831
  * - Sync changes from control plane to all child tenants (create, update, delete)
40832
40832
  * - Copy entities to newly created tenants
40833
40833
  *
40834
+ * Note: Connections are NOT synced. Instead, they use runtime fallback by strategy.
40835
+ * Tenants can define a connection with a strategy (e.g., "google") and leave keys blank,
40836
+ * and the system will fallback to the control plane's connection with the same strategy.
40837
+ *
40834
40838
  * @param config - Sync configuration
40835
40839
  * @returns Object with entityHooks and tenantHooks ready to use
40836
40840
  *
@@ -40841,7 +40845,7 @@ export interface SyncHooksResult {
40841
40845
  * getChildTenantIds: async () => ["tenant1", "tenant2"],
40842
40846
  * getAdapters: async () => dataAdapter,
40843
40847
  * getControlPlaneAdapters: async () => dataAdapter,
40844
- * sync: { resourceServers: true, roles: true, connections: true },
40848
+ * sync: { resourceServers: true, roles: true },
40845
40849
  * });
40846
40850
  * ```
40847
40851
  */
@@ -40927,9 +40931,11 @@ export interface RuntimeFallbackConfig {
40927
40931
  * - **Client URL fallbacks**: Add control plane URLs to allowed origins, callbacks, etc.
40928
40932
  * - **Tenant property defaults**: Fall back to control plane values for missing tenant settings
40929
40933
  *
40930
- * **Key difference from entity sync:**
40931
- * - **Runtime fallback**: Values are merged at query time, sensitive data stays in control plane only
40932
- * - **Entity sync**: Entities are copied to child tenants, needed for foreign key relationships
40934
+ * **Connection fallback by strategy:**
40935
+ * Connections are matched by **strategy** (e.g., "google", "facebook", "email") rather than by name.
40936
+ * This allows tenants to create a connection with a strategy like "google" and leave keys blank,
40937
+ * and the system will automatically merge in the OAuth credentials from the control plane's
40938
+ * google connection. This is the recommended approach for sharing social auth across tenants.
40933
40939
  *
40934
40940
  * @param baseAdapters - The base data adapters to wrap
40935
40941
  * @param config - Configuration for runtime settings fallback