@aiquants/auth-directory-drizzle 0.2.1 → 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 ye}from"@aiquants/authz-core";var Ne=(n,s)=>`\u5916\u90E8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u9023\u643A\u30B0\u30EB\u30FC\u30D7 (${s}) \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,s,g,h){return new Set(n.filter(a=>a.roleId===s&&a.appKey===g&&a.resourceKey===h).map(a=>a.action))}var F=n=>ye.every(s=>n.has(s)),H=(n,s)=>n.find(g=>g.roleId===s)?.roleKey??`#${s}`;function Ue(n,s){let g=n;async function h(a,i,t){if(i.size===0)return;let l=await g.listGroupRoles(a),u=new Set;for(let d of l){if(!i.has(d.roleId)||u.has(d.groupId))continue;u.add(d.groupId);let b=await s.linkLookup.externalGroupKey(a,d.groupId);if(b!==null)throw new Error(s.message(d.roleKey||H(t,d.roleId),b))}}return{...n,async assignGroupRole(a,i,t,l){let u=await g.listGrants(a);if(F(j(u,t,s.adminAppKey,s.adminResourceKey))){let d=await s.linkLookup.externalGroupKey(a,i);if(d!==null)throw new Error(s.message(H(u,t),d))}return n.assignGroupRole(a,i,t,l)},async createGrant(a,i,t){let u=(await g.listResources(a)).find(d=>d.id===i.resourceId);if(u?.appKey===s.adminAppKey&&u?.resourceKey===s.adminResourceKey){let d=await g.listGrants(a),b=j(d,i.roleId,s.adminAppKey,s.adminResourceKey);b.add(i.action),F(b)&&await h(a,new Set([i.roleId]),d)}return n.createGrant(a,i,t)}}}async function qe(n,s,g,h){let a=(await n.listGroupRoles(s)).filter(t=>t.groupId===g);if(a.length===0)return;let i=await n.listGrants(s);for(let t of a)if(F(j(i,t.roleId,h.adminAppKey,h.adminResourceKey)))throw new Error(h.message(t.roleKey||H(i,t.roleId),t.groupKey))}import{and as ge,eq as K}from"drizzle-orm";async function B(n,s,g){let{ledger:h,group:a}=s,{tenantId:i,externalUserId:t,email:l}=g;if(t===null&&l==="")return[];let u={ledgerId:h.id,groupId:h.groupId,tenantId:a.tenantId,externalUserId:h.externalUserId},d=async y=>(await n.select(u).from(h).innerJoin(a,ge(K(a.id,h.groupId),K(a.tenantId,i),K(a.isActive,!0))).where(y)).map(w=>({ledgerId:Number(w.ledgerId),groupId:Number(w.groupId),tenantId:String(w.tenantId),externalUserId:w.externalUserId===null?null:String(w.externalUserId)})),b=y=>{let v=new Map;for(let w of y)v.set(w.ledgerId,{ledgerId:w.ledgerId,groupId:w.groupId,tenantId:w.tenantId});return[...v.values()].sort((w,r)=>w.ledgerId-r.ledgerId)},e=t===null?[]:await d(K(h.externalUserId,t));if(l==="")return b(e);let m=await d(K(h.email,l)),S=y=>t!==null&&y.externalUserId!==null&&y.externalUserId!==t;return b([...e,...m.filter(y=>!S(y))])}import{normalizeAuthEmail as J}from"@aiquants/auth-core";import{assertTenantMembershipPolicy as he}from"@aiquants/auth-directory-core";import{assertTenantId as G,assertTenantMatch as be}from"@aiquants/authz-core";import{and as D,asc as te,count as V,eq as I,isNull as ne}from"drizzle-orm";var re=["direct","transitive"];function N(n){if(!re.includes(n))throw new TypeError(`[@aiquants/auth-directory-drizzle] membershipMode must be one of ${re.join(" | ")}, received ${JSON.stringify(n)}`);return n}function We(n,s,g,h){let a=n,i=h.ensureConnection??(async()=>{}),{TDGroupExternalLink:t,TDGroupMemberEmail:l,TDTenantMembershipGroup:u,TDAllowlistGroup:d,TDDirectorySyncRequest:b}=s,{TMGroup:e,TDGroupMember:m}=g;async function S(r,o,c){if((await r.select({groupId:u.groupId}).from(u).where(D(I(u.tenantId,o),I(u.groupId,c),I(u.isBreakGlass,!0)))).length>0)throw new Error(`Group ${c} is a break-glass proof group; a break-glass proof path must be one the synchronization can never touch.`)}async function y(r,o,c){let[p]=await r.select({id:e.id}).from(e).where(D(I(e.id,c),I(e.tenantId,o)));if(!p)throw new Error(`Group not found in tenant: ${c}`)}function v(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:N(String(r.membershipMode)),isSyncEnabled:!!r.isSyncEnabled,...r.syncedAt?{syncedAt:new Date(r.syncedAt).toISOString()}:{},...r.syncError?{syncError:String(r.syncError)}:{},unresolvedCount:o}}async function w(r,o){let c=await r.select({groupId:l.groupId,n:V()}).from(l).innerJoin(e,D(I(e.id,l.groupId),I(e.tenantId,o))).where(ne(l.resolvedUserId)).groupBy(l.groupId);return new Map(c.map(p=>[Number(p.groupId),Number(p.n)]))}return{async listLinks(r){G(r,"listLinks(tenantId)"),await i();let o=await a.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(I(t.tenantId,r)).orderBy(te(t.groupId)),c=await w(a,r);return o.map(p=>(be(r,String(p.tenantId),"listLinks(row.tenantId)"),v(p,c.get(Number(p.groupId))??0)))},async externalGroupKey(r,o){G(r,"externalGroupKey(tenantId)"),await i();let[c]=await a.select({externalKey:t.externalKey}).from(t).where(D(I(t.groupId,o),I(t.tenantId,r)));return c?String(c.externalKey):null},async linkGroup(r,o,c,p){G(r,"linkGroup(tenantId)");let f=N(c.membershipMode),A=c.externalId.trim(),E=J(c.externalKey);if(A===""||E==="")throw new TypeError("[@aiquants/auth-directory-drizzle] externalId and externalKey must be non-empty");await i(),await h.linkable.assertLinkable(r,o),await a.transaction(async $=>{await y($,r,o),await S($,r,o),await $.insert(t).output().values({groupId:o,tenantId:r,provider:c.provider,externalId:A,externalKey:E,membershipMode:f,isSyncEnabled:!0,vanishedObservations:0,createdAt:new Date,createdBy:p})})},async unlinkGroup(r,o){G(r,"unlinkGroup(tenantId)"),await i(),await a.transaction(async c=>{await y(c,r,o),await c.delete(l).where(I(l.groupId,o)),await c.delete(d).where(D(I(d.groupId,o),I(d.tenantId,r))),await c.delete(t).where(D(I(t.groupId,o),I(t.tenantId,r)))})},async setSyncEnabled(r,o,c,p){G(r,"setSyncEnabled(tenantId)"),await i(),await a.update(t).set({isSyncEnabled:c,updatedAt:new Date,updatedBy:p}).output().where(D(I(t.groupId,o),I(t.tenantId,r)))},async requestSync(r,o,c){G(r,"requestSync(tenantId)"),await i(),await a.transaction(async p=>{o!==null&&await y(p,r,o),await p.insert(b).output().values({tenantId:r,groupId:o,requestedBy:c,requestedAt:new Date,createdAt:new Date,createdBy:c})})},async getStatus(r){G(r,"getStatus(tenantId)"),await i();let o=await a.select({groupId:t.groupId,isSyncEnabled:t.isSyncEnabled,syncedAt:t.syncedAt,syncError:t.syncError}).from(t).where(I(t.tenantId,r)),[c]=await a.select({n:V()}).from(b).where(D(I(b.tenantId,r),ne(b.finishedAt))),p=o.filter(k=>!!k.isSyncEnabled),f=p.map(k=>k.syncedAt?new Date(k.syncedAt).getTime():null),A=f.length>0&&f.every(k=>k!==null)?Math.min(...f):null,$=o.length>0&&p.length===0||p.length>0&&(A===null||Date.now()-A>h.stalenessThresholdSec*1e3);return{isEnabled:h.isEnabled,linkedGroupCount:o.length,...A!==null?{oldestSyncedAt:new Date(A).toISOString()}:{},isStale:$,pausedGroupCount:o.length-p.length,stalenessThresholdSec:h.stalenessThresholdSec,erroredGroupIds:o.filter(k=>!!k.syncError).map(k=>Number(k.groupId)).sort((k,me)=>k-me),pendingRequestCount:Number(c?.n??0)}},async loadTenantMembershipPolicy(r){G(r,"loadTenantMembershipPolicy(tenantId)"),await i();let o=await a.select({groupId:u.groupId,isBreakGlass:u.isBreakGlass,externalLinkId:t.id}).from(u).innerJoin(e,D(I(e.id,u.groupId),I(e.tenantId,r))).leftJoin(t,I(t.groupId,u.groupId)).where(I(u.tenantId,r));if(o.length===0)return{kind:"no-proof-required"};let c=o.map(A=>Number(A.groupId)),p=o.filter(A=>!!A.isBreakGlass);for(let A of p)if(A.externalLinkId!==null)throw new Error(`Break-glass group ${Number(A.groupId)} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`);let f={kind:"require-proof",proofGroupIds:c,breakGlassGroupIds:p.map(A=>Number(A.groupId))};return he(f,`tenant ${r}`),f},async setTenantMembershipGroup(r,o,c,p){G(r,"setTenantMembershipGroup(tenantId)"),await i(),await a.transaction(async f=>{if(await y(f,r,o),c){let[E]=await f.select({id:t.id}).from(t).where(D(I(t.tenantId,r),I(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 f.select({groupId:u.groupId}).from(u).where(D(I(u.tenantId,r),I(u.groupId,o)))).length>0){await f.update(u).set({isBreakGlass:c,updatedAt:new Date,updatedBy:p}).output().where(D(I(u.tenantId,r),I(u.groupId,o)));return}await f.insert(u).output().values({tenantId:r,groupId:o,isBreakGlass:c,createdAt:new Date,createdBy:p})})},async removeTenantMembershipGroup(r,o){G(r,"removeTenantMembershipGroup(tenantId)"),await i(),await a.transaction(async c=>{let f=(await c.select({groupId:u.groupId,isBreakGlass:u.isBreakGlass}).from(u).where(I(u.tenantId,r))).filter(A=>Number(A.groupId)!==o);if(f.length>0&&!f.some(A=>!!A.isBreakGlass))throw new Error(`Removing group ${o} would leave tenant ${r} with a membership proof but no break-glass path.`);await c.delete(u).where(D(I(u.tenantId,r),I(u.groupId,o)))})},async setGroupActive(r,o,c,p){G(r,"setGroupActive(tenantId)"),await i(),await a.update(e).set({isActive:c,updatedAt:new Date,updatedBy:p}).output().where(D(I(e.id,o),I(e.tenantId,r)))},async listLedgerGroupIds(r,o){if(G(r,"listLedgerGroupIds(tenantId)"),!h.isEnabled)return[];await i();let c=await B(a,{ledger:l,group:e},{tenantId:r,externalUserId:o.externalUserId?.trim()||null,email:J(o.email)});return[...new Set(c.map(p=>p.groupId))].sort((p,f)=>p-f)},async isEmailAllowedByGroup(r){if(!h.isEnabled)return!1;let o=J(r);if(o==="")return!1;await i();let[c]=await a.select({id:l.id}).from(l).innerJoin(d,I(d.groupId,l.groupId)).innerJoin(e,D(I(e.id,l.groupId),I(e.isActive,!0))).where(I(l.email,o));return!!c},async listAllowlistGroups(r){return G(r,"listAllowlistGroups(tenantId)"),await i(),(await a.select({id:d.id,groupId:d.groupId,groupKey:e.groupKey,groupName:e.name,description:d.description}).from(d).innerJoin(e,D(I(e.id,d.groupId),I(e.tenantId,r))).where(I(d.tenantId,r)).orderBy(te(e.name))).map(c=>({id:Number(c.id),type:"group",groupId:Number(c.groupId),groupKey:String(c.groupKey),groupName:String(c.groupName),...c.description?{description:String(c.description)}:{},owner:{kind:"tenant",tenantId:r},readOnly:!1}))},async addAllowlistGroup(r,o,c,p){G(r,"addAllowlistGroup(tenantId)"),await i(),await a.transaction(async f=>{await y(f,r,o);let[A]=await f.select({id:t.id}).from(t).where(D(I(t.groupId,o),I(t.tenantId,r)));if(!A)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 f.insert(d).output().values({tenantId:r,groupId:o,description:c??null,createdAt:new Date,createdBy:p})})},async removeAllowlistGroup(r,o){G(r,"removeAllowlistGroup(tenantId)"),await i(),await a.delete(d).where(D(I(d.id,o),I(d.tenantId,r)))},async countProjectedMembers(r,o){G(r,"countProjectedMembers(tenantId)"),await i();let[c]=await a.select({n:V()}).from(m).innerJoin(e,D(I(e.id,m.groupId),I(e.tenantId,r))).where(I(m.groupId,o));return Number(c?.n??0)}}}import{normalizeAuthEmail as Ie}from"@aiquants/auth-core";import{assertTenantId as fe,assertTenantMatch as we}from"@aiquants/authz-core";async function er(n,s){let g=fe(s.tenantId,"promoteLedgerForUser(tenantId)");if(!Number.isInteger(s.userId)||s.userId<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] userId must be a positive integer, received ${String(s.userId)}`);let h=Ie(s.email),a=s.externalUserId?.trim()||null;if(a===null&&h==="")return{addedGroupIds:[],matchedLedgerIds:[]};let i=await n.findLedgerMatches({tenantId:g,externalUserId:a,email:h});if(i.length===0)return{addedGroupIds:[],matchedLedgerIds:[]};for(let d of i)we(g,d.tenantId,"promoteLedgerForUser(findLedgerMatches row)");let t=[...new Set(i.map(d=>d.ledgerId))].sort((d,b)=>d-b),l=[...new Set(i.map(d=>d.groupId))].sort((d,b)=>d-b);return{addedGroupIds:[...await n.attachUserToGroups({tenantId:g,userId:s.userId,ledgerIds:t,groupIds:l})].sort((d,b)=>d-b),matchedLedgerIds:t}}import{assertTenantId as ie}from"@aiquants/authz-core";import{and as Y,eq as W,inArray as O}from"drizzle-orm";var oe=2100;var tr=500;function M(n,s=500){if(!Number.isInteger(s)||s<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] chunk size must be a positive integer, received ${String(s)}`);let g=[];for(let h=0;h<n.length;h+=s)g.push(n.slice(h,h+s));return g}function U(n,s,g=100){if(!Number.isInteger(s)||s<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] parametersPerItem must be a positive integer, received ${String(s)}`);if(!Number.isInteger(g)||g<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] reservedParameters must be a non-negative integer, received ${String(g)}`);let h=2100-g,a=Math.floor(h/s);if(a<1)throw new TypeError(`[@aiquants/auth-directory-drizzle] a single item binds ${s} parameters, which exceeds the ${h} available in one statement`);return M(n,a)}var Se=4,Ae=100;function dr(n,s,g,h){let a=n,i=h.ensureConnection??(async()=>{}),t=h.now??(()=>new Date),l=s.TDGroupMemberEmail,{TMGroup:u,TDGroupMember:d}=g;return{async findLedgerMatches(b){return ie(b.tenantId,"findLedgerMatches(tenantId)"),await i(),B(a,{ledger:l,group:u},b)},async attachUserToGroups({tenantId:b,userId:e,ledgerIds:m,groupIds:S}){return ie(b,"attachUserToGroups(tenantId)"),await i(),m.length===0?[]:a.transaction(async y=>{let v=new Set;for(let p of M(S)){let f=await y.select({id:u.id}).from(u).where(Y(O(u.id,p),W(u.tenantId,b),W(u.isActive,!0)));for(let A of f)v.add(Number(A.id))}let w=S.filter(p=>v.has(p));if(w.length===0)return[];let r=new Set;for(let p of M(w)){let f=await y.select({groupId:d.groupId}).from(d).where(Y(W(d.userId,e),O(d.groupId,p)));for(let A of f)r.add(Number(A.groupId))}let o=w.filter(p=>!r.has(p)),c=Math.max(1,2100-Ae-w.length);for(let p of M(m,c))await y.update(l).set({resolvedUserId:e}).output().where(Y(O(l.id,p),O(l.groupId,w)));if(o.length>0){let p=t();for(let f of U(o,Se))await y.insert(d).output().values(f.map(A=>({groupId:A,userId:e,createdAt:p,createdBy:h.actor})))}return o})}}}import{AUTHZ_TENANT_COLUMN as q}from"@aiquants/authz-drizzle";import{bigint as R,bit as se,datetime2 as _,foreignKey as C,int as ve,mssqlSchema as Te,nvarchar as T,primaryKey as L,uniqueIndex as z}from"drizzle-orm/mssql-core";var De="provider",cr=["TDGroupExternalLink","TDTenantMembershipGroup","TDAllowlistGroup","TDDirectorySyncRequest","TMAllowlist"];function mr(n,s){let g=Te(n),h=()=>({createdAt:_("created_at"),createdBy:T("created_by",{length:50}),updatedAt:_("updated_at"),updatedBy:T("updated_by",{length:50})}),a=()=>({createdAt:_("created_at"),createdBy:T("created_by",{length:50})}),i=g.table("TDGroupExternalLink",{id:R("id",{mode:"number"}).identity().notNull(),groupId:R("group_id",{mode:"number"}).notNull(),tenantId:T(q,{length:64}).notNull(),provider:T(De,{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:se("is_sync_enabled").notNull(),syncedAt:_("synced_at"),syncError:T("sync_error",{length:1e3}),vanishedObservations:ve("vanished_observations").notNull(),...h()},e=>[L({name:`${n}_TDGroupExternalLink_pk`,columns:[e.id]}),z(`${n}_TDGroupExternalLink_group_uq`).on(e.groupId),z(`${n}_TDGroupExternalLink_external_uq`).on(e.tenantId,e.provider,e.externalId),C({name:`${n}_TDGroupExternalLink_group_id_fk`,columns:[e.groupId],foreignColumns:[s.groupTable.id]})]),t=g.table("TDGroupMemberEmail",{id:R("id",{mode:"number"}).identity().notNull(),groupId:R("group_id",{mode:"number"}).notNull(),email:T("email",{length:320}).notNull(),externalUserId:T("external_user_id",{length:128}),resolvedUserId:R("resolved_user_id",{mode:"number"}),observedAt:_("observed_at").notNull(),...h()},e=>[L({name:`${n}_TDGroupMemberEmail_pk`,columns:[e.id]}),z(`${n}_TDGroupMemberEmail_group_email_uq`).on(e.groupId,e.email),C({name:`${n}_TDGroupMemberEmail_group_id_fk`,columns:[e.groupId],foreignColumns:[s.groupTable.id]})]),l=g.table("TDTenantMembershipGroup",{tenantId:T(q,{length:64}).notNull(),groupId:R("group_id",{mode:"number"}).notNull(),isBreakGlass:se("is_break_glass").notNull(),...a()},e=>[L({name:`${n}_TDTenantMembershipGroup_pk`,columns:[e.tenantId,e.groupId]}),C({name:`${n}_TDTenantMembershipGroup_group_id_fk`,columns:[e.groupId],foreignColumns:[s.groupTable.id]})]),u=g.table("TDAllowlistGroup",{id:R("id",{mode:"number"}).identity().notNull(),tenantId:T(q,{length:64}).notNull(),groupId:R("group_id",{mode:"number"}).notNull(),description:T("description",{length:512}),...a()},e=>[L({name:`${n}_TDAllowlistGroup_pk`,columns:[e.id]}),z(`${n}_TDAllowlistGroup_tenant_group_uq`).on(e.tenantId,e.groupId),C({name:`${n}_TDAllowlistGroup_group_id_fk`,columns:[e.groupId],foreignColumns:[s.groupTable.id]})]),d=g.table("TDDirectorySyncRequest",{id:R("id",{mode:"number"}).identity().notNull(),tenantId:T(q,{length:64}).notNull(),groupId:R("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}),...a()},e=>[L({name:`${n}_TDDirectorySyncRequest_pk`,columns:[e.id]})]),b=g.table("TMAllowlist",{id:R("id",{mode:"number"}).identity().notNull(),tenantId:T(q,{length:64}).notNull(),pattern:T("pattern",{length:256}).notNull(),type:T("type",{length:32}).notNull(),description:T("description",{length:512}),...a()},e=>[L({name:`${n}_TMAllowlist_pk`,columns:[e.id]}),z(`${n}_TMAllowlist_pattern_uq`).on(e.tenantId,e.pattern)]);return{TDGroupExternalLink:i,TDGroupMemberEmail:t,TDTenantMembershipGroup:l,TDAllowlistGroup:u,TDDirectorySyncRequest:d,TMAllowlist:b}}import{assertBlastRadiusPolicy as xe,reconcile as Ge}from"@aiquants/auth-directory-core";var X=(n,s=20)=>n.length<=s?n.join(", "):`${n.slice(0,s).join(", ")} \u307B\u304B ${n.length-s} \u4EF6`,ae={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,s)=>`\u4E0A\u6D41\u304C ${n} \u4EF6\u306E\u30E1\u30F3\u30D0\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F\u304C\u3001\u7A81\u5408\u7D50\u679C\u306F ${s} \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,s,g)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (${n})\u3002\u9023\u7D9A ${s} \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:(n,s)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C ${s} \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 (${X(n.offendingEmails)})\u3002`;let s=n.axis==="membership"?"\u6A29\u9650\u3092\u5931\u3046\u4EBA\u6570":"\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u306A\u304F\u306A\u308B\u4EBA\u6570",g=n.axis==="membership"?n.withheldProjectionRemoves.map(String):n.withheldLedgerDeletes;return[`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: ${s}\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: ${X(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: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 ${X(n.map(s=>String(s.userId)))})\u3002\u6B8B\u308A\u306E\u5909\u66F4\u306F\u9069\u7528\u6E08\u307F\u3067\u3059\u3002`,foreignProvider:(n,s)=>`\u3053\u306E\u30EA\u30F3\u30AF\u306E\u4F9B\u7D66\u5143\u306F ${n} \u3067\u3059\u304C\u3001\u5B9F\u884C\u4E2D\u306E\u540C\u671F\u306F ${s} \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 de=["aborted","missing","vanished","upstream-failed","local-failed","foreign-provider"];function wr(n){let{directory:s,repository:g,policy:h,now:a,logger:i}=n,t=n.messages??ae;if(xe(h),!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 l(e,m){try{await g.recordFailure(e,m)}catch(S){i.error(`[auth-directory] could not record the failure for ${e.externalKey}:`,S)}}async function u(e){try{let m=await s.getGroup(e.externalId);return m===null?{kind:"missing"}:{kind:"ok",group:m,members:await s.listGroupMembers(m,e.membershipMode)}}catch(m){return{kind:"failed",error:m instanceof Error?m:new Error(String(m))}}}async function d(e){let m=await g.recordVanishedObservation(e,t.groupMissing(e.externalKey,e.vanishedObservations+1,n.vanishedThreshold));return m<n.vanishedThreshold?(i.warn(`[auth-directory] ${e.externalKey} missing upstream (${m}/${n.vanishedThreshold})`),{kind:"missing",link:e,observations:m}):(await g.markGroupInactive(e),await l(e,t.groupVanished(e.externalKey,m)),i.warn(`[auth-directory] ${e.externalKey} deactivated after ${m} consecutive absences`),{kind:"vanished",link:e})}async function b(e){if(e.provider!==n.provider)return await l(e,t.foreignProvider(e.provider,n.provider)),i.warn(`[auth-directory] skipping ${e.externalKey}: belongs to provider ${e.provider}`),{kind:"foreign-provider",link:e};let m=await u(e);if(m.kind==="missing")return d(e);if(m.kind==="failed")return await l(e,t.upstreamUnreadable(m.error.message)),i.error(`[auth-directory] upstream read failed for ${e.externalKey}:`,m.error),{kind:"upstream-failed",link:e,error:m.error};try{let[S,y]=await Promise.all([g.loadGroupSnapshot(e),g.resolveMembers(m.members)]);if(y.length!==m.members.length){let c=t.resolverIncomplete(m.members.length,y.length);return await l(e,c),i.error(`[auth-directory] ${c}`),{kind:"local-failed",link:e,error:new Error(c)}}let v=Ge(S,y,h);if(v.aborted)return await l(e,t.planRefused(v.aborted)),i.warn(`[auth-directory] plan refused for ${e.externalKey}: ${v.aborted.reason}`),{kind:"aborted",link:e,plan:v};let w=v.ledgerUpserts.length>0||v.ledgerDeletes.length>0||v.projectionAdds.length>0||v.projectionRemoves.length>0,r=a(),o={refusals:[]};return w&&(o=await g.applyPlan(e,v,r)),await g.recordSuccess(e,m.group,r),o.refusals.length>0&&(await l(e,t.removalsRefused(o.refusals)),i.warn(`[auth-directory] ${o.refusals.length} removal(s) refused for ${e.externalKey}`)),v.unresolvedEmails.length>0&&i.info(`[auth-directory] ${e.externalKey}: ${v.unresolvedEmails.length} upstream member(s) have no local user yet`),w?{kind:"applied",link:e,plan:v}:{kind:"unchanged",link:e}}catch(S){let y=S instanceof Error?S:new Error(String(S));return await l(e,t.localWriteFailed(y.message)),i.error(`[auth-directory] local write failed for ${e.externalKey}:`,y),{kind:"local-failed",link:e,error:y}}}return{syncOne:b,async run(e={}){let m=await g.listSyncableLinks(e),S=[];for(let v of m)try{S.push(await b(v))}catch(w){let r=w instanceof Error?w:new Error(String(w));i.error(`[auth-directory] ${v.externalKey} ended abnormally:`,r),S.push({kind:"local-failed",link:v,error:r})}let y=S.filter(v=>de.includes(v.kind)).length;return i.info(`[auth-directory] run finished: ${S.length} link(s), ${y} unhealthy`),{outcomes:S,unhealthyCount:y}}}}import{assertTenantId as ke}from"@aiquants/authz-core";import{and as Ee,asc as ue,eq as le,isNull as Q}from"drizzle-orm";var Z=2e3;function Tr(n,s,g){let h=n,a=g.ensureConnection??(async()=>{}),i=s.TDDirectorySyncRequest,t=g.maxErrorLength??Z;if(!Number.isInteger(t)||t<1||t>Z)throw new TypeError(`[@aiquants/auth-directory-drizzle] maxErrorLength must be an integer within 1..${Z}, received ${String(g.maxErrorLength)}`);return{async claim(){await a();let l=await h.select({id:i.id}).from(i).where(Q(i.pickedAt)).orderBy(ue(i.id));for(let u of l){let b=(await h.update(i).set({pickedAt:g.now()}).output().where(Ee(le(i.id,Number(u.id)),Q(i.pickedAt))))?.[0];if(!b)continue;let e=String(b.tenant_id??b.tenantId??""),m=b.group_id??b.groupId??null;return{id:Number(b.id),tenantId:ke(e,"claim(row.tenantId)"),groupId:m===null?null:Number(m),requestedBy:String(b.requested_by??b.requestedBy??"")}}return null},async finish(l,u){await a();let d=u.error?u.error.slice(0,t):null;await h.update(i).set({finishedAt:g.now(),result:u.result,error:d}).output().where(le(i.id,l))},async listStranded(l){return await a(),(await h.select({id:i.id,pickedAt:i.pickedAt}).from(i).where(Q(i.finishedAt)).orderBy(ue(i.id))).filter(d=>d.pickedAt!==null&&new Date(d.pickedAt)<l).map(d=>Number(d.id))}}}import{normalizeAuthEmail as pe}from"@aiquants/auth-core";import{assertTenantId as ce,assertTenantMatch as Re}from"@aiquants/authz-core";import{and as P,eq as x,inArray as ee,isNull as Me,notInArray as _e,or as Pe}from"drizzle-orm";var Le=7,$e=4;function Mr(n,s,g,h){let a=n,i=h.ensureConnection??(async()=>{}),{TDGroupExternalLink:t,TDGroupMemberEmail:l}=s,{TMGroup:u,TDGroupMember:d}=g,b=h.actor;return{async listSyncableLinks(e){await i();let m=[x(t.isSyncEnabled,!0)];return e.tenantId!==void 0&&m.push(x(t.tenantId,ce(e.tenantId,"listSyncableLinks(tenantId)"))),e.groupId!==void 0&&m.push(x(t.groupId,e.groupId)),(await a.select({linkId:t.id,groupId:t.groupId,linkTenantId:t.tenantId,groupTenantId:u.tenantId,groupKey:u.groupKey,isGroupActive:u.isActive,provider:t.provider,externalId:t.externalId,externalKey:t.externalKey,membershipMode:t.membershipMode,vanishedObservations:t.vanishedObservations}).from(t).innerJoin(u,x(u.id,t.groupId)).where(P(...m))).map(y=>(Re(String(y.groupTenantId),String(y.linkTenantId),"listSyncableLinks(link.tenantId vs TMGroup.tenant_id)"),{linkId:Number(y.linkId),groupId:Number(y.groupId),groupKey:String(y.groupKey),tenantId:String(y.groupTenantId),provider:String(y.provider),externalId:String(y.externalId),externalKey:String(y.externalKey),membershipMode:N(String(y.membershipMode)),isGroupActive:!!y.isGroupActive,vanishedObservations:Number(y.vanishedObservations??0)}))},async loadGroupSnapshot(e){await i();let[m,S]=await Promise.all([a.select({email:l.email,externalUserId:l.externalUserId,resolvedUserId:l.resolvedUserId}).from(l).where(x(l.groupId,e.groupId)),a.select({userId:d.userId}).from(d).where(x(d.groupId,e.groupId))]);return{ledger:m.map(y=>({email:String(y.email),...y.externalUserId?{externalUserId:String(y.externalUserId)}:{},resolvedUserId:y.resolvedUserId===null?null:Number(y.resolvedUserId)})),projectedUserIds:S.map(y=>Number(y.userId))}},resolveMembers(e){return h.memberResolver.resolve(e)},async applyPlan(e,m,S){ce(e.tenantId,"applyPlan(link.tenantId)"),await i(),(m.ledgerUpserts.length>0||m.projectionAdds.length>0)&&await a.transaction(async w=>{let r=m.ledgerUpserts.map(p=>({...p,email:pe(p.email)})),o=new Map;for(let p of M(r.map(f=>f.email))){let f=await w.select({id:l.id,email:l.email}).from(l).where(P(x(l.groupId,e.groupId),ee(l.email,p)));for(let A of f)o.set(String(A.email),Number(A.id))}let c=r.filter(p=>!o.has(p.email));for(let p of U(c,Le))await w.insert(l).output().values(p.map(f=>({groupId:e.groupId,email:f.email,externalUserId:f.externalUserId??null,resolvedUserId:f.resolvedUserId,observedAt:S,createdAt:S,createdBy:b})));for(let p of r){let f=o.get(p.email);f!==void 0&&await w.update(l).set({externalUserId:p.externalUserId??null,resolvedUserId:p.resolvedUserId,observedAt:S,updatedAt:S,updatedBy:b}).output().where(x(l.id,f))}for(let p of U(m.projectionAdds,$e)){let f=new Set((await w.select({userId:d.userId}).from(d).where(P(x(d.groupId,e.groupId),ee(d.userId,p)))).map(E=>Number(E.userId))),A=p.filter(E=>!f.has(E));A.length>0&&await w.insert(d).output().values(A.map(E=>({groupId:e.groupId,userId:E,createdAt:S,createdBy:b})))}});let y=[];for(let w of m.projectionRemoves)try{await h.lockoutGuard.guardedWrite(e.tenantId,{removeGroupMembership:{groupId:e.groupId,userId:w}},"remove-group-member",r=>r.delete(d).where(P(x(d.groupId,e.groupId),x(d.userId,w))))}catch(r){y.push({userId:w,applied:!1,refusal:r instanceof Error?r.message:String(r)})}let v=y.map(w=>w.userId);return m.ledgerDeletes.length>0&&await a.transaction(async w=>{for(let r of M(m.ledgerDeletes)){let o=v.length>0?Pe(Me(l.resolvedUserId),_e(l.resolvedUserId,v)):void 0;await w.delete(l).where(P(x(l.groupId,e.groupId),ee(l.email,r),...o?[o]:[]))}}),{refusals:y}},async recordSuccess(e,m,S){await i(),await a.transaction(async y=>{await y.update(t).set({syncedAt:S,syncError:null,vanishedObservations:0,externalId:m.externalId,externalKey:pe(m.externalKey),updatedAt:S,updatedBy:b}).output().where(x(t.id,e.linkId)),e.isGroupActive||await y.update(u).set({isActive:!0,updatedAt:S,updatedBy:b}).output().where(P(x(u.id,e.groupId),x(u.tenantId,e.tenantId)))})},async recordFailure(e,m){await i(),await a.update(t).set({syncError:m.slice(0,1e3),updatedAt:new Date,updatedBy:b}).output().where(x(t.id,e.linkId))},async recordVanishedObservation(e,m){await i();let S=e.vanishedObservations+1;return await a.update(t).set({vanishedObservations:S,syncError:m.slice(0,1e3),updatedAt:new Date,updatedBy:b}).output().where(x(t.id,e.linkId)),S},async markGroupInactive(e){await i(),await a.update(u).set({isActive:!1,updatedAt:new Date,updatedBy:b}).output().where(P(x(u.id,e.groupId),x(u.tenantId,e.tenantId)))}}}export{De as AUTH_DIRECTORY_PROVIDER_COLUMN,cr as AUTH_DIRECTORY_TENANT_SCOPED_TABLES,tr as MAX_IN_LIST,oe as MAX_PARAMETERS,re as MEMBERSHIP_MODES,de as UNHEALTHY_SYNC_OUTCOMES,qe as assertGroupMayBeLinked,N as assertMembershipMode,M as chunk,U as chunkByParameters,We as createDirectoryLinkStore,wr as createDirectorySync,Mr as createDirectorySyncRepository,dr as createPromotionRepository,Tr as createSyncRequestQueue,Ne as defaultExternalGroupGuardMessage,ae as defaultSyncMessages,mr as defineAuthDirectorySchema,B as findLedgerRowsForIdentity,er as promoteLedgerForUser,Ue 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