@aiquants/auth-directory-drizzle 0.3.0 → 0.4.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/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{AUTHZ_ACTIONS as be}from"@aiquants/authz-core";var ze=(n,a)=>`\u5916\u90E8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u9023\u643A\u30B0\u30EB\u30FC\u30D7 (${a}) \u306B\u306F\u7BA1\u7406\u30ED\u30FC\u30EB (${n}) \u3092\u6301\u305F\u305B\u3089\u308C\u307E\u305B\u3093\u3002\u4E0A\u6D41\u5074\u306E\u6240\u5C5E\u5909\u66F4\u304C\u305D\u306E\u307E\u307E\u7BA1\u7406\u8005\u306E\u5897\u6E1B\u306B\u306A\u308B\u305F\u3081\u3001\u7BA1\u7406\u8005\u7D4C\u8DEF\u306F\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u30B0\u30EB\u30FC\u30D7\u306B\u9650\u5B9A\u3057\u3066\u3044\u307E\u3059\u3002`;function J(n,a,m,y){return new Set(n.filter(d=>d.roleId===a&&d.appKey===m&&d.resourceKey===y).map(d=>d.action))}var V=n=>be.every(a=>n.has(a)),Y=(n,a)=>n.find(m=>m.roleId===a)?.roleKey??`#${a}`;function Be(n,a){let m=n;async function y(d,i,p){if(i.size===0)return;let g=await m.listGroupRoles(d),b=new Set;for(let t of g){if(!i.has(t.roleId)||b.has(t.groupId))continue;b.add(t.groupId);let c=await a.linkLookup.externalGroupKey(d,t.groupId);if(c!==null)throw new Error(a.message(t.roleKey||Y(p,t.roleId),c))}}return{...n,async assignGroupRole(d,i,p,g){let b=await m.listGrants(d);if(V(J(b,p,a.adminAppKey,a.adminResourceKey))){let t=await a.linkLookup.externalGroupKey(d,i);if(t!==null)throw new Error(a.message(Y(b,p),t))}return n.assignGroupRole(d,i,p,g)},async createGrant(d,i,p){let b=(await m.listResources(d)).find(t=>t.id===i.resourceId);if(b?.appKey===a.adminAppKey&&b?.resourceKey===a.adminResourceKey){let t=await m.listGrants(d),c=J(t,i.roleId,a.adminAppKey,a.adminResourceKey);c.add(i.action),V(c)&&await y(d,new Set([i.roleId]),t)}return n.createGrant(d,i,p)}}}async function Oe(n,a,m,y){let d=(await n.listGroupRoles(a)).filter(p=>p.groupId===m);if(d.length===0)return;let i=await n.listGrants(a);for(let p of d)if(V(J(i,p.roleId,y.adminAppKey,y.adminResourceKey)))throw new Error(y.message(p.roleKey||Y(i,p.roleId),p.groupKey))}import{and as fe,eq as q}from"drizzle-orm";async function j(n,a,m){let{ledger:y,group:d}=a,{tenantId:i,externalUserId:p,email:g}=m;if(p===null&&g==="")return[];let b={ledgerId:y.id,groupId:y.groupId,tenantId:d.tenantId,externalUserId:y.externalUserId},t=async s=>(await n.select(b).from(y).innerJoin(d,fe(q(d.id,y.groupId),q(d.tenantId,i),q(d.isActive,!0))).where(s)).map(f=>({ledgerId:Number(f.ledgerId),groupId:Number(f.groupId),tenantId:String(f.tenantId),externalUserId:f.externalUserId===null?null:String(f.externalUserId)})),c=s=>{let S=new Map;for(let f of s)S.set(f.ledgerId,{ledgerId:f.ledgerId,groupId:f.groupId,tenantId:f.tenantId});return[...S.values()].sort((f,A)=>f.ledgerId-A.ledgerId)},e=p===null?[]:await t(q(y.externalUserId,p));if(g==="")return c(e);let u=await t(q(y.email,g)),I=s=>p!==null&&s.externalUserId!==null&&s.externalUserId!==p;return c([...e,...u.filter(s=>!I(s))])}import{normalizeAuthEmail as W}from"@aiquants/auth-core";import{assertTenantMembershipPolicy as we}from"@aiquants/auth-directory-core";import{assertTenantId as M,assertTenantMatch as Ie}from"@aiquants/authz-core";import{and as D,asc as ie,count as X,eq as h,isNull as se}from"drizzle-orm";var oe=["direct","transitive"];function z(n){if(!oe.includes(n))throw new TypeError(`[@aiquants/auth-directory-drizzle] membershipMode must be one of ${oe.join(" | ")}, received ${JSON.stringify(n)}`);return n}function Ze(n,a,m,y){let d=n,i=y.ensureConnection??(async()=>{}),p=y.policyCacheTtlMs??0;if(!Number.isFinite(p)||p<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] policyCacheTtlMs must be a non-negative number, received ${String(y.policyCacheTtlMs)}`);let g=new Map,b=r=>g.delete(r),{TDGroupExternalLink:t,TDGroupMemberEmail:c,TDTenantMembershipGroup:e,TDAllowlistGroup:u,TDDirectorySyncRequest:I}=a,{TMGroup:s,TDGroupMember:S}=m;async function f(r,o,l){if((await r.select({groupId:e.groupId}).from(e).where(D(h(e.tenantId,o),h(e.groupId,l),h(e.isBreakGlass,!0)))).length>0)throw new Error(`Group ${l} is a break-glass proof group; a break-glass proof path must be one the synchronization can never touch.`)}async function A(r,o,l){let[w]=await r.select({id:s.id}).from(s).where(D(h(s.id,l),h(s.tenantId,o)));if(!w)throw new Error(`Group not found in tenant: ${l}`)}function k(r,o){return{linkId:Number(r.linkId),groupId:Number(r.groupId),tenantId:String(r.tenantId),provider:String(r.provider),externalId:String(r.externalId),externalKey:String(r.externalKey),membershipMode:z(String(r.membershipMode)),isSyncEnabled:!!r.isSyncEnabled,...r.syncedAt?{syncedAt:new Date(r.syncedAt).toISOString()}:{},...r.syncError?{syncError:String(r.syncError)}:{},unresolvedCount:o}}async function _(r,o){let l=await r.select({groupId:c.groupId,n:X()}).from(c).innerJoin(s,D(h(s.id,c.groupId),h(s.tenantId,o))).where(se(c.resolvedUserId)).groupBy(c.groupId);return new Map(l.map(w=>[Number(w.groupId),Number(w.n)]))}return{async listLinks(r){M(r,"listLinks(tenantId)"),await i();let o=await d.select({linkId:t.id,groupId:t.groupId,tenantId:t.tenantId,provider:t.provider,externalId:t.externalId,externalKey:t.externalKey,membershipMode:t.membershipMode,isSyncEnabled:t.isSyncEnabled,syncedAt:t.syncedAt,syncError:t.syncError}).from(t).where(h(t.tenantId,r)).orderBy(ie(t.groupId)),l=await _(d,r);return o.map(w=>(Ie(r,String(w.tenantId),"listLinks(row.tenantId)"),k(w,l.get(Number(w.groupId))??0)))},async externalGroupKey(r,o){M(r,"externalGroupKey(tenantId)"),await i();let[l]=await d.select({externalKey:t.externalKey}).from(t).where(D(h(t.groupId,o),h(t.tenantId,r)));return l?String(l.externalKey):null},async linkGroup(r,o,l,w){M(r,"linkGroup(tenantId)");let v=z(l.membershipMode),G=l.externalId.trim(),E=W(l.externalKey);if(G===""||E==="")throw new TypeError("[@aiquants/auth-directory-drizzle] externalId and externalKey must be non-empty");await i(),await y.linkable.assertLinkable(r,o),await d.transaction(async U=>{await A(U,r,o),await f(U,r,o),await U.insert(t).output().values({groupId:o,tenantId:r,provider:l.provider,externalId:G,externalKey:E,membershipMode:v,isSyncEnabled:!0,vanishedObservations:0,createdAt:new Date,createdBy:w})}),b(r)},async unlinkGroup(r,o){M(r,"unlinkGroup(tenantId)"),await i(),await d.transaction(async l=>{await A(l,r,o),await l.delete(c).where(h(c.groupId,o)),await l.delete(u).where(D(h(u.groupId,o),h(u.tenantId,r))),await l.delete(t).where(D(h(t.groupId,o),h(t.tenantId,r)))}),b(r)},async setSyncEnabled(r,o,l,w){M(r,"setSyncEnabled(tenantId)"),await i(),await d.update(t).set({isSyncEnabled:l,updatedAt:new Date,updatedBy:w}).output().where(D(h(t.groupId,o),h(t.tenantId,r)))},async requestSync(r,o,l){M(r,"requestSync(tenantId)"),await i(),await d.transaction(async w=>{o!==null&&await A(w,r,o),await w.insert(I).output().values({tenantId:r,groupId:o,requestedBy:l,requestedAt:new Date,createdAt:new Date,createdBy:l})})},async getStatus(r){M(r,"getStatus(tenantId)"),await i();let o=await d.select({groupId:t.groupId,isSyncEnabled:t.isSyncEnabled,syncedAt:t.syncedAt,syncError:t.syncError}).from(t).where(h(t.tenantId,r)),[l]=await d.select({n:X()}).from(I).where(D(h(I.tenantId,r),se(I.finishedAt))),w=o.filter(R=>!!R.isSyncEnabled),v=w.map(R=>R.syncedAt?new Date(R.syncedAt).getTime():null),G=v.length>0&&v.every(R=>R!==null)?Math.min(...v):null,U=o.length>0&&w.length===0||w.length>0&&(G===null||Date.now()-G>y.stalenessThresholdSec*1e3);return{isEnabled:y.isEnabled,linkedGroupCount:o.length,...G!==null?{oldestSyncedAt:new Date(G).toISOString()}:{},isStale:U,pausedGroupCount:o.length-w.length,stalenessThresholdSec:y.stalenessThresholdSec,erroredGroupIds:o.filter(R=>!!R.syncError).map(R=>Number(R.groupId)).sort((R,he)=>R-he),pendingRequestCount:Number(l?.n??0)}},async loadTenantMembershipPolicy(r){M(r,"loadTenantMembershipPolicy(tenantId)");let o=p>0?g.get(r):void 0;if(o&&Date.now()-o.loadedAt<p)return o.policy;await i();let l=await d.select({groupId:e.groupId,isBreakGlass:e.isBreakGlass,externalLinkId:t.id}).from(e).innerJoin(s,D(h(s.id,e.groupId),h(s.tenantId,r))).leftJoin(t,h(t.groupId,e.groupId)).where(h(e.tenantId,r));if(l.length===0){let E={kind:"no-proof-required"};return p>0&&g.set(r,{policy:E,loadedAt:Date.now()}),E}let w=l.map(E=>Number(E.groupId)),v=l.filter(E=>!!E.isBreakGlass);for(let E of v)if(E.externalLinkId!==null)throw new Error(`Break-glass group ${Number(E.groupId)} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`);let G={kind:"require-proof",proofGroupIds:w,breakGlassGroupIds:v.map(E=>Number(E.groupId))};return we(G,`tenant ${r}`),p>0&&g.set(r,{policy:G,loadedAt:Date.now()}),G},async setTenantMembershipGroup(r,o,l,w){M(r,"setTenantMembershipGroup(tenantId)"),await i(),await d.transaction(async v=>{if(await A(v,r,o),l){let[E]=await v.select({id:t.id}).from(t).where(D(h(t.tenantId,r),h(t.groupId,o)));if(E)throw new Error(`Group ${o} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`)}if((await v.select({groupId:e.groupId}).from(e).where(D(h(e.tenantId,r),h(e.groupId,o)))).length>0){await v.update(e).set({isBreakGlass:l,updatedAt:new Date,updatedBy:w}).output().where(D(h(e.tenantId,r),h(e.groupId,o))),b(r);return}await v.insert(e).output().values({tenantId:r,groupId:o,isBreakGlass:l,createdAt:new Date,createdBy:w})}),b(r)},async removeTenantMembershipGroup(r,o){M(r,"removeTenantMembershipGroup(tenantId)"),await i(),await d.transaction(async l=>{let v=(await l.select({groupId:e.groupId,isBreakGlass:e.isBreakGlass}).from(e).where(h(e.tenantId,r))).filter(G=>Number(G.groupId)!==o);if(v.length>0&&!v.some(G=>!!G.isBreakGlass))throw new Error(`Removing group ${o} would leave tenant ${r} with a membership proof but no break-glass path.`);await l.delete(e).where(D(h(e.tenantId,r),h(e.groupId,o)))}),b(r)},async setGroupActive(r,o,l,w){M(r,"setGroupActive(tenantId)"),await i(),await d.update(s).set({isActive:l,updatedAt:new Date,updatedBy:w}).output().where(D(h(s.id,o),h(s.tenantId,r)))},async listLedgerGroupIds(r,o){if(M(r,"listLedgerGroupIds(tenantId)"),!y.isEnabled)return[];await i();let l=await j(d,{ledger:c,group:s},{tenantId:r,externalUserId:o.externalUserId?.trim()||null,email:W(o.email)});return[...new Set(l.map(w=>w.groupId))].sort((w,v)=>w-v)},async isEmailAllowedByGroup(r){if(!y.isEnabled)return!1;let o=W(r);if(o==="")return!1;await i();let[l]=await d.select({id:c.id}).from(c).innerJoin(u,h(u.groupId,c.groupId)).innerJoin(s,D(h(s.id,c.groupId),h(s.isActive,!0))).where(h(c.email,o));return!!l},async listAllowlistGroups(r){return M(r,"listAllowlistGroups(tenantId)"),await i(),(await d.select({id:u.id,groupId:u.groupId,groupKey:s.groupKey,groupName:s.name,description:u.description}).from(u).innerJoin(s,D(h(s.id,u.groupId),h(s.tenantId,r))).where(h(u.tenantId,r)).orderBy(ie(s.name))).map(l=>({id:Number(l.id),type:"group",groupId:Number(l.groupId),groupKey:String(l.groupKey),groupName:String(l.groupName),...l.description?{description:String(l.description)}:{},owner:{kind:"tenant",tenantId:r},readOnly:!1}))},async addAllowlistGroup(r,o,l,w){M(r,"addAllowlistGroup(tenantId)"),await i(),await d.transaction(async v=>{await A(v,r,o);let[G]=await v.select({id:t.id}).from(t).where(D(h(t.groupId,o),h(t.tenantId,r)));if(!G)throw new Error(`Group ${o} is locally managed; only externally-sourced groups can gate sign-in, because the decision reads the upstream member ledger.`);await v.insert(u).output().values({tenantId:r,groupId:o,description:l??null,createdAt:new Date,createdBy:w})})},async removeAllowlistGroup(r,o){M(r,"removeAllowlistGroup(tenantId)"),await i(),await d.delete(u).where(D(h(u.id,o),h(u.tenantId,r)))},async countProjectedMembers(r,o){M(r,"countProjectedMembers(tenantId)"),await i();let[l]=await d.select({n:X()}).from(S).innerJoin(s,D(h(s.id,S.groupId),h(s.tenantId,r))).where(h(S.groupId,o));return Number(l?.n??0)}}}import{normalizeAuthEmail as Se}from"@aiquants/auth-core";import{assertTenantId as Ae,assertTenantMatch as ve}from"@aiquants/authz-core";async function nr(n,a){let m=Ae(a.tenantId,"promoteLedgerForUser(tenantId)");if(!Number.isInteger(a.userId)||a.userId<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] userId must be a positive integer, received ${String(a.userId)}`);let y=Se(a.email),d=a.externalUserId?.trim()||null;if(d===null&&y==="")return{addedGroupIds:[],matchedLedgerIds:[]};let i=await n.findLedgerMatches({tenantId:m,externalUserId:d,email:y});if(i.length===0)return{addedGroupIds:[],matchedLedgerIds:[]};for(let t of i)ve(m,t.tenantId,"promoteLedgerForUser(findLedgerMatches row)");let p=[...new Set(i.map(t=>t.ledgerId))].sort((t,c)=>t-c),g=[...new Set(i.map(t=>t.groupId))].sort((t,c)=>t-c);return{addedGroupIds:[...await n.attachUserToGroups({tenantId:m,userId:a.userId,ledgerIds:p,groupIds:g})].sort((t,c)=>t-c),matchedLedgerIds:p}}import{assertTenantId as de}from"@aiquants/authz-core";import{and as Q,eq as Z,inArray as F}from"drizzle-orm";var ae=2100;var ir=500;function L(n,a=500){if(!Number.isInteger(a)||a<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] chunk size must be a positive integer, received ${String(a)}`);let m=[];for(let y=0;y<n.length;y+=a)m.push(n.slice(y,y+a));return m}function B(n,a,m=100){if(!Number.isInteger(a)||a<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] parametersPerItem must be a positive integer, received ${String(a)}`);if(!Number.isInteger(m)||m<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] reservedParameters must be a non-negative integer, received ${String(m)}`);let y=2100-m,d=Math.floor(y/a);if(d<1)throw new TypeError(`[@aiquants/auth-directory-drizzle] a single item binds ${a} parameters, which exceeds the ${y} available in one statement`);return L(n,d)}var Te=4,De=100;function pr(n,a,m,y){let d=n,i=y.ensureConnection??(async()=>{}),p=y.now??(()=>new Date),g=a.TDGroupMemberEmail,{TMGroup:b,TDGroupMember:t}=m;return{async findLedgerMatches(c){return de(c.tenantId,"findLedgerMatches(tenantId)"),await i(),j(d,{ledger:g,group:b},c)},async attachUserToGroups({tenantId:c,userId:e,ledgerIds:u,groupIds:I}){return de(c,"attachUserToGroups(tenantId)"),await i(),u.length===0?[]:d.transaction(async s=>{let S=new Set;for(let r of L(I)){let o=await s.select({id:b.id}).from(b).where(Q(F(b.id,r),Z(b.tenantId,c),Z(b.isActive,!0)));for(let l of o)S.add(Number(l.id))}let f=I.filter(r=>S.has(r));if(f.length===0)return[];let A=new Set;for(let r of L(f)){let o=await s.select({groupId:t.groupId}).from(t).where(Q(Z(t.userId,e),F(t.groupId,r)));for(let l of o)A.add(Number(l.groupId))}let k=f.filter(r=>!A.has(r)),_=Math.max(1,2100-De-f.length);for(let r of L(u,_))await s.update(g).set({resolvedUserId:e}).output().where(Q(F(g.id,r),F(g.groupId,f)));if(k.length>0){let r=p();for(let o of B(k,Te))await s.insert(t).output().values(o.map(l=>({groupId:l,userId:e,createdAt:r,createdBy:y.actor})))}return k})}}}import{AUTHZ_TENANT_COLUMN as O}from"@aiquants/authz-drizzle";import{bigint as P,bit as ue,datetime2 as $,foreignKey as H,int as xe,mssqlSchema as Ge,nvarchar as T,primaryKey as N,uniqueIndex as C}from"drizzle-orm/mssql-core";var ke="provider",gr=["TDGroupExternalLink","TDTenantMembershipGroup","TDAllowlistGroup","TDDirectorySyncRequest","TMAllowlist"];function hr(n,a){let m=Ge(n),y=()=>({createdAt:$("created_at"),createdBy:T("created_by",{length:50}),updatedAt:$("updated_at"),updatedBy:T("updated_by",{length:50})}),d=()=>({createdAt:$("created_at"),createdBy:T("created_by",{length:50})}),i=m.table("TDGroupExternalLink",{id:P("id",{mode:"number"}).identity().notNull(),groupId:P("group_id",{mode:"number"}).notNull(),tenantId:T(O,{length:64}).notNull(),provider:T(ke,{length:32}).notNull(),externalId:T("external_id",{length:256}).notNull(),externalKey:T("external_key",{length:320}).notNull(),membershipMode:T("membership_mode",{length:16}).notNull(),isSyncEnabled:ue("is_sync_enabled").notNull(),syncedAt:$("synced_at"),syncError:T("sync_error",{length:1e3}),vanishedObservations:xe("vanished_observations").notNull(),...y()},e=>[N({name:`${n}_TDGroupExternalLink_pk`,columns:[e.id]}),C(`${n}_TDGroupExternalLink_group_uq`).on(e.groupId),C(`${n}_TDGroupExternalLink_external_uq`).on(e.tenantId,e.provider,e.externalId),H({name:`${n}_TDGroupExternalLink_group_id_fk`,columns:[e.groupId],foreignColumns:[a.groupTable.id]})]),p=m.table("TDGroupMemberEmail",{id:P("id",{mode:"number"}).identity().notNull(),groupId:P("group_id",{mode:"number"}).notNull(),email:T("email",{length:320}).notNull(),externalUserId:T("external_user_id",{length:128}),resolvedUserId:P("resolved_user_id",{mode:"number"}),observedAt:$("observed_at").notNull(),...y()},e=>[N({name:`${n}_TDGroupMemberEmail_pk`,columns:[e.id]}),C(`${n}_TDGroupMemberEmail_group_email_uq`).on(e.groupId,e.email),H({name:`${n}_TDGroupMemberEmail_group_id_fk`,columns:[e.groupId],foreignColumns:[a.groupTable.id]})]),g=m.table("TDTenantMembershipGroup",{tenantId:T(O,{length:64}).notNull(),groupId:P("group_id",{mode:"number"}).notNull(),isBreakGlass:ue("is_break_glass").notNull(),...d()},e=>[N({name:`${n}_TDTenantMembershipGroup_pk`,columns:[e.tenantId,e.groupId]}),H({name:`${n}_TDTenantMembershipGroup_group_id_fk`,columns:[e.groupId],foreignColumns:[a.groupTable.id]})]),b=m.table("TDAllowlistGroup",{id:P("id",{mode:"number"}).identity().notNull(),tenantId:T(O,{length:64}).notNull(),groupId:P("group_id",{mode:"number"}).notNull(),description:T("description",{length:512}),...d()},e=>[N({name:`${n}_TDAllowlistGroup_pk`,columns:[e.id]}),C(`${n}_TDAllowlistGroup_tenant_group_uq`).on(e.tenantId,e.groupId),H({name:`${n}_TDAllowlistGroup_group_id_fk`,columns:[e.groupId],foreignColumns:[a.groupTable.id]})]),t=m.table("TDDirectorySyncRequest",{id:P("id",{mode:"number"}).identity().notNull(),tenantId:T(O,{length:64}).notNull(),groupId:P("group_id",{mode:"number"}),requestedBy:T("requested_by",{length:320}).notNull(),requestedAt:$("requested_at").notNull(),pickedAt:$("picked_at"),finishedAt:$("finished_at"),result:T("result",{length:32}),error:T("error",{length:2e3}),...d()},e=>[N({name:`${n}_TDDirectorySyncRequest_pk`,columns:[e.id]})]),c=m.table("TMAllowlist",{id:P("id",{mode:"number"}).identity().notNull(),tenantId:T(O,{length:64}).notNull(),pattern:T("pattern",{length:256}).notNull(),type:T("type",{length:32}).notNull(),description:T("description",{length:512}),...d()},e=>[N({name:`${n}_TMAllowlist_pk`,columns:[e.id]}),C(`${n}_TMAllowlist_pattern_uq`).on(e.tenantId,e.pattern)]);return{TDGroupExternalLink:i,TDGroupMemberEmail:p,TDTenantMembershipGroup:g,TDAllowlistGroup:b,TDDirectorySyncRequest:t,TMAllowlist:c}}import{assertBlastRadiusPolicy as Ee,reconcile as Me}from"@aiquants/auth-directory-core";var ee=(n,a=20)=>n.length<=a?n.join(", "):`${n.slice(0,a).join(", ")} \u307B\u304B ${n.length-a} \u4EF6`,le={upstreamUnreadable:n=>`\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${n}`,localWriteFailed:n=>`\u30ED\u30FC\u30AB\u30EB\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${n}`,resolverIncomplete:(n,a)=>`\u4E0A\u6D41\u304C ${n} \u4EF6\u306E\u30E1\u30F3\u30D0\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F\u304C\u3001\u7A81\u5408\u7D50\u679C\u306F ${a} \u4EF6\u3067\u3057\u305F\u3002\u4EF6\u6570\u304C\u6E1B\u308B\u7A81\u5408\u306F\u3001\u89E3\u6C7A\u3067\u304D\u306A\u304B\u3063\u305F\u4EBA\u7269\u3092\u300C\u4E0A\u6D41\u304B\u3089\u6D88\u3048\u305F\u300D\u3068\u3057\u3066\u524A\u9664\u3057\u3066\u3057\u307E\u3046\u305F\u3081\u4E2D\u65AD\u3057\u307E\u3057\u305F\u3002\u89E3\u6C7A\u3067\u304D\u306A\u3044\u30E1\u30F3\u30D0\u30FC\u306F\u7701\u7565\u305B\u305A resolvedUserId: null \u3067\u8FD4\u3059\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`,groupMissing:(n,a,m)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (${n})\u3002\u9023\u7D9A ${a} \u56DE\u76EE\u3067\u3059\u3002${m} \u56DE\u306B\u9054\u3057\u305F\u6642\u70B9\u3067\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3059\u3002`,groupVanished:(n,a)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C ${a} \u56DE\u9023\u7D9A\u3067\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F (${n})\u3002\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3057\u305F\u3002\u4E0A\u6D41\u304C\u5FA9\u65E7\u3059\u308C\u3070\u6B21\u56DE\u306E\u540C\u671F\u3067\u81EA\u52D5\u7684\u306B\u518D\u6709\u52B9\u5316\u3055\u308C\u307E\u3059\u3002`,planRefused:n=>{if(n.reason==="invalid-upstream-data")return`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: \u4E0A\u6D41\u306E\u30E1\u30F3\u30D0\u30FC\u4E00\u89A7\u306B\u6271\u3048\u306A\u3044\u30A2\u30C9\u30EC\u30B9\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u3059 (${ee(n.offendingEmails)})\u3002`;let a=n.axis==="membership"?"\u6A29\u9650\u3092\u5931\u3046\u4EBA\u6570":"\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u306A\u304F\u306A\u308B\u4EBA\u6570",m=n.axis==="membership"?n.withheldProjectionRemoves.map(String):n.withheldLedgerDeletes;return[`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: ${a}\u304C ${n.population} \u4EBA\u4E2D ${n.removalCount} \u4EBA\u306B\u306A\u308A\u307E\u3059`,`(\u4E0A\u9650: ${n.limits.maxRemovalCount} \u4EF6 / \u6BD4\u7387 ${n.limits.maxRemovalRatio})\u3002`,`\u5DEE\u3057\u6B62\u3081\u305F\u5BFE\u8C61: ${ee(m)}\u3002`,"\u4E0A\u6D41\u5074\u306E\u8A2D\u5B9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u610F\u56F3\u3057\u305F\u5909\u66F4\u3067\u3042\u308C\u3070\u3001\u3053\u306E\u8EF8\u306E\u4E0A\u9650\u3060\u3051\u3092\u4E00\u6642\u7684\u306B\u5F15\u304D\u4E0A\u3052\u3066\u518D\u5B9F\u884C\u3057\u307E\u3059\u3002"].join(" ")},removalsRefused:n=>`${n.length} \u4EF6\u306E\u6240\u5C5E\u89E3\u9664\u304C\u6700\u5F8C\u306E\u7BA1\u7406\u8005\u30AC\u30FC\u30C9\u306B\u3088\u308A\u62D2\u5426\u3055\u308C\u307E\u3057\u305F (\u30E6\u30FC\u30B6\u30FC ${ee(n.map(a=>String(a.userId)))})\u3002\u6B8B\u308A\u306E\u5909\u66F4\u306F\u9069\u7528\u6E08\u307F\u3067\u3059\u3002`,foreignProvider:(n,a)=>`\u3053\u306E\u30EA\u30F3\u30AF\u306E\u4F9B\u7D66\u5143\u306F ${n} \u3067\u3059\u304C\u3001\u5B9F\u884C\u4E2D\u306E\u540C\u671F\u306F ${a} \u7528\u3067\u3059\u3002\u5BFE\u5FDC\u3059\u308B\u540C\u671F\u30B8\u30E7\u30D6\u304B\u3089\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002`};var pe=["aborted","missing","vanished","upstream-failed","local-failed","foreign-provider"];function vr(n){let{directory:a,repository:m,policy:y,now:d,logger:i}=n,p=n.messages??le;if(Ee(y),!Number.isInteger(n.vanishedThreshold)||n.vanishedThreshold<1)throw new TypeError(`[@aiquants/auth-directory-drizzle] vanishedThreshold must be a positive integer, received ${String(n.vanishedThreshold)}`);if(typeof n.provider!="string"||n.provider.trim()==="")throw new TypeError("[@aiquants/auth-directory-drizzle] provider must be a non-empty string so links of other providers can be told apart");async function g(e,u){try{await m.recordFailure(e,u)}catch(I){i.error(`[auth-directory] could not record the failure for ${e.externalKey}:`,I)}}async function b(e){try{let u=await a.getGroup(e.externalId);return u===null?{kind:"missing"}:{kind:"ok",group:u,members:await a.listGroupMembers(u,e.membershipMode)}}catch(u){return{kind:"failed",error:u instanceof Error?u:new Error(String(u))}}}async function t(e){let u=await m.recordVanishedObservation(e,p.groupMissing(e.externalKey,e.vanishedObservations+1,n.vanishedThreshold));return u<n.vanishedThreshold?(i.warn(`[auth-directory] ${e.externalKey} missing upstream (${u}/${n.vanishedThreshold})`),{kind:"missing",link:e,observations:u}):(await m.markGroupInactive(e),await g(e,p.groupVanished(e.externalKey,u)),i.warn(`[auth-directory] ${e.externalKey} deactivated after ${u} consecutive absences`),{kind:"vanished",link:e})}async function c(e){if(e.provider!==n.provider)return await g(e,p.foreignProvider(e.provider,n.provider)),i.warn(`[auth-directory] skipping ${e.externalKey}: belongs to provider ${e.provider}`),{kind:"foreign-provider",link:e};let u=await b(e);if(u.kind==="missing")return t(e);if(u.kind==="failed")return await g(e,p.upstreamUnreadable(u.error.message)),i.error(`[auth-directory] upstream read failed for ${e.externalKey}:`,u.error),{kind:"upstream-failed",link:e,error:u.error};try{let[I,s]=await Promise.all([m.loadGroupSnapshot(e),m.resolveMembers(u.members)]);if(s.length!==u.members.length){let _=p.resolverIncomplete(u.members.length,s.length);return await g(e,_),i.error(`[auth-directory] ${_}`),{kind:"local-failed",link:e,error:new Error(_)}}let S=Me(I,s,y);if(S.aborted)return await g(e,p.planRefused(S.aborted)),i.warn(`[auth-directory] plan refused for ${e.externalKey}: ${S.aborted.reason}`),{kind:"aborted",link:e,plan:S};let f=S.ledgerUpserts.length>0||S.ledgerDeletes.length>0||S.projectionAdds.length>0||S.projectionRemoves.length>0,A=d(),k={refusals:[]};return f&&(k=await m.applyPlan(e,S,A)),await m.recordSuccess(e,u.group,A),k.refusals.length>0&&(await g(e,p.removalsRefused(k.refusals)),i.warn(`[auth-directory] ${k.refusals.length} removal(s) refused for ${e.externalKey}`)),S.unresolvedEmails.length>0&&i.info(`[auth-directory] ${e.externalKey}: ${S.unresolvedEmails.length} upstream member(s) have no local user yet`),f?{kind:"applied",link:e,plan:S}:{kind:"unchanged",link:e}}catch(I){let s=I instanceof Error?I:new Error(String(I));return await g(e,p.localWriteFailed(s.message)),i.error(`[auth-directory] local write failed for ${e.externalKey}:`,s),{kind:"local-failed",link:e,error:s}}}return{syncOne:c,async run(e={}){let u=await m.listSyncableLinks(e),I=[];for(let S of u)try{I.push(await c(S))}catch(f){let A=f instanceof Error?f:new Error(String(f));i.error(`[auth-directory] ${S.externalKey} ended abnormally:`,A),I.push({kind:"local-failed",link:S,error:A})}let s=I.filter(S=>pe.includes(S.kind)).length;return i.info(`[auth-directory] run finished: ${I.length} link(s), ${s} unhealthy`),{outcomes:I,unhealthyCount:s}}}}import{assertTenantId as Re}from"@aiquants/authz-core";import{and as Pe,asc as ce,eq as me,isNull as re}from"drizzle-orm";var te=2e3;function Gr(n,a,m){let y=n,d=m.ensureConnection??(async()=>{}),i=a.TDDirectorySyncRequest,p=m.maxErrorLength??te;if(!Number.isInteger(p)||p<1||p>te)throw new TypeError(`[@aiquants/auth-directory-drizzle] maxErrorLength must be an integer within 1..${te}, received ${String(m.maxErrorLength)}`);return{async claim(){await d();let g=await y.select({id:i.id}).from(i).where(re(i.pickedAt)).orderBy(ce(i.id));for(let b of g){let c=(await y.update(i).set({pickedAt:m.now()}).output().where(Pe(me(i.id,Number(b.id)),re(i.pickedAt))))?.[0];if(!c)continue;let e=String(c.tenant_id??c.tenantId??""),u=c.group_id??c.groupId??null;return{id:Number(c.id),tenantId:Re(e,"claim(row.tenantId)"),groupId:u===null?null:Number(u),requestedBy:String(c.requested_by??c.requestedBy??"")}}return null},async finish(g,b){await d();let t=b.error?b.error.slice(0,p):null;await y.update(i).set({finishedAt:m.now(),result:b.result,error:t}).output().where(me(i.id,g))},async listStranded(g){return await d(),(await y.select({id:i.id,pickedAt:i.pickedAt}).from(i).where(re(i.finishedAt)).orderBy(ce(i.id))).filter(t=>t.pickedAt!==null&&new Date(t.pickedAt)<g).map(t=>Number(t.id))}}}import{normalizeAuthEmail as ye}from"@aiquants/auth-core";import{assertTenantId as ge,assertTenantMatch as _e}from"@aiquants/authz-core";import{and as K,eq as x,inArray as ne,isNull as Le,notInArray as $e,or as Ke}from"drizzle-orm";var Ne=7,Ue=4;function Lr(n,a,m,y){let d=n,i=y.ensureConnection??(async()=>{}),{TDGroupExternalLink:p,TDGroupMemberEmail:g}=a,{TMGroup:b,TDGroupMember:t}=m,c=y.actor;return{async listSyncableLinks(e){await i();let u=[x(p.isSyncEnabled,!0)];return e.tenantId!==void 0&&u.push(x(p.tenantId,ge(e.tenantId,"listSyncableLinks(tenantId)"))),e.groupId!==void 0&&u.push(x(p.groupId,e.groupId)),(await d.select({linkId:p.id,groupId:p.groupId,linkTenantId:p.tenantId,groupTenantId:b.tenantId,groupKey:b.groupKey,isGroupActive:b.isActive,provider:p.provider,externalId:p.externalId,externalKey:p.externalKey,membershipMode:p.membershipMode,vanishedObservations:p.vanishedObservations}).from(p).innerJoin(b,x(b.id,p.groupId)).where(K(...u))).map(s=>(_e(String(s.groupTenantId),String(s.linkTenantId),"listSyncableLinks(link.tenantId vs TMGroup.tenant_id)"),{linkId:Number(s.linkId),groupId:Number(s.groupId),groupKey:String(s.groupKey),tenantId:String(s.groupTenantId),provider:String(s.provider),externalId:String(s.externalId),externalKey:String(s.externalKey),membershipMode:z(String(s.membershipMode)),isGroupActive:!!s.isGroupActive,vanishedObservations:Number(s.vanishedObservations??0)}))},async loadGroupSnapshot(e){await i();let[u,I]=await Promise.all([d.select({email:g.email,externalUserId:g.externalUserId,resolvedUserId:g.resolvedUserId}).from(g).where(x(g.groupId,e.groupId)),d.select({userId:t.userId}).from(t).where(x(t.groupId,e.groupId))]);return{ledger:u.map(s=>({email:String(s.email),...s.externalUserId?{externalUserId:String(s.externalUserId)}:{},resolvedUserId:s.resolvedUserId===null?null:Number(s.resolvedUserId)})),projectedUserIds:I.map(s=>Number(s.userId))}},resolveMembers(e){return y.memberResolver.resolve(e)},async applyPlan(e,u,I){ge(e.tenantId,"applyPlan(link.tenantId)"),await i(),(u.ledgerUpserts.length>0||u.projectionAdds.length>0)&&await d.transaction(async f=>{let A=u.ledgerUpserts.map(r=>({...r,email:ye(r.email)})),k=new Map;for(let r of L(A.map(o=>o.email))){let o=await f.select({id:g.id,email:g.email}).from(g).where(K(x(g.groupId,e.groupId),ne(g.email,r)));for(let l of o)k.set(String(l.email),Number(l.id))}let _=A.filter(r=>!k.has(r.email));for(let r of B(_,Ne))await f.insert(g).output().values(r.map(o=>({groupId:e.groupId,email:o.email,externalUserId:o.externalUserId??null,resolvedUserId:o.resolvedUserId,observedAt:I,createdAt:I,createdBy:c})));for(let r of A){let o=k.get(r.email);o!==void 0&&await f.update(g).set({externalUserId:r.externalUserId??null,resolvedUserId:r.resolvedUserId,observedAt:I,updatedAt:I,updatedBy:c}).output().where(x(g.id,o))}for(let r of B(u.projectionAdds,Ue)){let o=new Set((await f.select({userId:t.userId}).from(t).where(K(x(t.groupId,e.groupId),ne(t.userId,r)))).map(w=>Number(w.userId))),l=r.filter(w=>!o.has(w));l.length>0&&await f.insert(t).output().values(l.map(w=>({groupId:e.groupId,userId:w,createdAt:I,createdBy:c})))}});let s=[];for(let f of u.projectionRemoves)try{await y.lockoutGuard.guardedWrite(e.tenantId,{removeGroupMembership:{groupId:e.groupId,userId:f}},"remove-group-member",A=>A.delete(t).where(K(x(t.groupId,e.groupId),x(t.userId,f))))}catch(A){s.push({userId:f,applied:!1,refusal:A instanceof Error?A.message:String(A)})}let S=s.map(f=>f.userId);return u.ledgerDeletes.length>0&&await d.transaction(async f=>{for(let A of L(u.ledgerDeletes)){let k=S.length>0?Ke(Le(g.resolvedUserId),$e(g.resolvedUserId,S)):void 0;await f.delete(g).where(K(x(g.groupId,e.groupId),ne(g.email,A),...k?[k]:[]))}}),{refusals:s}},async recordSuccess(e,u,I){await i(),await d.transaction(async s=>{await s.update(p).set({syncedAt:I,syncError:null,vanishedObservations:0,externalId:u.externalId,externalKey:ye(u.externalKey),updatedAt:I,updatedBy:c}).output().where(x(p.id,e.linkId)),e.isGroupActive||await s.update(b).set({isActive:!0,updatedAt:I,updatedBy:c}).output().where(K(x(b.id,e.groupId),x(b.tenantId,e.tenantId)))})},async recordFailure(e,u){await i(),await d.update(p).set({syncError:u.slice(0,1e3),updatedAt:new Date,updatedBy:c}).output().where(x(p.id,e.linkId))},async recordVanishedObservation(e,u){await i();let I=e.vanishedObservations+1;return await d.update(p).set({vanishedObservations:I,syncError:u.slice(0,1e3),updatedAt:new Date,updatedBy:c}).output().where(x(p.id,e.linkId)),I},async markGroupInactive(e){await i(),await d.update(b).set({isActive:!1,updatedAt:new Date,updatedBy:c}).output().where(K(x(b.id,e.groupId),x(b.tenantId,e.tenantId)))}}}export{ke as AUTH_DIRECTORY_PROVIDER_COLUMN,gr as AUTH_DIRECTORY_TENANT_SCOPED_TABLES,ir as MAX_IN_LIST,ae as MAX_PARAMETERS,oe as MEMBERSHIP_MODES,pe as UNHEALTHY_SYNC_OUTCOMES,Oe as assertGroupMayBeLinked,z as assertMembershipMode,L as chunk,B as chunkByParameters,Ze as createDirectoryLinkStore,vr as createDirectorySync,Lr as createDirectorySyncRepository,pr as createPromotionRepository,Gr as createSyncRequestQueue,ze as defaultExternalGroupGuardMessage,le as defaultSyncMessages,hr as defineAuthDirectorySchema,j as findLedgerRowsForIdentity,nr as promoteLedgerForUser,Be as withExternalGroupGuards};
1
+ import{AUTHZ_ACTIONS as be}from"@aiquants/authz-core";var qe=(t,d)=>`\u5916\u90E8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u9023\u643A\u30B0\u30EB\u30FC\u30D7 (${d}) \u306B\u306F\u7BA1\u7406\u30ED\u30FC\u30EB (${t}) \u3092\u6301\u305F\u305B\u3089\u308C\u307E\u305B\u3093\u3002\u4E0A\u6D41\u5074\u306E\u6240\u5C5E\u5909\u66F4\u304C\u305D\u306E\u307E\u307E\u7BA1\u7406\u8005\u306E\u5897\u6E1B\u306B\u306A\u308B\u305F\u3081\u3001\u7BA1\u7406\u8005\u7D4C\u8DEF\u306F\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u30B0\u30EB\u30FC\u30D7\u306B\u9650\u5B9A\u3057\u3066\u3044\u307E\u3059\u3002`;function J(t,d,g,y){return new Set(t.filter(u=>u.roleId===d&&u.appKey===g&&u.resourceKey===y).map(u=>u.action))}var V=t=>be.every(d=>t.has(d)),Y=(t,d)=>t.find(g=>g.roleId===d)?.roleKey??`#${d}`;function Be(t,d){let g=t;async function y(u,i,l,c,b){if(c.size===0)return;let n=await u.listGroupRoles(l),m=new Set;for(let e of n){if(!c.has(e.roleId)||m.has(e.groupId))continue;m.add(e.groupId);let a=await d.linkLookup.externalGroupKey(l,e.groupId,i);if(a!==null)throw new Error(d.message(e.roleKey||Y(b,e.roleId),a))}}return{...t,async assignGroupRole(u,i,l,c){await g.withTransaction(async(b,n)=>{let m=await b.listGrants(u);if(V(J(m,l,d.adminAppKey,d.adminResourceKey))){let e=await d.linkLookup.externalGroupKey(u,i,n);if(e!==null)throw new Error(d.message(Y(m,l),e))}await b.assignGroupRole(u,i,l,c)})},async createGrant(u,i,l){await g.withTransaction(async(c,b)=>{let m=(await c.listResources(u)).find(e=>e.id===i.resourceId);if(m?.appKey===d.adminAppKey&&m?.resourceKey===d.adminResourceKey){let e=await c.listGrants(u),a=J(e,i.roleId,d.adminAppKey,d.adminResourceKey);a.add(i.action),V(a)&&await y(c,b,u,new Set([i.roleId]),e)}await c.createGrant(u,i,l)})}}}async function Oe(t,d,g,y){let u=(await t.listGroupRoles(d)).filter(l=>l.groupId===g);if(u.length===0)return;let i=await t.listGrants(d);for(let l of u)if(V(J(i,l.roleId,y.adminAppKey,y.adminResourceKey)))throw new Error(y.message(l.roleKey||Y(i,l.roleId),l.groupKey))}import{and as we,eq as z}from"drizzle-orm";async function j(t,d,g){let{ledger:y,group:u}=d,{tenantId:i,externalUserId:l,email:c}=g;if(l===null&&c==="")return[];let b={ledgerId:y.id,groupId:y.groupId,tenantId:u.tenantId,externalUserId:y.externalUserId},n=async s=>(await t.select(b).from(y).innerJoin(u,we(z(u.id,y.groupId),z(u.tenantId,i),z(u.isActive,!0))).where(s)).map(f=>({ledgerId:Number(f.ledgerId),groupId:Number(f.groupId),tenantId:String(f.tenantId),externalUserId:f.externalUserId===null?null:String(f.externalUserId)})),m=s=>{let S=new Map;for(let f of s)S.set(f.ledgerId,{ledgerId:f.ledgerId,groupId:f.groupId,tenantId:f.tenantId});return[...S.values()].sort((f,A)=>f.ledgerId-A.ledgerId)},e=l===null?[]:await n(z(y.externalUserId,l));if(c==="")return m(e);let a=await n(z(y.email,c)),I=s=>l!==null&&s.externalUserId!==null&&s.externalUserId!==l;return m([...e,...a.filter(s=>!I(s))])}import{normalizeAuthEmail as W}from"@aiquants/auth-core";import{assertTenantMembershipPolicy as fe}from"@aiquants/auth-directory-core";import{assertTenantId as M,assertTenantMatch as Ie}from"@aiquants/authz-core";import{and as D,asc as ie,count as X,eq as h,isNull as se}from"drizzle-orm";var oe=["direct","transitive"];function q(t){if(!oe.includes(t))throw new TypeError(`[@aiquants/auth-directory-drizzle] membershipMode must be one of ${oe.join(" | ")}, received ${JSON.stringify(t)}`);return t}function Ze(t,d,g,y){let u=t,i=y.ensureConnection??(async()=>{}),l=y.policyCacheTtlMs??0;if(!Number.isFinite(l)||l<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] policyCacheTtlMs must be a non-negative number, received ${String(y.policyCacheTtlMs)}`);let c=new Map,b=r=>c.delete(r),{TDGroupExternalLink:n,TDGroupMemberEmail:m,TDTenantMembershipGroup:e,TDAllowlistGroup:a,TDDirectorySyncRequest:I}=d,{TMGroup:s,TDGroupMember:S}=g;async function f(r,o,p){if((await r.select({groupId:e.groupId}).from(e).where(D(h(e.tenantId,o),h(e.groupId,p),h(e.isBreakGlass,!0)))).length>0)throw new Error(`Group ${p} is a break-glass proof group; a break-glass proof path must be one the synchronization can never touch.`)}async function A(r,o,p){let[w]=await r.select({id:s.id}).from(s).where(D(h(s.id,p),h(s.tenantId,o)));if(!w)throw new Error(`Group not found in tenant: ${p}`)}function G(r,o){return{linkId:Number(r.linkId),groupId:Number(r.groupId),tenantId:String(r.tenantId),provider:String(r.provider),externalId:String(r.externalId),externalKey:String(r.externalKey),membershipMode:q(String(r.membershipMode)),isSyncEnabled:!!r.isSyncEnabled,...r.syncedAt?{syncedAt:new Date(r.syncedAt).toISOString()}:{},...r.syncError?{syncError:String(r.syncError)}:{},unresolvedCount:o}}async function _(r,o){let p=await r.select({groupId:m.groupId,n:X()}).from(m).innerJoin(s,D(h(s.id,m.groupId),h(s.tenantId,o))).where(se(m.resolvedUserId)).groupBy(m.groupId);return new Map(p.map(w=>[Number(w.groupId),Number(w.n)]))}return{async listLinks(r){M(r,"listLinks(tenantId)"),await i();let o=await u.select({linkId:n.id,groupId:n.groupId,tenantId:n.tenantId,provider:n.provider,externalId:n.externalId,externalKey:n.externalKey,membershipMode:n.membershipMode,isSyncEnabled:n.isSyncEnabled,syncedAt:n.syncedAt,syncError:n.syncError}).from(n).where(h(n.tenantId,r)).orderBy(ie(n.groupId)),p=await _(u,r);return o.map(w=>(Ie(r,String(w.tenantId),"listLinks(row.tenantId)"),G(w,p.get(Number(w.groupId))??0)))},async externalGroupKey(r,o,p){M(r,"externalGroupKey(tenantId)"),await i();let[w]=await(p??u).select({externalKey:n.externalKey}).from(n).where(D(h(n.groupId,o),h(n.tenantId,r)));return w?String(w.externalKey):null},async linkGroup(r,o,p,w){M(r,"linkGroup(tenantId)");let v=q(p.membershipMode),k=p.externalId.trim(),E=W(p.externalKey);if(k===""||E==="")throw new TypeError("[@aiquants/auth-directory-drizzle] externalId and externalKey must be non-empty");await i(),await y.linkable.assertLinkable(r,o),await u.transaction(async U=>{await A(U,r,o),await f(U,r,o),await U.insert(n).output().values({groupId:o,tenantId:r,provider:p.provider,externalId:k,externalKey:E,membershipMode:v,isSyncEnabled:!0,vanishedObservations:0,createdAt:new Date,createdBy:w})}),b(r)},async unlinkGroup(r,o){M(r,"unlinkGroup(tenantId)"),await i(),await u.transaction(async p=>{await A(p,r,o),await p.delete(m).where(h(m.groupId,o)),await p.delete(a).where(D(h(a.groupId,o),h(a.tenantId,r))),await p.delete(n).where(D(h(n.groupId,o),h(n.tenantId,r)))}),b(r)},async setSyncEnabled(r,o,p,w){M(r,"setSyncEnabled(tenantId)"),await i(),await u.update(n).set({isSyncEnabled:p,updatedAt:new Date,updatedBy:w}).output().where(D(h(n.groupId,o),h(n.tenantId,r)))},async requestSync(r,o,p){M(r,"requestSync(tenantId)"),await i(),await u.transaction(async w=>{o!==null&&await A(w,r,o),await w.insert(I).output().values({tenantId:r,groupId:o,requestedBy:p,requestedAt:new Date,createdAt:new Date,createdBy:p})})},async getStatus(r){M(r,"getStatus(tenantId)"),await i();let o=await u.select({groupId:n.groupId,isSyncEnabled:n.isSyncEnabled,syncedAt:n.syncedAt,syncError:n.syncError}).from(n).where(h(n.tenantId,r)),[p]=await u.select({n:X()}).from(I).where(D(h(I.tenantId,r),se(I.finishedAt))),w=o.filter(R=>!!R.isSyncEnabled),v=w.map(R=>R.syncedAt?new Date(R.syncedAt).getTime():null),k=v.length>0&&v.every(R=>R!==null)?Math.min(...v):null,U=o.length>0&&w.length===0||w.length>0&&(k===null||Date.now()-k>y.stalenessThresholdSec*1e3);return{isEnabled:y.isEnabled,linkedGroupCount:o.length,...k!==null?{oldestSyncedAt:new Date(k).toISOString()}:{},isStale:U,pausedGroupCount:o.length-w.length,stalenessThresholdSec:y.stalenessThresholdSec,erroredGroupIds:o.filter(R=>!!R.syncError).map(R=>Number(R.groupId)).sort((R,he)=>R-he),pendingRequestCount:Number(p?.n??0)}},async loadTenantMembershipPolicy(r){M(r,"loadTenantMembershipPolicy(tenantId)");let o=l>0?c.get(r):void 0;if(o&&Date.now()-o.loadedAt<l)return o.policy;await i();let p=await u.select({groupId:e.groupId,isBreakGlass:e.isBreakGlass,externalLinkId:n.id}).from(e).innerJoin(s,D(h(s.id,e.groupId),h(s.tenantId,r))).leftJoin(n,h(n.groupId,e.groupId)).where(h(e.tenantId,r));if(p.length===0){let E={kind:"no-proof-required"};return l>0&&c.set(r,{policy:E,loadedAt:Date.now()}),E}let w=p.map(E=>Number(E.groupId)),v=p.filter(E=>!!E.isBreakGlass);for(let E of v)if(E.externalLinkId!==null)throw new Error(`Break-glass group ${Number(E.groupId)} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`);let k={kind:"require-proof",proofGroupIds:w,breakGlassGroupIds:v.map(E=>Number(E.groupId))};return fe(k,`tenant ${r}`),l>0&&c.set(r,{policy:k,loadedAt:Date.now()}),k},async setTenantMembershipGroup(r,o,p,w){M(r,"setTenantMembershipGroup(tenantId)"),await i(),await u.transaction(async v=>{if(await A(v,r,o),p){let[E]=await v.select({id:n.id}).from(n).where(D(h(n.tenantId,r),h(n.groupId,o)));if(E)throw new Error(`Group ${o} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`)}if((await v.select({groupId:e.groupId}).from(e).where(D(h(e.tenantId,r),h(e.groupId,o)))).length>0){await v.update(e).set({isBreakGlass:p,updatedAt:new Date,updatedBy:w}).output().where(D(h(e.tenantId,r),h(e.groupId,o))),b(r);return}await v.insert(e).output().values({tenantId:r,groupId:o,isBreakGlass:p,createdAt:new Date,createdBy:w})}),b(r)},async removeTenantMembershipGroup(r,o){M(r,"removeTenantMembershipGroup(tenantId)"),await i(),await u.transaction(async p=>{let v=(await p.select({groupId:e.groupId,isBreakGlass:e.isBreakGlass}).from(e).where(h(e.tenantId,r))).filter(k=>Number(k.groupId)!==o);if(v.length>0&&!v.some(k=>!!k.isBreakGlass))throw new Error(`Removing group ${o} would leave tenant ${r} with a membership proof but no break-glass path.`);await p.delete(e).where(D(h(e.tenantId,r),h(e.groupId,o)))}),b(r)},async setGroupActive(r,o,p,w){M(r,"setGroupActive(tenantId)"),await i(),await u.update(s).set({isActive:p,updatedAt:new Date,updatedBy:w}).output().where(D(h(s.id,o),h(s.tenantId,r)))},async listLedgerGroupIds(r,o){if(M(r,"listLedgerGroupIds(tenantId)"),!y.isEnabled)return[];await i();let p=await j(u,{ledger:m,group:s},{tenantId:r,externalUserId:o.externalUserId?.trim()||null,email:W(o.email)});return[...new Set(p.map(w=>w.groupId))].sort((w,v)=>w-v)},async isEmailAllowedByGroup(r){if(!y.isEnabled)return!1;let o=W(r);if(o==="")return!1;await i();let[p]=await u.select({id:m.id}).from(m).innerJoin(a,h(a.groupId,m.groupId)).innerJoin(s,D(h(s.id,m.groupId),h(s.isActive,!0))).where(h(m.email,o));return!!p},async listAllowlistGroups(r){return M(r,"listAllowlistGroups(tenantId)"),await i(),(await u.select({id:a.id,groupId:a.groupId,groupKey:s.groupKey,groupName:s.name,description:a.description}).from(a).innerJoin(s,D(h(s.id,a.groupId),h(s.tenantId,r))).where(h(a.tenantId,r)).orderBy(ie(s.name))).map(p=>({id:Number(p.id),type:"group",groupId:Number(p.groupId),groupKey:String(p.groupKey),groupName:String(p.groupName),...p.description?{description:String(p.description)}:{},owner:{kind:"tenant",tenantId:r},readOnly:!1}))},async addAllowlistGroup(r,o,p,w){M(r,"addAllowlistGroup(tenantId)"),await i(),await u.transaction(async v=>{await A(v,r,o);let[k]=await v.select({id:n.id}).from(n).where(D(h(n.groupId,o),h(n.tenantId,r)));if(!k)throw new Error(`Group ${o} is locally managed; only externally-sourced groups can gate sign-in, because the decision reads the upstream member ledger.`);await v.insert(a).output().values({tenantId:r,groupId:o,description:p??null,createdAt:new Date,createdBy:w})})},async removeAllowlistGroup(r,o){M(r,"removeAllowlistGroup(tenantId)"),await i(),await u.delete(a).where(D(h(a.id,o),h(a.tenantId,r)))},async countProjectedMembers(r,o){M(r,"countProjectedMembers(tenantId)"),await i();let[p]=await u.select({n:X()}).from(S).innerJoin(s,D(h(s.id,S.groupId),h(s.tenantId,r))).where(h(S.groupId,o));return Number(p?.n??0)}}}import{normalizeAuthEmail as Se}from"@aiquants/auth-core";import{assertTenantId as Ae,assertTenantMatch as ve}from"@aiquants/authz-core";async function nr(t,d){let g=Ae(d.tenantId,"promoteLedgerForUser(tenantId)");if(!Number.isInteger(d.userId)||d.userId<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] userId must be a positive integer, received ${String(d.userId)}`);let y=Se(d.email),u=d.externalUserId?.trim()||null;if(u===null&&y==="")return{addedGroupIds:[],matchedLedgerIds:[]};let i=await t.findLedgerMatches({tenantId:g,externalUserId:u,email:y});if(i.length===0)return{addedGroupIds:[],matchedLedgerIds:[]};for(let n of i)ve(g,n.tenantId,"promoteLedgerForUser(findLedgerMatches row)");let l=[...new Set(i.map(n=>n.ledgerId))].sort((n,m)=>n-m),c=[...new Set(i.map(n=>n.groupId))].sort((n,m)=>n-m);return{addedGroupIds:[...await t.attachUserToGroups({tenantId:g,userId:d.userId,ledgerIds:l,groupIds:c})].sort((n,m)=>n-m),matchedLedgerIds:l}}import{assertTenantId as de}from"@aiquants/authz-core";import{and as Q,eq as Z,inArray as F}from"drizzle-orm";var ae=2100;var ir=500;function L(t,d=500){if(!Number.isInteger(d)||d<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] chunk size must be a positive integer, received ${String(d)}`);let g=[];for(let y=0;y<t.length;y+=d)g.push(t.slice(y,y+d));return g}function B(t,d,g=100){if(!Number.isInteger(d)||d<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] parametersPerItem must be a positive integer, received ${String(d)}`);if(!Number.isInteger(g)||g<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] reservedParameters must be a non-negative integer, received ${String(g)}`);let y=2100-g,u=Math.floor(y/d);if(u<1)throw new TypeError(`[@aiquants/auth-directory-drizzle] a single item binds ${d} parameters, which exceeds the ${y} available in one statement`);return L(t,u)}var Te=4,De=100;function pr(t,d,g,y){let u=t,i=y.ensureConnection??(async()=>{}),l=y.now??(()=>new Date),c=d.TDGroupMemberEmail,{TMGroup:b,TDGroupMember:n}=g;return{async findLedgerMatches(m){return de(m.tenantId,"findLedgerMatches(tenantId)"),await i(),j(u,{ledger:c,group:b},m)},async attachUserToGroups({tenantId:m,userId:e,ledgerIds:a,groupIds:I}){return de(m,"attachUserToGroups(tenantId)"),await i(),a.length===0?[]:u.transaction(async s=>{let S=new Set;for(let r of L(I)){let o=await s.select({id:b.id}).from(b).where(Q(F(b.id,r),Z(b.tenantId,m),Z(b.isActive,!0)));for(let p of o)S.add(Number(p.id))}let f=I.filter(r=>S.has(r));if(f.length===0)return[];let A=new Set;for(let r of L(f)){let o=await s.select({groupId:n.groupId}).from(n).where(Q(Z(n.userId,e),F(n.groupId,r)));for(let p of o)A.add(Number(p.groupId))}let G=f.filter(r=>!A.has(r)),_=Math.max(1,2100-De-f.length);for(let r of L(a,_))await s.update(c).set({resolvedUserId:e}).output().where(Q(F(c.id,r),F(c.groupId,f)));if(G.length>0){let r=l();for(let o of B(G,Te))await s.insert(n).output().values(o.map(p=>({groupId:p,userId:e,createdAt:r,createdBy:y.actor})))}return G})}}}import{AUTHZ_TENANT_COLUMN as O}from"@aiquants/authz-drizzle";import{bigint as P,bit as ue,datetime2 as $,foreignKey as H,int as xe,mssqlSchema as ke,nvarchar as T,primaryKey as N,uniqueIndex as C}from"drizzle-orm/mssql-core";var Ge="provider",gr=["TDGroupExternalLink","TDTenantMembershipGroup","TDAllowlistGroup","TDDirectorySyncRequest","TMAllowlist"];function hr(t,d){let g=ke(t),y=()=>({createdAt:$("created_at"),createdBy:T("created_by",{length:50}),updatedAt:$("updated_at"),updatedBy:T("updated_by",{length:50})}),u=()=>({createdAt:$("created_at"),createdBy:T("created_by",{length:50})}),i=g.table("TDGroupExternalLink",{id:P("id",{mode:"number"}).identity().notNull(),groupId:P("group_id",{mode:"number"}).notNull(),tenantId:T(O,{length:64}).notNull(),provider:T(Ge,{length:32}).notNull(),externalId:T("external_id",{length:256}).notNull(),externalKey:T("external_key",{length:320}).notNull(),membershipMode:T("membership_mode",{length:16}).notNull(),isSyncEnabled:ue("is_sync_enabled").notNull(),syncedAt:$("synced_at"),syncError:T("sync_error",{length:1e3}),vanishedObservations:xe("vanished_observations").notNull(),...y()},e=>[N({name:`${t}_TDGroupExternalLink_pk`,columns:[e.id]}),C(`${t}_TDGroupExternalLink_group_uq`).on(e.groupId),C(`${t}_TDGroupExternalLink_external_uq`).on(e.tenantId,e.provider,e.externalId),H({name:`${t}_TDGroupExternalLink_group_id_fk`,columns:[e.groupId],foreignColumns:[d.groupTable.id]})]),l=g.table("TDGroupMemberEmail",{id:P("id",{mode:"number"}).identity().notNull(),groupId:P("group_id",{mode:"number"}).notNull(),email:T("email",{length:320}).notNull(),externalUserId:T("external_user_id",{length:128}),resolvedUserId:P("resolved_user_id",{mode:"number"}),observedAt:$("observed_at").notNull(),...y()},e=>[N({name:`${t}_TDGroupMemberEmail_pk`,columns:[e.id]}),C(`${t}_TDGroupMemberEmail_group_email_uq`).on(e.groupId,e.email),H({name:`${t}_TDGroupMemberEmail_group_id_fk`,columns:[e.groupId],foreignColumns:[d.groupTable.id]})]),c=g.table("TDTenantMembershipGroup",{tenantId:T(O,{length:64}).notNull(),groupId:P("group_id",{mode:"number"}).notNull(),isBreakGlass:ue("is_break_glass").notNull(),...u()},e=>[N({name:`${t}_TDTenantMembershipGroup_pk`,columns:[e.tenantId,e.groupId]}),H({name:`${t}_TDTenantMembershipGroup_group_id_fk`,columns:[e.groupId],foreignColumns:[d.groupTable.id]})]),b=g.table("TDAllowlistGroup",{id:P("id",{mode:"number"}).identity().notNull(),tenantId:T(O,{length:64}).notNull(),groupId:P("group_id",{mode:"number"}).notNull(),description:T("description",{length:512}),...u()},e=>[N({name:`${t}_TDAllowlistGroup_pk`,columns:[e.id]}),C(`${t}_TDAllowlistGroup_tenant_group_uq`).on(e.tenantId,e.groupId),H({name:`${t}_TDAllowlistGroup_group_id_fk`,columns:[e.groupId],foreignColumns:[d.groupTable.id]})]),n=g.table("TDDirectorySyncRequest",{id:P("id",{mode:"number"}).identity().notNull(),tenantId:T(O,{length:64}).notNull(),groupId:P("group_id",{mode:"number"}),requestedBy:T("requested_by",{length:320}).notNull(),requestedAt:$("requested_at").notNull(),pickedAt:$("picked_at"),finishedAt:$("finished_at"),result:T("result",{length:32}),error:T("error",{length:2e3}),...u()},e=>[N({name:`${t}_TDDirectorySyncRequest_pk`,columns:[e.id]})]),m=g.table("TMAllowlist",{id:P("id",{mode:"number"}).identity().notNull(),tenantId:T(O,{length:64}).notNull(),pattern:T("pattern",{length:256}).notNull(),type:T("type",{length:32}).notNull(),description:T("description",{length:512}),...u()},e=>[N({name:`${t}_TMAllowlist_pk`,columns:[e.id]}),C(`${t}_TMAllowlist_pattern_uq`).on(e.tenantId,e.pattern)]);return{TDGroupExternalLink:i,TDGroupMemberEmail:l,TDTenantMembershipGroup:c,TDAllowlistGroup:b,TDDirectorySyncRequest:n,TMAllowlist:m}}import{assertBlastRadiusPolicy as Ee,reconcile as Me}from"@aiquants/auth-directory-core";var ee=(t,d=20)=>t.length<=d?t.join(", "):`${t.slice(0,d).join(", ")} \u307B\u304B ${t.length-d} \u4EF6`,le={upstreamUnreadable:t=>`\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${t}`,localWriteFailed:t=>`\u30ED\u30FC\u30AB\u30EB\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${t}`,resolverIncomplete:(t,d)=>`\u4E0A\u6D41\u304C ${t} \u4EF6\u306E\u30E1\u30F3\u30D0\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F\u304C\u3001\u7A81\u5408\u7D50\u679C\u306F ${d} \u4EF6\u3067\u3057\u305F\u3002\u4EF6\u6570\u304C\u6E1B\u308B\u7A81\u5408\u306F\u3001\u89E3\u6C7A\u3067\u304D\u306A\u304B\u3063\u305F\u4EBA\u7269\u3092\u300C\u4E0A\u6D41\u304B\u3089\u6D88\u3048\u305F\u300D\u3068\u3057\u3066\u524A\u9664\u3057\u3066\u3057\u307E\u3046\u305F\u3081\u4E2D\u65AD\u3057\u307E\u3057\u305F\u3002\u89E3\u6C7A\u3067\u304D\u306A\u3044\u30E1\u30F3\u30D0\u30FC\u306F\u7701\u7565\u305B\u305A resolvedUserId: null \u3067\u8FD4\u3059\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`,groupMissing:(t,d,g)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (${t})\u3002\u9023\u7D9A ${d} \u56DE\u76EE\u3067\u3059\u3002${g} \u56DE\u306B\u9054\u3057\u305F\u6642\u70B9\u3067\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3059\u3002`,groupVanished:(t,d)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C ${d} \u56DE\u9023\u7D9A\u3067\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F (${t})\u3002\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3057\u305F\u3002\u4E0A\u6D41\u304C\u5FA9\u65E7\u3059\u308C\u3070\u6B21\u56DE\u306E\u540C\u671F\u3067\u81EA\u52D5\u7684\u306B\u518D\u6709\u52B9\u5316\u3055\u308C\u307E\u3059\u3002`,planRefused:t=>{if(t.reason==="invalid-upstream-data")return`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: \u4E0A\u6D41\u306E\u30E1\u30F3\u30D0\u30FC\u4E00\u89A7\u306B\u6271\u3048\u306A\u3044\u30A2\u30C9\u30EC\u30B9\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u3059 (${ee(t.offendingEmails)})\u3002`;let d=t.axis==="membership"?"\u6A29\u9650\u3092\u5931\u3046\u4EBA\u6570":"\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u306A\u304F\u306A\u308B\u4EBA\u6570",g=t.axis==="membership"?t.withheldProjectionRemoves.map(String):t.withheldLedgerDeletes;return[`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: ${d}\u304C ${t.population} \u4EBA\u4E2D ${t.removalCount} \u4EBA\u306B\u306A\u308A\u307E\u3059`,`(\u4E0A\u9650: ${t.limits.maxRemovalCount} \u4EF6 / \u6BD4\u7387 ${t.limits.maxRemovalRatio})\u3002`,`\u5DEE\u3057\u6B62\u3081\u305F\u5BFE\u8C61: ${ee(g)}\u3002`,"\u4E0A\u6D41\u5074\u306E\u8A2D\u5B9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u610F\u56F3\u3057\u305F\u5909\u66F4\u3067\u3042\u308C\u3070\u3001\u3053\u306E\u8EF8\u306E\u4E0A\u9650\u3060\u3051\u3092\u4E00\u6642\u7684\u306B\u5F15\u304D\u4E0A\u3052\u3066\u518D\u5B9F\u884C\u3057\u307E\u3059\u3002"].join(" ")},removalsRefused:t=>`${t.length} \u4EF6\u306E\u6240\u5C5E\u89E3\u9664\u304C\u6700\u5F8C\u306E\u7BA1\u7406\u8005\u30AC\u30FC\u30C9\u306B\u3088\u308A\u62D2\u5426\u3055\u308C\u307E\u3057\u305F (\u30E6\u30FC\u30B6\u30FC ${ee(t.map(d=>String(d.userId)))})\u3002\u6B8B\u308A\u306E\u5909\u66F4\u306F\u9069\u7528\u6E08\u307F\u3067\u3059\u3002`,foreignProvider:(t,d)=>`\u3053\u306E\u30EA\u30F3\u30AF\u306E\u4F9B\u7D66\u5143\u306F ${t} \u3067\u3059\u304C\u3001\u5B9F\u884C\u4E2D\u306E\u540C\u671F\u306F ${d} \u7528\u3067\u3059\u3002\u5BFE\u5FDC\u3059\u308B\u540C\u671F\u30B8\u30E7\u30D6\u304B\u3089\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002`};var pe=["aborted","missing","vanished","upstream-failed","local-failed","foreign-provider"];function vr(t){let{directory:d,repository:g,policy:y,now:u,logger:i}=t,l=t.messages??le;if(Ee(y),!Number.isInteger(t.vanishedThreshold)||t.vanishedThreshold<1)throw new TypeError(`[@aiquants/auth-directory-drizzle] vanishedThreshold must be a positive integer, received ${String(t.vanishedThreshold)}`);if(typeof t.provider!="string"||t.provider.trim()==="")throw new TypeError("[@aiquants/auth-directory-drizzle] provider must be a non-empty string so links of other providers can be told apart");async function c(e,a){try{await g.recordFailure(e,a)}catch(I){i.error(`[auth-directory] could not record the failure for ${e.externalKey}:`,I)}}async function b(e){try{let a=await d.getGroup(e.externalId);return a===null?{kind:"missing"}:{kind:"ok",group:a,members:await d.listGroupMembers(a,e.membershipMode)}}catch(a){return{kind:"failed",error:a instanceof Error?a:new Error(String(a))}}}async function n(e){let a=await g.recordVanishedObservation(e,l.groupMissing(e.externalKey,e.vanishedObservations+1,t.vanishedThreshold));return a<t.vanishedThreshold?(i.warn(`[auth-directory] ${e.externalKey} missing upstream (${a}/${t.vanishedThreshold})`),{kind:"missing",link:e,observations:a}):(await g.markGroupInactive(e),await c(e,l.groupVanished(e.externalKey,a)),i.warn(`[auth-directory] ${e.externalKey} deactivated after ${a} consecutive absences`),{kind:"vanished",link:e})}async function m(e){if(e.provider!==t.provider)return await c(e,l.foreignProvider(e.provider,t.provider)),i.warn(`[auth-directory] skipping ${e.externalKey}: belongs to provider ${e.provider}`),{kind:"foreign-provider",link:e};let a=await b(e);if(a.kind==="missing")return n(e);if(a.kind==="failed")return await c(e,l.upstreamUnreadable(a.error.message)),i.error(`[auth-directory] upstream read failed for ${e.externalKey}:`,a.error),{kind:"upstream-failed",link:e,error:a.error};try{let[I,s]=await Promise.all([g.loadGroupSnapshot(e),g.resolveMembers(a.members)]);if(s.length!==a.members.length){let _=l.resolverIncomplete(a.members.length,s.length);return await c(e,_),i.error(`[auth-directory] ${_}`),{kind:"local-failed",link:e,error:new Error(_)}}let S=Me(I,s,y);if(S.aborted)return await c(e,l.planRefused(S.aborted)),i.warn(`[auth-directory] plan refused for ${e.externalKey}: ${S.aborted.reason}`),{kind:"aborted",link:e,plan:S};let f=S.ledgerUpserts.length>0||S.ledgerDeletes.length>0||S.projectionAdds.length>0||S.projectionRemoves.length>0,A=u(),G={refusals:[]};return f&&(G=await g.applyPlan(e,S,A)),await g.recordSuccess(e,a.group,A),G.refusals.length>0&&(await c(e,l.removalsRefused(G.refusals)),i.warn(`[auth-directory] ${G.refusals.length} removal(s) refused for ${e.externalKey}`)),S.unresolvedEmails.length>0&&i.info(`[auth-directory] ${e.externalKey}: ${S.unresolvedEmails.length} upstream member(s) have no local user yet`),f?{kind:"applied",link:e,plan:S}:{kind:"unchanged",link:e}}catch(I){let s=I instanceof Error?I:new Error(String(I));return await c(e,l.localWriteFailed(s.message)),i.error(`[auth-directory] local write failed for ${e.externalKey}:`,s),{kind:"local-failed",link:e,error:s}}}return{syncOne:m,async run(e={}){let a=await g.listSyncableLinks(e),I=[];for(let S of a)try{I.push(await m(S))}catch(f){let A=f instanceof Error?f:new Error(String(f));i.error(`[auth-directory] ${S.externalKey} ended abnormally:`,A),I.push({kind:"local-failed",link:S,error:A})}let s=I.filter(S=>pe.includes(S.kind)).length;return i.info(`[auth-directory] run finished: ${I.length} link(s), ${s} unhealthy`),{outcomes:I,unhealthyCount:s}}}}import{assertTenantId as Re}from"@aiquants/authz-core";import{and as Pe,asc as ce,eq as me,isNull as re}from"drizzle-orm";var te=2e3;function kr(t,d,g){let y=t,u=g.ensureConnection??(async()=>{}),i=d.TDDirectorySyncRequest,l=g.maxErrorLength??te;if(!Number.isInteger(l)||l<1||l>te)throw new TypeError(`[@aiquants/auth-directory-drizzle] maxErrorLength must be an integer within 1..${te}, received ${String(g.maxErrorLength)}`);return{async claim(){await u();let c=await y.select({id:i.id}).from(i).where(re(i.pickedAt)).orderBy(ce(i.id));for(let b of c){let m=(await y.update(i).set({pickedAt:g.now()}).output().where(Pe(me(i.id,Number(b.id)),re(i.pickedAt))))?.[0];if(!m)continue;let e=String(m.tenant_id??m.tenantId??""),a=m.group_id??m.groupId??null;return{id:Number(m.id),tenantId:Re(e,"claim(row.tenantId)"),groupId:a===null?null:Number(a),requestedBy:String(m.requested_by??m.requestedBy??"")}}return null},async finish(c,b){await u();let n=b.error?b.error.slice(0,l):null;await y.update(i).set({finishedAt:g.now(),result:b.result,error:n}).output().where(me(i.id,c))},async listStranded(c){return await u(),(await y.select({id:i.id,pickedAt:i.pickedAt}).from(i).where(re(i.finishedAt)).orderBy(ce(i.id))).filter(n=>n.pickedAt!==null&&new Date(n.pickedAt)<c).map(n=>Number(n.id))}}}import{normalizeAuthEmail as ye}from"@aiquants/auth-core";import{assertTenantId as ge,assertTenantMatch as _e}from"@aiquants/authz-core";import{and as K,eq as x,inArray as ne,isNull as Le,notInArray as $e,or as Ke}from"drizzle-orm";var Ne=7,Ue=4;function Lr(t,d,g,y){let u=t,i=y.ensureConnection??(async()=>{}),{TDGroupExternalLink:l,TDGroupMemberEmail:c}=d,{TMGroup:b,TDGroupMember:n}=g,m=y.actor;return{async listSyncableLinks(e){await i();let a=[x(l.isSyncEnabled,!0)];return e.tenantId!==void 0&&a.push(x(l.tenantId,ge(e.tenantId,"listSyncableLinks(tenantId)"))),e.groupId!==void 0&&a.push(x(l.groupId,e.groupId)),(await u.select({linkId:l.id,groupId:l.groupId,linkTenantId:l.tenantId,groupTenantId:b.tenantId,groupKey:b.groupKey,isGroupActive:b.isActive,provider:l.provider,externalId:l.externalId,externalKey:l.externalKey,membershipMode:l.membershipMode,vanishedObservations:l.vanishedObservations}).from(l).innerJoin(b,x(b.id,l.groupId)).where(K(...a))).map(s=>(_e(String(s.groupTenantId),String(s.linkTenantId),"listSyncableLinks(link.tenantId vs TMGroup.tenant_id)"),{linkId:Number(s.linkId),groupId:Number(s.groupId),groupKey:String(s.groupKey),tenantId:String(s.groupTenantId),provider:String(s.provider),externalId:String(s.externalId),externalKey:String(s.externalKey),membershipMode:q(String(s.membershipMode)),isGroupActive:!!s.isGroupActive,vanishedObservations:Number(s.vanishedObservations??0)}))},async loadGroupSnapshot(e){await i();let[a,I]=await Promise.all([u.select({email:c.email,externalUserId:c.externalUserId,resolvedUserId:c.resolvedUserId}).from(c).where(x(c.groupId,e.groupId)),u.select({userId:n.userId}).from(n).where(x(n.groupId,e.groupId))]);return{ledger:a.map(s=>({email:String(s.email),...s.externalUserId?{externalUserId:String(s.externalUserId)}:{},resolvedUserId:s.resolvedUserId===null?null:Number(s.resolvedUserId)})),projectedUserIds:I.map(s=>Number(s.userId))}},resolveMembers(e){return y.memberResolver.resolve(e)},async applyPlan(e,a,I){ge(e.tenantId,"applyPlan(link.tenantId)"),await i(),(a.ledgerUpserts.length>0||a.projectionAdds.length>0)&&await u.transaction(async f=>{let A=a.ledgerUpserts.map(r=>({...r,email:ye(r.email)})),G=new Map;for(let r of L(A.map(o=>o.email))){let o=await f.select({id:c.id,email:c.email}).from(c).where(K(x(c.groupId,e.groupId),ne(c.email,r)));for(let p of o)G.set(String(p.email),Number(p.id))}let _=A.filter(r=>!G.has(r.email));for(let r of B(_,Ne))await f.insert(c).output().values(r.map(o=>({groupId:e.groupId,email:o.email,externalUserId:o.externalUserId??null,resolvedUserId:o.resolvedUserId,observedAt:I,createdAt:I,createdBy:m})));for(let r of A){let o=G.get(r.email);o!==void 0&&await f.update(c).set({externalUserId:r.externalUserId??null,resolvedUserId:r.resolvedUserId,observedAt:I,updatedAt:I,updatedBy:m}).output().where(x(c.id,o))}for(let r of B(a.projectionAdds,Ue)){let o=new Set((await f.select({userId:n.userId}).from(n).where(K(x(n.groupId,e.groupId),ne(n.userId,r)))).map(w=>Number(w.userId))),p=r.filter(w=>!o.has(w));p.length>0&&await f.insert(n).output().values(p.map(w=>({groupId:e.groupId,userId:w,createdAt:I,createdBy:m})))}});let s=[];for(let f of a.projectionRemoves)try{await y.lockoutGuard.guardedWrite(e.tenantId,{removeGroupMembership:{groupId:e.groupId,userId:f}},"remove-group-member",A=>A.delete(n).where(K(x(n.groupId,e.groupId),x(n.userId,f))))}catch(A){s.push({userId:f,applied:!1,refusal:A instanceof Error?A.message:String(A)})}let S=s.map(f=>f.userId);return a.ledgerDeletes.length>0&&await u.transaction(async f=>{for(let A of L(a.ledgerDeletes)){let G=S.length>0?Ke(Le(c.resolvedUserId),$e(c.resolvedUserId,S)):void 0;await f.delete(c).where(K(x(c.groupId,e.groupId),ne(c.email,A),...G?[G]:[]))}}),{refusals:s}},async recordSuccess(e,a,I){await i(),await u.transaction(async s=>{await s.update(l).set({syncedAt:I,syncError:null,vanishedObservations:0,externalId:a.externalId,externalKey:ye(a.externalKey),updatedAt:I,updatedBy:m}).output().where(x(l.id,e.linkId)),e.isGroupActive||await s.update(b).set({isActive:!0,updatedAt:I,updatedBy:m}).output().where(K(x(b.id,e.groupId),x(b.tenantId,e.tenantId)))})},async recordFailure(e,a){await i(),await u.update(l).set({syncError:a.slice(0,1e3),updatedAt:new Date,updatedBy:m}).output().where(x(l.id,e.linkId))},async recordVanishedObservation(e,a){await i();let I=e.vanishedObservations+1;return await u.update(l).set({vanishedObservations:I,syncError:a.slice(0,1e3),updatedAt:new Date,updatedBy:m}).output().where(x(l.id,e.linkId)),I},async markGroupInactive(e){await i(),await u.update(b).set({isActive:!1,updatedAt:new Date,updatedBy:m}).output().where(K(x(b.id,e.groupId),x(b.tenantId,e.tenantId)))}}}export{Ge as AUTH_DIRECTORY_PROVIDER_COLUMN,gr as AUTH_DIRECTORY_TENANT_SCOPED_TABLES,ir as MAX_IN_LIST,ae as MAX_PARAMETERS,oe as MEMBERSHIP_MODES,pe as UNHEALTHY_SYNC_OUTCOMES,Oe as assertGroupMayBeLinked,q as assertMembershipMode,L as chunk,B as chunkByParameters,Ze as createDirectoryLinkStore,vr as createDirectorySync,Lr as createDirectorySyncRepository,pr as createPromotionRepository,kr as createSyncRequestQueue,qe as defaultExternalGroupGuardMessage,le as defaultSyncMessages,hr as defineAuthDirectorySchema,j as findLedgerRowsForIdentity,nr as promoteLedgerForUser,Be as withExternalGroupGuards};
2
2
  //# sourceMappingURL=index.mjs.map