@aiquants/auth-directory-drizzle 0.8.0 → 0.8.1
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.d.mts +177 -996
- package/dist/index.d.ts +177 -996
- package/dist/index.js +0 -1
- package/dist/index.mjs +0 -1
- package/package.json +9 -9
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
1
|
"use strict";var ae=Object.defineProperty;var ke=Object.getOwnPropertyDescriptor;var Re=Object.getOwnPropertyNames;var Ee=Object.prototype.hasOwnProperty;var Ge=(e,n)=>{for(var g in n)ae(e,g,{get:n[g],enumerable:!0})},Me=(e,n,g,d)=>{if(n&&typeof n=="object"||typeof n=="function")for(let l of Re(n))!Ee.call(e,l)&&l!==g&&ae(e,l,{get:()=>n[l],enumerable:!(d=ke(n,l))||d.enumerable});return e};var Ce=e=>Me(ae({},"__esModule",{value:!0}),e);var er={};Ge(er,{AUTH_DIRECTORY_PROVIDER_COLUMN:()=>ne,AUTH_DIRECTORY_TENANT_SCOPED_TABLES:()=>We,MEMBERSHIP_MODES:()=>ue,NEWLY_UNHEALTHY_SYNC_OUTCOMES:()=>oe,STRANDED_REASON:()=>Ae,UNHEALTHY_SYNC_OUTCOMES:()=>me,assertGroupMayBeLinked:()=>$e,assertMembershipMode:()=>B,classifySyncFailure:()=>H,createDirectoryCatalogReader:()=>Ke,createDirectoryCatalogWriter:()=>Le,createDirectoryLinkStore:()=>Ue,createDirectorySync:()=>Je,createDirectorySyncRepository:()=>Ze,createPromotionRepository:()=>je,createSyncRequestQueue:()=>Ye,defaultExternalGroupGuardMessage:()=>Oe,defaultSyncMessages:()=>pe,defineAuthDirectorySchema:()=>He,findLedgerRowsForIdentity:()=>W,promoteLedgerForUser:()=>ze,transactionRunner:()=>O,withExternalGroupGuards:()=>Ne});module.exports=Ce(er);var se=require("@aiquants/authz-core"),Q=require("@aiquants/mssql"),_=require("drizzle-orm");var ge=require("@aiquants/mssql");function O(e,n){return g=>(0,ge.runWithTransactionRetry)(d=>e.transaction(l=>d(()=>g(l))),n.transactionRetry)}var _e=10,N={tenantId:64,provider:32,externalId:256,externalKey:320,name:256,description:1e3,actor:50},he=2147483647;function Pe(e){if(e===void 0||!Number.isFinite(e))return null;let n=Math.trunc(e);return n<0?null:n>he?he:n}function F(e,n,g){let d=e.trim();if(d==="")throw new TypeError(`[@aiquants/auth-directory-drizzle] ${g} must not be blank`);if(d.length>n)throw new TypeError(`[@aiquants/auth-directory-drizzle] ${g} exceeds its column length (${n}); truncating it would change which rows it owns`);return d}function Le(e,n,g){let d=e,l=g.ensureConnection??(async()=>{}),s=O(d,g),f=n.TDDirectoryCatalog,v=n.TMDirectoryCatalogObservation,w=b=>{let a=new Map;for(let t of b)a.set(t.externalId,t);return[...a.values()]};return{async replace(b,a,t){await l();let i=F((0,se.assertTenantId)(b,"replace(tenantId)"),N.tenantId,"tenantId"),r=F(a,N.provider,"provider"),h=F(g.actor,N.actor,"actor"),u=g.now(),I=w(t.map(A=>({tenantId:i,provider:r,externalId:A.externalId.slice(0,N.externalId),externalKey:A.externalKey.slice(0,N.externalKey),name:A.name.slice(0,N.name),description:A.description===void 0?null:A.description.slice(0,N.description),memberCount:Pe(A.memberCount),observedAt:u,createdAt:u,createdBy:h})));return await s(async A=>{await A.delete(f).where((0,_.and)((0,_.eq)(f.tenantId,i),(0,_.eq)(f.provider,r)));for(let D of(0,Q.chunkByParameters)(I,_e))await A.insert(f).values(D);await A.delete(v).where((0,_.and)((0,_.eq)(v.tenantId,i),(0,_.eq)(v.provider,r))),await A.insert(v).values([{tenantId:i,provider:r,observedAt:u,entryCount:I.length,createdAt:u,createdBy:h,updatedAt:u,updatedBy:h}])}),I.length}}}function Ke(e,n,g={}){let d=e,l=g.ensureConnection??(async()=>{}),s=n.TDDirectoryCatalog,f=n.TMDirectoryCatalogObservation;return{async read(v,w){await l();let b=F((0,se.assertTenantId)(v,"read(tenantId)"),N.tenantId,"tenantId"),a=F(w,N.provider,"provider"),t=await d.select({observedAt:f.observedAt,entryCount:f.entryCount,externalId:s.externalId,externalKey:s.externalKey,name:s.name,description:s.description,memberCount:s.memberCount}).from(f).leftJoin(s,(0,_.and)((0,_.eq)(s.tenantId,f.tenantId),(0,_.eq)(s.provider,f.provider))).where((0,_.and)((0,_.eq)(f.tenantId,b),(0,_.eq)(f.provider,a))).orderBy((0,_.asc)(s.name)),i=t.filter(u=>u.externalId!==null).map(u=>({externalId:String(u.externalId),externalKey:String(u.externalKey),name:String(u.name),...u.description===null?{}:{description:u.description},...u.memberCount===null?{}:{memberCount:Number(u.memberCount)}})),r=t[0];if(r===void 0)return{groups:i,observedAt:null};if(Number(r.entryCount)!==i.length)throw new Error(`[@aiquants/auth-directory-drizzle] catalog is inconsistent for (${b}, ${a}): the observation records ${r.entryCount} entries but ${i.length} rows are present`);let h=(0,Q.toInstant)(r.observedAt);if(!Number.isFinite(h.getTime()))throw new Error(`[@aiquants/auth-directory-drizzle] catalog observation for (${b}, ${a}) has an unreadable observed_at`);return{groups:i,observedAt:h}}}}var be=require("@aiquants/authz-core"),Oe=(e,n)=>`\u5916\u90E8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u9023\u643A\u30B0\u30EB\u30FC\u30D7 (${n}) \u306B\u306F\u7BA1\u7406\u30ED\u30FC\u30EB (${e}) \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 z(e,n,g,d){return new Set(e.filter(l=>n.has(l.roleId)&&l.appKey===g&&l.resourceKey===d).map(l=>l.action))}function X(e,n){return new Set(e.filter(g=>g.groupId===n).map(g=>g.roleId))}var j=e=>be.AUTHZ_ACTIONS.every(n=>e.has(n)),ee=(e,n)=>e.find(g=>g.roleId===n)?.roleKey??`#${n}`;function Z(e,n,g,d){let l=[...n].find(f=>j(z(e,new Set([f]),g,d)));return l!==void 0?ee(e,l):[...n].filter(f=>z(e,new Set([f]),g,d).size>0).map(f=>ee(e,f)).join(" + ")}function Ne(e,n){return fe(e,n)}function fe(e,n){let g=e;return{...e,withTransaction:(d,l)=>g.withTransaction((s,f)=>d(fe(s,n),f),l),async assignGroupRole(d,l,s,f){await g.withTransaction(async(v,w)=>{let b=await v.listGrants(d),a=X(await v.listGroupRoles(d),l);if(a.add(s),j(z(b,a,n.adminAppKey,n.adminResourceKey))){let t=await n.linkLookup.externalGroupKey(d,l,w);if(t!==null)throw new Error(n.message(Z(b,a,n.adminAppKey,n.adminResourceKey),t))}await v.assignGroupRole(d,l,s,f)})},async updateGrant(d,l,s,f){let v=s.action;if(v===void 0)return g.updateGrant(d,l,s,f);await g.withTransaction(async(w,b)=>{let a=await w.listGrants(d),t=a.find(h=>h.id===l);if(t===void 0)return w.updateGrant(d,l,s,f);let r=(await w.listResources(d)).find(h=>h.id===t.resourceId);if(r?.appKey===n.adminAppKey&&r?.resourceKey===n.adminResourceKey){let h=await w.listGroupRoles(d);for(let u of new Set(h.filter(I=>I.roleId===t.roleId).map(I=>I.groupId))){let I=X(h,u),A=z(a.filter(x=>x.id!==l),I,n.adminAppKey,n.adminResourceKey);if(I.has(t.roleId)&&A.add(v),!j(A))continue;let D=await n.linkLookup.externalGroupKey(d,u,b);if(D!==null)throw new Error(n.message(Z(a,I,n.adminAppKey,n.adminResourceKey)||ee(a,t.roleId),D))}}await w.updateGrant(d,l,s,f)})},async createGrant(d,l,s){await g.withTransaction(async(f,v)=>{let b=(await f.listResources(d)).find(a=>a.id===l.resourceId);if(b?.appKey===n.adminAppKey&&b?.resourceKey===n.adminResourceKey){let a=await f.listGrants(d),t=await f.listGroupRoles(d);for(let i of new Set(t.filter(r=>r.roleId===l.roleId).map(r=>r.groupId))){let r=z(a,X(t,i),n.adminAppKey,n.adminResourceKey);if(r.add(l.action),!j(r))continue;let h=await n.linkLookup.externalGroupKey(d,i,v);if(h!==null)throw new Error(n.message(Z(a,X(t,i),n.adminAppKey,n.adminResourceKey)||ee(a,l.roleId),h))}}await f.createGrant(d,l,s)})}}}async function $e(e,n,g,d){let l=(await e.listGroupRoles(n)).filter(w=>w.groupId===g);if(l.length===0)return;let s=await e.listGrants(n),f=new Set(l.map(w=>w.roleId));if(!j(z(s,f,d.adminAppKey,d.adminResourceKey)))return;let v=l[0];if(v)throw new Error(d.message(Z(s,f,d.adminAppKey,d.adminResourceKey),v.groupKey))}var $=require("drizzle-orm");async function W(e,n,g){let{ledger:d,group:l}=n,{tenantId:s,externalUserId:f,email:v}=g;if(f===null&&v==="")return[];let w={ledgerId:d.id,groupId:d.groupId,tenantId:l.tenantId,externalUserId:d.externalUserId},b=async h=>(await e.select(w).from(d).innerJoin(l,(0,$.and)((0,$.eq)(l.id,d.groupId),(0,$.eq)(l.tenantId,s),(0,$.eq)(l.isActive,!0))).where(h)).map(I=>({ledgerId:Number(I.ledgerId),groupId:Number(I.groupId),tenantId:String(I.tenantId),externalUserId:I.externalUserId===null?null:String(I.externalUserId)})),a=h=>{let u=new Map;for(let I of h)u.set(I.ledgerId,{ledgerId:I.ledgerId,groupId:I.groupId,tenantId:I.tenantId});return[...u.values()].sort((I,A)=>I.ledgerId-A.ledgerId)},t=f===null?[]:await b((0,$.eq)(d.externalUserId,f));if(v==="")return a(t);let i=await b((0,$.eq)(d.email,v)),r=h=>h.externalUserId!==null&&h.externalUserId!==f;return a([...t,...i.filter(h=>!r(h))])}var re=require("@aiquants/auth-core"),we=require("@aiquants/auth-directory-core"),M=require("@aiquants/authz-core"),de=require("@aiquants/mssql"),c=require("drizzle-orm");var ue=["direct","transitive"];function B(e){if(!ue.includes(e))throw new TypeError(`[@aiquants/auth-directory-drizzle] membershipMode must be one of ${ue.join(" | ")}, received ${JSON.stringify(e)}`);return e}function qe(e){let n=typeof e=="object"&&e!==null&&"number"in e?e.number:void 0;return n===2601||n===2627}function Ue(e,n,g,d){let l=e,s=d.ensureConnection??(async()=>{}),f=O(l,d),v=d.policyCacheTtlMs??0;if(!Number.isFinite(v)||v<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] policyCacheTtlMs must be a non-negative number, received ${String(d.policyCacheTtlMs)}`);let w=new Map,b=o=>w.delete(o),{TDGroupExternalLink:a,TDGroupMemberEmail:t,TDTenantMembershipGroup:i,TDAllowlistGroup:r,TDDirectorySyncRequest:h}=n,{TMGroup:u,TDGroupMember:I}=g;async function A(o,m,y){if((await o.select({groupId:i.groupId}).from(i).where((0,c.and)((0,c.eq)(i.tenantId,m),(0,c.eq)(i.groupId,y),(0,c.eq)(i.isBreakGlass,!0)))).length>0)throw new Error(`Group ${y} is a break-glass proof group; a break-glass proof path must be one the synchronization can never touch.`)}async function D(o,m,y){let[S]=await o.select({id:u.id}).from(u).where((0,c.and)((0,c.eq)(u.id,y),(0,c.eq)(u.tenantId,m)));if(!S)throw new Error(`Group not found in tenant: ${y}`)}function x(o,m){return{linkId:Number(o.linkId),groupId:Number(o.groupId),tenantId:String(o.tenantId),provider:String(o.provider),externalId:String(o.externalId),externalKey:String(o.externalKey),membershipMode:B(String(o.membershipMode)),isSyncEnabled:!!o.isSyncEnabled,...o.syncedAt?{syncedAt:(0,de.toInstant)(o.syncedAt).toISOString()}:{},...o.syncError?{syncError:String(o.syncError)}:{},unresolvedCount:m}}async function P(o,m){let y=await o.select({groupId:t.groupId,n:(0,c.count)()}).from(t).innerJoin(u,(0,c.and)((0,c.eq)(u.id,t.groupId),(0,c.eq)(u.tenantId,m))).where((0,c.isNull)(t.resolvedUserId)).groupBy(t.groupId);return new Map(y.map(S=>[Number(S.groupId),Number(S.n)]))}return{async listLinks(o){(0,M.assertTenantId)(o,"listLinks(tenantId)"),await s();let m=await l.select({linkId:a.id,groupId:a.groupId,tenantId:a.tenantId,provider:a.provider,externalId:a.externalId,externalKey:a.externalKey,membershipMode:a.membershipMode,isSyncEnabled:a.isSyncEnabled,syncedAt:a.syncedAt,syncError:a.syncError}).from(a).where((0,c.eq)(a.tenantId,o)).orderBy((0,c.asc)(a.groupId)),y=await P(l,o);return m.map(S=>((0,M.assertTenantMatch)(o,String(S.tenantId),"listLinks(row.tenantId)"),x(S,y.get(Number(S.groupId))??0)))},async externalGroupKey(o,m,y){(0,M.assertTenantId)(o,"externalGroupKey(tenantId)"),await s();let[S]=await(y??l).select({externalKey:a.externalKey}).from(a).where((0,c.and)((0,c.eq)(a.groupId,m),(0,c.eq)(a.tenantId,o)));return S?String(S.externalKey):null},async linkGroup(o,m,y,S){(0,M.assertTenantId)(o,"linkGroup(tenantId)");let k=B(y.membershipMode),R=y.externalId.trim(),C=(0,re.normalizeAuthEmail)(y.externalKey);if(R===""||C==="")throw new TypeError("[@aiquants/auth-directory-drizzle] externalId and externalKey must be non-empty");await s(),await d.linkable.assertLinkableAndWrite(o,m,async V=>{let E=V;await D(E,o,m),await A(E,o,m),await E.insert(a).output().values({groupId:m,tenantId:o,provider:y.provider,externalId:R,externalKey:C,membershipMode:k,isSyncEnabled:!0,vanishedObservations:0,createdAt:new Date,createdBy:S})}),b(o)},async unlinkGroup(o,m){(0,M.assertTenantId)(o,"unlinkGroup(tenantId)"),await s(),await f(async y=>{await D(y,o,m),await y.delete(t).where((0,c.eq)(t.groupId,m)),await y.delete(r).where((0,c.and)((0,c.eq)(r.groupId,m),(0,c.eq)(r.tenantId,o))),await y.delete(a).where((0,c.and)((0,c.eq)(a.groupId,m),(0,c.eq)(a.tenantId,o)))}),b(o)},async setSyncEnabled(o,m,y,S){(0,M.assertTenantId)(o,"setSyncEnabled(tenantId)"),await s(),await l.update(a).set({isSyncEnabled:y,updatedAt:new Date,updatedBy:S}).output().where((0,c.and)((0,c.eq)(a.groupId,m),(0,c.eq)(a.tenantId,o)))},async requestSync(o,m,y){return(0,M.assertTenantId)(o,"requestSync(tenantId)"),await s(),l.transaction(async S=>{if(m!==null&&await D(S,o,m),(await S.select({id:h.id}).from(h).where((0,c.and)((0,c.eq)(h.tenantId,o),m===null?(0,c.isNull)(h.groupId):(0,c.eq)(h.groupId,m),(0,c.isNull)(h.pickedAt)))).length>0)return"already-pending";try{await S.insert(h).output().values({tenantId:o,groupId:m,requestedBy:y,requestedAt:new Date,createdAt:new Date,createdBy:y})}catch(R){if(!qe(R))throw R;return"already-pending"}return"queued"})},async getStatus(o){(0,M.assertTenantId)(o,"getStatus(tenantId)"),await s();let m=await l.select({groupId:a.groupId,isSyncEnabled:a.isSyncEnabled,syncedAt:a.syncedAt,syncError:a.syncError}).from(a).where((0,c.eq)(a.tenantId,o)),[y]=await l.select({n:(0,c.count)()}).from(h).where((0,c.and)((0,c.eq)(h.tenantId,o),(0,c.isNull)(h.finishedAt))),S=m.filter(E=>!!E.isSyncEnabled),k=S.map(E=>E.syncedAt?(0,de.toInstant)(E.syncedAt).getTime():null),R=k.length>0&&k.every(E=>E!==null)?Math.min(...k):null,V=m.length>0&&S.length===0||S.length>0&&(R===null||Date.now()-R>d.stalenessThresholdSec*1e3);return{isEnabled:d.isEnabled,provider:d.provider,linkedGroupCount:m.length,...R!==null?{oldestSyncedAt:new Date(R).toISOString()}:{},isStale:V,pausedGroupCount:m.length-S.length,stalenessThresholdSec:d.stalenessThresholdSec,erroredGroupIds:m.filter(E=>!!E.syncError).map(E=>Number(E.groupId)).sort((E,xe)=>E-xe),pendingRequestCount:Number(y?.n??0)}},async loadTenantMembershipPolicy(o){(0,M.assertTenantId)(o,"loadTenantMembershipPolicy(tenantId)");let m=v>0?w.get(o):void 0;if(m&&Date.now()-m.loadedAt<v)return m.policy;await s();let y=await l.select({groupId:i.groupId,isBreakGlass:i.isBreakGlass,externalLinkId:a.id}).from(i).innerJoin(u,(0,c.and)((0,c.eq)(u.id,i.groupId),(0,c.eq)(u.tenantId,o))).leftJoin(a,(0,c.and)((0,c.eq)(a.groupId,i.groupId),(0,c.eq)(a.tenantId,o))).where((0,c.eq)(i.tenantId,o));if(y.length===0){let C={kind:"no-proof-required"};return v>0&&w.set(o,{policy:C,loadedAt:Date.now()}),C}let S=y.map(C=>Number(C.groupId)),k=y.filter(C=>!!C.isBreakGlass);for(let C of k)if(C.externalLinkId!==null)throw new Error(`Break-glass group ${Number(C.groupId)} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`);let R={kind:"require-proof",proofGroupIds:S,breakGlassGroupIds:k.map(C=>Number(C.groupId))};return(0,we.assertTenantMembershipPolicy)(R,`tenant ${o}`),v>0&&w.set(o,{policy:R,loadedAt:Date.now()}),R},async setTenantMembershipGroup(o,m,y,S){(0,M.assertTenantId)(o,"setTenantMembershipGroup(tenantId)"),await s(),await f(async k=>{if(await D(k,o,m),y){let[C]=await k.select({id:a.id}).from(a).where((0,c.and)((0,c.eq)(a.tenantId,o),(0,c.eq)(a.groupId,m)));if(C)throw new Error(`Group ${m} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`)}let R=await k.select({groupId:i.groupId}).from(i).where((0,c.and)((0,c.eq)(i.tenantId,o),(0,c.eq)(i.groupId,m)));if(!y&&!(await k.select({groupId:i.groupId,isBreakGlass:i.isBreakGlass}).from(i).where((0,c.eq)(i.tenantId,o))).filter(E=>Number(E.groupId)!==m).some(E=>!!E.isBreakGlass))throw new Error(`Declaring group ${m} as ordinary proof would leave tenant ${o} with a membership proof but no break-glass path; declare a locally-managed break-glass group first.`);if(R.length>0){await k.update(i).set({isBreakGlass:y,updatedAt:new Date,updatedBy:S}).output().where((0,c.and)((0,c.eq)(i.tenantId,o),(0,c.eq)(i.groupId,m)));return}await k.insert(i).output().values({tenantId:o,groupId:m,isBreakGlass:y,createdAt:new Date,createdBy:S})}),b(o)},async removeTenantMembershipGroup(o,m){(0,M.assertTenantId)(o,"removeTenantMembershipGroup(tenantId)"),await s(),await f(async y=>{let k=(await y.select({groupId:i.groupId,isBreakGlass:i.isBreakGlass}).from(i).where((0,c.eq)(i.tenantId,o))).filter(R=>Number(R.groupId)!==m);if(k.length>0&&!k.some(R=>!!R.isBreakGlass))throw new Error(`Removing group ${m} would leave tenant ${o} with a membership proof but no break-glass path.`);await y.delete(i).where((0,c.and)((0,c.eq)(i.tenantId,o),(0,c.eq)(i.groupId,m)))}),b(o)},async setGroupActive(o,m,y,S){(0,M.assertTenantId)(o,"setGroupActive(tenantId)"),await s(),await l.update(u).set({isActive:y,updatedAt:new Date,updatedBy:S}).output().where((0,c.and)((0,c.eq)(u.id,m),(0,c.eq)(u.tenantId,o)))},async listLedgerGroupIds(o,m){if((0,M.assertTenantId)(o,"listLedgerGroupIds(tenantId)"),!d.isEnabled)return[];await s();let y=await W(l,{ledger:t,group:u},{tenantId:o,externalUserId:m.externalUserId?.trim()||null,email:(0,re.normalizeAuthEmail)(m.email)});return[...new Set(y.map(S=>S.groupId))].sort((S,k)=>S-k)},async isEmailAllowedByGroup(o){if(!d.isEnabled)return!1;let m=(0,re.normalizeAuthEmail)(o);if(m==="")return!1;await s();let[y]=await l.select({id:t.id}).from(t).innerJoin(r,(0,c.eq)(r.groupId,t.groupId)).innerJoin(u,(0,c.and)((0,c.eq)(u.id,t.groupId),(0,c.eq)(u.isActive,!0))).where((0,c.eq)(t.email,m));return!!y},async listAllowlistGroups(o){return(0,M.assertTenantId)(o,"listAllowlistGroups(tenantId)"),await s(),(await l.select({id:r.id,groupId:r.groupId,groupKey:u.groupKey,groupName:u.name,description:r.description}).from(r).innerJoin(u,(0,c.and)((0,c.eq)(u.id,r.groupId),(0,c.eq)(u.tenantId,o))).where((0,c.eq)(r.tenantId,o)).orderBy((0,c.asc)(u.name))).map(y=>({id:Number(y.id),type:"group",groupId:Number(y.groupId),groupKey:String(y.groupKey),groupName:String(y.groupName),...y.description?{description:String(y.description)}:{},owner:{kind:"tenant",tenantId:o},readOnly:!1}))},async addAllowlistGroup(o,m,y,S){(0,M.assertTenantId)(o,"addAllowlistGroup(tenantId)"),await s(),await f(async k=>{await D(k,o,m);let[R]=await k.select({id:a.id}).from(a).where((0,c.and)((0,c.eq)(a.groupId,m),(0,c.eq)(a.tenantId,o)));if(!R)throw new Error(`Group ${m} is locally managed; only externally-sourced groups can gate sign-in, because the decision reads the upstream member ledger.`);await k.insert(r).output().values({tenantId:o,groupId:m,description:y??null,createdAt:new Date,createdBy:S})})},async removeAllowlistGroup(o,m){(0,M.assertTenantId)(o,"removeAllowlistGroup(tenantId)"),await s(),await l.delete(r).where((0,c.and)((0,c.eq)(r.id,m),(0,c.eq)(r.tenantId,o)))},async countProjectedMembers(o,m){(0,M.assertTenantId)(o,"countProjectedMembers(tenantId)"),await s();let[y]=await l.select({n:(0,c.count)()}).from(I).innerJoin(u,(0,c.and)((0,c.eq)(u.id,I.groupId),(0,c.eq)(u.tenantId,o))).where((0,c.eq)(I.groupId,m));return Number(y?.n??0)}}}var Ie=require("@aiquants/auth-core"),te=require("@aiquants/authz-core");async function ze(e,n){let g=(0,te.assertTenantId)(n.tenantId,"promoteLedgerForUser(tenantId)");if(!Number.isInteger(n.userId)||n.userId<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] userId must be a positive integer, received ${String(n.userId)}`);let d=(0,Ie.normalizeAuthEmail)(n.email),l=n.externalUserId?.trim()||null;if(l===null&&d==="")return{addedGroupIds:[],matchedLedgerIds:[]};let s=await e.findLedgerMatches({tenantId:g,externalUserId:l,email:d});if(s.length===0)return{addedGroupIds:[],matchedLedgerIds:[]};for(let b of s)(0,te.assertTenantMatch)(g,b.tenantId,"promoteLedgerForUser(findLedgerMatches row)");let f=[...new Set(s.map(b=>b.ledgerId))].sort((b,a)=>b-a),v=[...new Set(s.map(b=>b.groupId))].sort((b,a)=>b-a);return{addedGroupIds:[...await e.attachUserToGroups({tenantId:g,userId:n.userId,ledgerIds:f,groupIds:v})].sort((b,a)=>b-a),matchedLedgerIds:f}}var le=require("@aiquants/authz-core"),q=require("@aiquants/mssql"),L=require("drizzle-orm");var Be=4,Fe=100;function je(e,n,g,d){let l=e,s=d.ensureConnection??(async()=>{}),f=O(l,d),v=d.now??(()=>new Date),w=n.TDGroupMemberEmail,{TMGroup:b,TDGroupMember:a}=g;return{async findLedgerMatches(t){return(0,le.assertTenantId)(t.tenantId,"findLedgerMatches(tenantId)"),await s(),W(l,{ledger:w,group:b},t)},async attachUserToGroups({tenantId:t,userId:i,ledgerIds:r,groupIds:h}){return(0,le.assertTenantId)(t,"attachUserToGroups(tenantId)"),await s(),r.length===0?[]:f(async u=>{let I=new Set;for(let o of(0,q.chunk)(h)){let m=await u.select({id:b.id}).from(b).where((0,L.and)((0,L.inArray)(b.id,o),(0,L.eq)(b.tenantId,t),(0,L.eq)(b.isActive,!0)));for(let y of m)I.add(Number(y.id))}let A=h.filter(o=>I.has(o));if(A.length===0)return[];let D=new Set;for(let o of(0,q.chunk)(A)){let m=await u.select({groupId:a.groupId}).from(a).where((0,L.and)((0,L.eq)(a.userId,i),(0,L.inArray)(a.groupId,o)));for(let y of m)D.add(Number(y.groupId))}let x=A.filter(o=>!D.has(o)),P=Math.max(1,q.MAX_PARAMETERS-Fe-A.length);for(let o of(0,q.chunk)(r,P))await u.update(w).set({resolvedUserId:i}).output().where((0,L.and)((0,L.inArray)(w.id,o),(0,L.inArray)(w.groupId,A)));if(x.length>0){let o=v();for(let m of(0,q.chunkByParameters)(x,Be))await u.insert(a).output().values(m.map(y=>({groupId:y,userId:i,createdAt:o,createdBy:d.actor})))}return x})}}}var U=require("@aiquants/authz-drizzle"),p=require("drizzle-orm/mssql-core"),ne="provider",We=["TDGroupExternalLink","TDTenantMembershipGroup","TDAllowlistGroup","TDDirectorySyncRequest","TDDirectoryCatalog","TMDirectoryCatalogObservation","TMAllowlist"];function He(e,n){let g=(0,p.mssqlSchema)(e),d=()=>({createdAt:(0,p.datetime2)("created_at"),createdBy:(0,p.nvarchar)("created_by",{length:50}),updatedAt:(0,p.datetime2)("updated_at"),updatedBy:(0,p.nvarchar)("updated_by",{length:50})}),l=()=>({createdAt:(0,p.datetime2)("created_at"),createdBy:(0,p.nvarchar)("created_by",{length:50})}),s=g.table("TDGroupExternalLink",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),tenantId:(0,p.nvarchar)(U.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(ne,{length:32}).notNull(),externalId:(0,p.nvarchar)("external_id",{length:256}).notNull(),externalKey:(0,p.nvarchar)("external_key",{length:320}).notNull(),membershipMode:(0,p.nvarchar)("membership_mode",{length:16}).notNull(),isSyncEnabled:(0,p.bit)("is_sync_enabled").notNull(),syncedAt:(0,p.datetime2)("synced_at"),syncError:(0,p.nvarchar)("sync_error",{length:1e3}),vanishedObservations:(0,p.int)("vanished_observations").notNull(),...d()},r=>[(0,p.primaryKey)({name:`${e}_TDGroupExternalLink_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TDGroupExternalLink_group_uq`).on(r.groupId),(0,p.uniqueIndex)(`${e}_TDGroupExternalLink_external_uq`).on(r.tenantId,r.provider,r.externalId),(0,p.foreignKey)({name:`${e}_TDGroupExternalLink_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),f=g.table("TDGroupMemberEmail",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),email:(0,p.nvarchar)("email",{length:320}).notNull(),externalUserId:(0,p.nvarchar)("external_user_id",{length:128}),resolvedUserId:(0,p.bigint)("resolved_user_id",{mode:"number"}),observedAt:(0,p.datetime2)("observed_at").notNull(),...d()},r=>[(0,p.primaryKey)({name:`${e}_TDGroupMemberEmail_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TDGroupMemberEmail_group_email_uq`).on(r.groupId,r.email),(0,p.foreignKey)({name:`${e}_TDGroupMemberEmail_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),v=g.table("TDTenantMembershipGroup",{tenantId:(0,p.nvarchar)(U.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),isBreakGlass:(0,p.bit)("is_break_glass").notNull(),...l()},r=>[(0,p.primaryKey)({name:`${e}_TDTenantMembershipGroup_pk`,columns:[r.tenantId,r.groupId]}),(0,p.foreignKey)({name:`${e}_TDTenantMembershipGroup_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),w=g.table("TDAllowlistGroup",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(U.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),description:(0,p.nvarchar)("description",{length:512}),...l()},r=>[(0,p.primaryKey)({name:`${e}_TDAllowlistGroup_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TDAllowlistGroup_tenant_group_uq`).on(r.tenantId,r.groupId),(0,p.foreignKey)({name:`${e}_TDAllowlistGroup_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),b=g.table("TDDirectorySyncRequest",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(U.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}),requestedBy:(0,p.nvarchar)("requested_by",{length:320}).notNull(),requestedAt:(0,p.datetime2)("requested_at").notNull(),pickedAt:(0,p.datetime2)("picked_at"),finishedAt:(0,p.datetime2)("finished_at"),result:(0,p.nvarchar)("result",{length:32}),error:(0,p.nvarchar)("error",{length:2e3}),...l()},r=>[(0,p.primaryKey)({name:`${e}_TDDirectorySyncRequest_pk`,columns:[r.id]})]),a=g.table("TMAllowlist",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(U.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),pattern:(0,p.nvarchar)("pattern",{length:256}).notNull(),type:(0,p.nvarchar)("type",{length:32}).notNull(),description:(0,p.nvarchar)("description",{length:512}),...l()},r=>[(0,p.primaryKey)({name:`${e}_TMAllowlist_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TMAllowlist_pattern_uq`).on(r.tenantId,r.pattern)]),t=g.table("TDDirectoryCatalog",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(U.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(ne,{length:32}).notNull(),externalId:(0,p.nvarchar)("external_id",{length:256}).notNull(),externalKey:(0,p.nvarchar)("external_key",{length:320}).notNull(),name:(0,p.nvarchar)("name",{length:256}).notNull(),description:(0,p.nvarchar)("description",{length:1e3}),memberCount:(0,p.int)("member_count"),observedAt:(0,p.datetime2)("observed_at").notNull(),...l()},r=>[(0,p.primaryKey)({name:`${e}_TDDirectoryCatalog_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TDDirectoryCatalog_external_uq`).on(r.tenantId,r.provider,r.externalId)]),i=g.table("TMDirectoryCatalogObservation",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(U.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(ne,{length:32}).notNull(),observedAt:(0,p.datetime2)("observed_at").notNull(),entryCount:(0,p.int)("entry_count").notNull(),...d()},r=>[(0,p.primaryKey)({name:`${e}_TMDirectoryCatalogObservation_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TMDirectoryCatalogObservation_target_uq`).on(r.tenantId,r.provider)]);return{TDGroupExternalLink:s,TDGroupMemberEmail:f,TDTenantMembershipGroup:v,TDAllowlistGroup:w,TDDirectorySyncRequest:b,TDDirectoryCatalog:t,TMDirectoryCatalogObservation:i,TMAllowlist:a}}var ie=require("@aiquants/auth-directory-core");function H(e){if(e instanceof Error&&(e.name==="AbortError"||e.name==="TimeoutError"))return"timeout";let n=typeof e=="object"&&e!==null&&"status"in e?e.status:void 0;return typeof n!="number"?"unknown":n===401||n===403?"denied":n===429||n===408?"rateLimited":n>=500?"upstreamError":"unknown"}var ve=e=>`${e.length} \u4EF6`,ce={denied:"\u8CC7\u683C\u60C5\u5831\u304C\u62D2\u5426\u3055\u308C\u307E\u3057\u305F",rateLimited:"\u547C\u3073\u51FA\u3057\u304C\u5236\u9650\u3055\u308C\u3066\u3044\u307E\u3059",upstreamError:"\u76F8\u624B\u5074\u304C\u5185\u90E8\u30A8\u30E9\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F",timeout:"\u5FDC\u7B54\u304C\u3042\u308A\u307E\u305B\u3093\u3067\u3057\u305F",localWriteError:"\u66F8\u304D\u8FBC\u307F\u304C\u62D2\u5426\u3055\u308C\u307E\u3057\u305F",unknown:"\u8A73\u7D30\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059"},pe={upstreamUnreadable:e=>`\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${ce[e]})\u3002`,localWriteFailed:e=>`\u30ED\u30FC\u30AB\u30EB\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${ce[e]})\u3002`,resolverIncomplete:(e,n)=>`\u4E0A\u6D41\u304C ${e} \u4EF6\u306E\u30E1\u30F3\u30D0\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F\u304C\u3001\u7A81\u5408\u7D50\u679C\u306F ${n} \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:(e,n,g)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (${e})\u3002\u9023\u7D9A ${n} \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:(e,n)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C ${n} \u56DE\u9023\u7D9A\u3067\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F (${e})\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:e=>{if(e.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 ${ve(e.offendingEmails)} \u542B\u307E\u308C\u3066\u3044\u307E\u3059\u3002\u5BFE\u8C61\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059\u3002`;let n=e.axis==="membership"?"\u6A29\u9650\u3092\u5931\u3046\u4EBA\u6570":"\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u306A\u304F\u306A\u308B\u4EBA\u6570",g=e.axis==="membership"?e.withheldProjectionRemoves.map(String):e.withheldLedgerDeletes;return[`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: ${n}\u304C ${e.population} \u4EBA\u4E2D ${e.removalCount} \u4EBA\u306B\u306A\u308A\u307E\u3059`,`(\u4E0A\u9650: ${e.limits.maxRemovalCount} \u4EF6 / \u6BD4\u7387 ${e.limits.maxRemovalRatio})\u3002`,`\u5DEE\u3057\u6B62\u3081\u305F\u5BFE\u8C61: ${ve(g)} (\u4E00\u89A7\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059)\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(" ")},removalWritesFailed:e=>`${e.length} \u4EF6\u306E\u6240\u5C5E\u89E3\u9664\u304C\u66F8\u304D\u8FBC\u3081\u307E\u305B\u3093\u3067\u3057\u305F (${[...new Set(e.map(n=>ce[n.cause]))].join(" / ")})\u3002\u6B21\u56DE\u306E\u5B9F\u884C\u3067\u518D\u8A66\u884C\u3057\u307E\u3059\u3002`,removalsRefused:e=>`${e.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 ${e.map(n=>n.userId).join(", ")})\u3002\u6B8B\u308A\u306E\u5909\u66F4\u306F\u9069\u7528\u6E08\u307F\u3067\u3059\u3002`,foreignProvider:(e,n)=>`\u3053\u306E\u30EA\u30F3\u30AF\u306E\u4F9B\u7D66\u5143\u306F ${e} \u3067\u3059\u304C\u3001\u5B9F\u884C\u4E2D\u306E\u540C\u671F\u306F ${n} \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 oe=["aborted","missing","vanished","upstream-failed","local-failed","foreign-provider","not-converged"],me=[...oe,"already-vanished"];function Je(e){let{directory:n,repository:g,policy:d,now:l,logger:s}=e,f=e.messages??pe;if((0,ie.assertBlastRadiusPolicy)(d),!Number.isInteger(e.vanishedThreshold)||e.vanishedThreshold<1)throw new TypeError(`[@aiquants/auth-directory-drizzle] vanishedThreshold must be a positive integer, received ${String(e.vanishedThreshold)}`);if(typeof e.provider!="string"||e.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 v(t,i){try{await g.recordFailure(t,i)}catch(r){s.error(`[auth-directory] could not record the failure for ${t.externalKey}:`,r)}}async function w(t){try{let i=await n.getGroup(t.externalId);return i===null?{kind:"missing"}:{kind:"ok",group:i,members:await n.listGroupMembers(i,t.membershipMode)}}catch(i){return{kind:"failed",error:i instanceof Error?i:new Error(String(i))}}}async function b(t){if(t.vanishedObservations>=e.vanishedThreshold&&!t.isGroupActive)return s.warn(`[auth-directory] ${t.externalKey} still missing upstream (already deactivated after ${t.vanishedObservations} absences)`),{kind:"already-vanished",link:t};let i=await g.recordVanishedObservation(t,f.groupMissing(t.externalKey,t.vanishedObservations+1,e.vanishedThreshold));return i<e.vanishedThreshold?(s.warn(`[auth-directory] ${t.externalKey} missing upstream (${i}/${e.vanishedThreshold})`),{kind:"missing",link:t,observations:i}):(await g.markGroupInactive(t),await v(t,f.groupVanished(t.externalKey,i)),s.warn(`[auth-directory] ${t.externalKey} deactivated after ${i} consecutive absences`),{kind:"vanished",link:t})}async function a(t){if(t.provider!==e.provider)return await v(t,f.foreignProvider(t.provider,e.provider)),s.warn(`[auth-directory] skipping ${t.externalKey}: belongs to provider ${t.provider}`),{kind:"foreign-provider",link:t};let i=await w(t);if(i.kind==="missing")return b(t);if(i.kind==="failed")return await v(t,f.upstreamUnreadable(H(i.error))),s.error(`[auth-directory] upstream read failed for ${t.externalKey}:`,i.error),{kind:"upstream-failed",link:t,error:i.error};try{await g.resolveExternalIdentity(t,i.group);let[r,h]=await Promise.all([g.loadGroupSnapshot(t),g.resolveMembers(i.members)]);if(h.length!==i.members.length){let x=f.resolverIncomplete(i.members.length,h.length);return await v(t,x),s.error(`[auth-directory] ${x}`),{kind:"local-failed",link:t,error:new Error(x)}}let u=(0,ie.reconcile)(r,h,d);if(u.aborted){await v(t,f.planRefused(u.aborted));let x=u.aborted.reason==="invalid-upstream-data"?u.aborted.offendingEmails:u.aborted.axis==="membership"?u.aborted.withheldProjectionRemoves.map(String):u.aborted.withheldLedgerDeletes;return s.warn(`[auth-directory] plan refused for ${t.externalKey}: ${u.aborted.reason}; withheld: ${x.join(", ")}`),{kind:"aborted",link:t,plan:u}}let I=u.ledgerUpserts.length>0||u.ledgerDeletes.length>0||u.projectionAdds.length>0||u.projectionRemoves.length>0,A=l(),D={refusals:[],failures:[]};return I&&(D=await g.applyPlan(t,u,A)),await g.recordSuccess(t,i.group,A),D.refusals.length>0&&(await v(t,f.removalsRefused(D.refusals)),s.warn(`[auth-directory] ${D.refusals.length} removal(s) refused for ${t.externalKey}: users ${D.refusals.map(x=>x.userId).join(", ")}`)),D.failures.length>0&&(await v(t,f.removalWritesFailed(D.failures)),s.warn(`[auth-directory] ${D.failures.length} removal(s) could not be written for ${t.externalKey}: users ${D.failures.map(x=>x.userId).join(", ")}`)),u.unresolvedEmails.length>0&&s.info(`[auth-directory] ${t.externalKey}: ${u.unresolvedEmails.length} upstream member(s) have no local user yet`),D.refusals.length>0||D.failures.length>0?{kind:"not-converged",link:t,plan:u,refusals:D.refusals,failures:D.failures}:I?{kind:"applied",link:t,plan:u}:{kind:"unchanged",link:t}}catch(r){let h=r instanceof Error?r:new Error(String(r));return await v(t,f.localWriteFailed("localWriteError")),s.error(`[auth-directory] local write failed for ${t.externalKey}:`,h),{kind:"local-failed",link:t,error:h}}}return{syncOne:a,async run(t){let i=await g.listSyncableLinks(t),r=[];for(let I of i)try{r.push(await a(I))}catch(A){let D=A instanceof Error?A:new Error(String(A));s.error(`[auth-directory] ${I.externalKey} ended abnormally:`,D),r.push({kind:"local-failed",link:I,error:D})}let h=r.filter(I=>me.includes(I.kind)).length,u=r.filter(I=>oe.includes(I.kind)).length;return s.info(`[auth-directory] run finished: ${r.length} link(s), ${h} unhealthy (${u} new)`),{outcomes:r,unhealthyCount:h,newlyUnhealthyCount:u}}}}var Se=require("@aiquants/authz-core"),Te=require("@aiquants/mssql"),G=require("drizzle-orm"),Ae="The job that claimed this request ended without recording an outcome; whether the synchronization ran is unknown.",ye=2e3;function Ye(e,n,g){let d=e,l=g.ensureConnection??(async()=>{}),s=n.TDDirectorySyncRequest,f=(0,Se.assertTenantId)(g.tenantId,"createSyncRequestQueue(tenantId)"),v=g.maxErrorLength??ye;if(!Number.isInteger(v)||v<1||v>ye)throw new TypeError(`[@aiquants/auth-directory-drizzle] maxErrorLength must be an integer within 1..${ye}, received ${String(g.maxErrorLength)}`);let w={async claim(){await l();let b=await d.select({id:s.id}).from(s).where((0,G.and)((0,G.eq)(s.tenantId,f),(0,G.isNull)(s.pickedAt))).orderBy((0,G.asc)(s.id));for(let a of b){let i=(await d.update(s).set({pickedAt:g.now()}).output().where((0,G.and)((0,G.eq)(s.tenantId,f),(0,G.eq)(s.id,Number(a.id)),(0,G.isNull)(s.pickedAt))))?.[0];if(!i)continue;if(i.requestedBy===void 0||i.requestedBy===null)throw new TypeError(`[@aiquants/auth-directory-drizzle] the driver returned a row without requestedBy for request ${String(i.id)}`);let r=i.groupId??null;return{id:Number(i.id),tenantId:f,groupId:r===null?null:Number(r),requestedBy:String(i.requestedBy)}}return null},async finish(b,a){await l();let t=a.error?a.error.slice(0,v):null;return!!(await d.update(s).set({finishedAt:g.now(),result:a.result,error:t}).output().where((0,G.and)((0,G.eq)(s.tenantId,f),(0,G.eq)(s.id,b),(0,G.isNull)(s.finishedAt))))?.length},async listStranded(b){await l();let a=await d.select({id:s.id,pickedAt:s.pickedAt,requestedAt:s.requestedAt}).from(s).where((0,G.and)((0,G.eq)(s.tenantId,f),(0,G.isNull)(s.finishedAt))).orderBy((0,G.asc)(s.id)),t=i=>{let r=(0,Te.toInstant)(i);return Number.isNaN(r.getTime())||r<b};return{stranded:a.filter(i=>i.pickedAt!==null&&t(i.pickedAt)).map(i=>Number(i.id)),unclaimed:a.filter(i=>i.pickedAt===null&&t(i.requestedAt)).map(i=>Number(i.id))}},async abandonStranded(b){let{stranded:a}=await w.listStranded(b),t=[];for(let i of a)try{await w.finish(i,{result:"failed",error:Ae})&&t.push(i)}catch(r){g.onCloseFailed?.(i,r)}return t}};return w}var J=require("@aiquants/auth-core"),Y=require("@aiquants/authz-core"),K=require("@aiquants/mssql"),T=require("drizzle-orm");var Ve=7,Qe=4,De=1e3;function Xe(e){if(!(e instanceof Error))return!1;let n=e;return n.number===void 0&&n.code===void 0&&e.name==="Error"}function Ze(e,n,g,d){let l=e,s=d.ensureConnection??(async()=>{}),f=O(l,d),v=d.now??(()=>new Date),{TDGroupExternalLink:w,TDGroupMemberEmail:b}=n,{TMGroup:a,TDGroupMember:t}=g,i=d.actor;return{async listSyncableLinks(r){await s();let h=(0,Y.assertTenantId)(r.tenantId,"listSyncableLinks(tenantId)"),u=[(0,T.eq)(w.isSyncEnabled,!0),(0,T.eq)(w.tenantId,h)];return r.groupId!==void 0&&u.push((0,T.eq)(w.groupId,r.groupId)),(await l.select({linkId:w.id,groupId:w.groupId,linkTenantId:w.tenantId,groupTenantId:a.tenantId,groupKey:a.groupKey,isGroupActive:a.isActive,provider:w.provider,externalId:w.externalId,externalKey:w.externalKey,membershipMode:w.membershipMode,vanishedObservations:w.vanishedObservations}).from(w).innerJoin(a,(0,T.and)((0,T.eq)(a.id,w.groupId),(0,T.eq)(a.tenantId,h))).where((0,T.and)(...u))).map(A=>((0,Y.assertTenantMatch)(String(A.groupTenantId),String(A.linkTenantId),"listSyncableLinks(link.tenantId vs TMGroup.tenant_id)"),{linkId:Number(A.linkId),groupId:Number(A.groupId),groupKey:String(A.groupKey),tenantId:String(A.groupTenantId),provider:String(A.provider),externalId:String(A.externalId),externalKey:String(A.externalKey),membershipMode:B(String(A.membershipMode)),isGroupActive:!!A.isGroupActive,vanishedObservations:Number(A.vanishedObservations??0)}))},async loadGroupSnapshot(r){await s();let[h,u]=await Promise.all([l.select({email:b.email,externalUserId:b.externalUserId,resolvedUserId:b.resolvedUserId}).from(b).where((0,T.eq)(b.groupId,r.groupId)),l.select({userId:t.userId}).from(t).where((0,T.eq)(t.groupId,r.groupId))]);return{ledger:h.map(I=>({email:String(I.email),...I.externalUserId?{externalUserId:String(I.externalUserId)}:{},resolvedUserId:I.resolvedUserId===null?null:Number(I.resolvedUserId)})),projectedUserIds:u.map(I=>Number(I.userId))}},resolveMembers(r){return d.memberResolver.resolve(r)},async applyPlan(r,h,u){(0,Y.assertTenantId)(r.tenantId,"applyPlan(link.tenantId)"),await s(),(h.ledgerUpserts.length>0||h.projectionAdds.length>0)&&await f(async x=>{let P=h.ledgerUpserts.map(y=>({...y,email:(0,J.normalizeAuthEmail)(y.email)})),o=new Map;for(let y of(0,K.chunk)(P.map(S=>S.email))){let S=await x.select({id:b.id,email:b.email}).from(b).where((0,T.and)((0,T.eq)(b.groupId,r.groupId),(0,T.inArray)(b.email,y)));for(let k of S)o.set(String(k.email),Number(k.id))}let m=P.filter(y=>!o.has(y.email));for(let y of(0,K.chunkByParameters)(m,Ve))await x.insert(b).output().values(y.map(S=>({groupId:r.groupId,email:S.email,externalUserId:S.externalUserId??null,resolvedUserId:S.resolvedUserId,observedAt:u,createdAt:u,createdBy:i})));for(let y of P){let S=o.get(y.email);S!==void 0&&await x.update(b).set({externalUserId:y.externalUserId??null,resolvedUserId:y.resolvedUserId,observedAt:u,updatedAt:u,updatedBy:i}).output().where((0,T.eq)(b.id,S))}for(let y of(0,K.chunkByParameters)(h.projectionAdds,Qe)){let S=new Set((await x.select({userId:t.userId}).from(t).where((0,T.and)((0,T.eq)(t.groupId,r.groupId),(0,T.inArray)(t.userId,y)))).map(R=>Number(R.userId))),k=y.filter(R=>!S.has(R));k.length>0&&await x.insert(t).output().values(k.map(R=>({groupId:r.groupId,userId:R,createdAt:u,createdBy:i})))}});let I=[],A=[];for(let x of h.projectionRemoves)try{await d.lockoutGuard.guardedWrite(r.tenantId,{removeGroupMembership:{groupId:r.groupId,userId:x}},"remove-group-member",P=>P.delete(t).where((0,T.and)((0,T.eq)(t.groupId,r.groupId),(0,T.eq)(t.userId,x))))}catch(P){Xe(P)?I.push({userId:x,applied:!1,refusal:P.message}):A.push({userId:x,cause:H(P)})}let D=I.map(x=>x.userId);if(h.ledgerDeletes.length>0){let x=Math.max(1,K.MAX_PARAMETERS-K.RESERVED_PARAMETERS-1-D.length);await f(async P=>{for(let o of(0,K.chunk)(h.ledgerDeletes,Math.min(K.MAX_IN_LIST,x))){let m=D.length>0?(0,T.or)((0,T.isNull)(b.resolvedUserId),(0,T.notInArray)(b.resolvedUserId,D)):void 0;await P.delete(b).where((0,T.and)((0,T.eq)(b.groupId,r.groupId),(0,T.inArray)(b.email,o),...m?[m]:[]))}})}return{refusals:I,failures:A}},async resolveExternalIdentity(r,h){await s(),!(r.externalId===h.externalId&&r.externalKey===(0,J.normalizeAuthEmail)(h.externalKey))&&await l.update(w).set({externalId:h.externalId,externalKey:(0,J.normalizeAuthEmail)(h.externalKey)}).output().where((0,T.and)((0,T.eq)(w.id,r.linkId),(0,T.eq)(w.tenantId,r.tenantId)))},async recordSuccess(r,h,u){await s(),await f(async I=>{await I.update(w).set({syncedAt:u,syncError:null,vanishedObservations:0,externalId:h.externalId,externalKey:(0,J.normalizeAuthEmail)(h.externalKey),updatedAt:u,updatedBy:i}).output().where((0,T.and)((0,T.eq)(w.id,r.linkId),(0,T.eq)(w.tenantId,r.tenantId))),r.isGroupActive||await I.update(a).set({isActive:!0,updatedAt:u,updatedBy:i}).output().where((0,T.and)((0,T.eq)(a.id,r.groupId),(0,T.eq)(a.tenantId,r.tenantId)))})},async recordFailure(r,h){await s(),await l.update(w).set({syncError:h.slice(0,De),updatedAt:v(),updatedBy:i}).output().where((0,T.and)((0,T.eq)(w.id,r.linkId),(0,T.eq)(w.tenantId,r.tenantId)))},async recordVanishedObservation(r,h){await s();let u=r.vanishedObservations+1;return await l.update(w).set({vanishedObservations:u,syncError:h.slice(0,De),updatedAt:v(),updatedBy:i}).output().where((0,T.and)((0,T.eq)(w.id,r.linkId),(0,T.eq)(w.tenantId,r.tenantId))),u},async markGroupInactive(r){await s(),await l.update(a).set({isActive:!1,updatedAt:v(),updatedBy:i}).output().where((0,T.and)((0,T.eq)(a.id,r.groupId),(0,T.eq)(a.tenantId,r.tenantId),(0,T.eq)(a.isActive,!0)))}}}0&&(module.exports={AUTH_DIRECTORY_PROVIDER_COLUMN,AUTH_DIRECTORY_TENANT_SCOPED_TABLES,MEMBERSHIP_MODES,NEWLY_UNHEALTHY_SYNC_OUTCOMES,STRANDED_REASON,UNHEALTHY_SYNC_OUTCOMES,assertGroupMayBeLinked,assertMembershipMode,classifySyncFailure,createDirectoryCatalogReader,createDirectoryCatalogWriter,createDirectoryLinkStore,createDirectorySync,createDirectorySyncRepository,createPromotionRepository,createSyncRequestQueue,defaultExternalGroupGuardMessage,defaultSyncMessages,defineAuthDirectorySchema,findLedgerRowsForIdentity,promoteLedgerForUser,transactionRunner,withExternalGroupGuards});
|
|
2
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
1
|
import{assertTenantId as Ie}from"@aiquants/authz-core";import{chunkByParameters as Oe,toInstant as Ne}from"@aiquants/mssql";import{and as V,asc as $e,eq as $}from"drizzle-orm";import{runWithTransactionRetry as Ke}from"@aiquants/mssql";function N(r,a){return y=>Ke(d=>r.transaction(l=>d(()=>y(l))),a.transactionRetry)}var qe=10,O={tenantId:64,provider:32,externalId:256,externalKey:320,name:256,description:1e3,actor:50},we=2147483647;function Ue(r){if(r===void 0||!Number.isFinite(r))return null;let a=Math.trunc(r);return a<0?null:a>we?we:a}function j(r,a,y){let d=r.trim();if(d==="")throw new TypeError(`[@aiquants/auth-directory-drizzle] ${y} must not be blank`);if(d.length>a)throw new TypeError(`[@aiquants/auth-directory-drizzle] ${y} exceeds its column length (${a}); truncating it would change which rows it owns`);return d}function Sr(r,a,y){let d=r,l=y.ensureConnection??(async()=>{}),s=N(d,y),h=a.TDDirectoryCatalog,w=a.TMDirectoryCatalogObservation,b=g=>{let i=new Map;for(let t of g)i.set(t.externalId,t);return[...i.values()]};return{async replace(g,i,t){await l();let o=j(Ie(g,"replace(tenantId)"),O.tenantId,"tenantId"),e=j(i,O.provider,"provider"),m=j(y.actor,O.actor,"actor"),u=y.now(),f=b(t.map(S=>({tenantId:o,provider:e,externalId:S.externalId.slice(0,O.externalId),externalKey:S.externalKey.slice(0,O.externalKey),name:S.name.slice(0,O.name),description:S.description===void 0?null:S.description.slice(0,O.description),memberCount:Ue(S.memberCount),observedAt:u,createdAt:u,createdBy:m})));return await s(async S=>{await S.delete(h).where(V($(h.tenantId,o),$(h.provider,e)));for(let T of Oe(f,qe))await S.insert(h).values(T);await S.delete(w).where(V($(w.tenantId,o),$(w.provider,e))),await S.insert(w).values([{tenantId:o,provider:e,observedAt:u,entryCount:f.length,createdAt:u,createdBy:m,updatedAt:u,updatedBy:m}])}),f.length}}}function Tr(r,a,y={}){let d=r,l=y.ensureConnection??(async()=>{}),s=a.TDDirectoryCatalog,h=a.TMDirectoryCatalogObservation;return{async read(w,b){await l();let g=j(Ie(w,"read(tenantId)"),O.tenantId,"tenantId"),i=j(b,O.provider,"provider"),t=await d.select({observedAt:h.observedAt,entryCount:h.entryCount,externalId:s.externalId,externalKey:s.externalKey,name:s.name,description:s.description,memberCount:s.memberCount}).from(h).leftJoin(s,V($(s.tenantId,h.tenantId),$(s.provider,h.provider))).where(V($(h.tenantId,g),$(h.provider,i))).orderBy($e(s.name)),o=t.filter(u=>u.externalId!==null).map(u=>({externalId:String(u.externalId),externalKey:String(u.externalKey),name:String(u.name),...u.description===null?{}:{description:u.description},...u.memberCount===null?{}:{memberCount:Number(u.memberCount)}})),e=t[0];if(e===void 0)return{groups:o,observedAt:null};if(Number(e.entryCount)!==o.length)throw new Error(`[@aiquants/auth-directory-drizzle] catalog is inconsistent for (${g}, ${i}): the observation records ${e.entryCount} entries but ${o.length} rows are present`);let m=Ne(e.observedAt);if(!Number.isFinite(m.getTime()))throw new Error(`[@aiquants/auth-directory-drizzle] catalog observation for (${g}, ${i}) has an unreadable observed_at`);return{groups:o,observedAt:m}}}}import{AUTHZ_ACTIONS as ze}from"@aiquants/authz-core";var xr=(r,a)=>`\u5916\u90E8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u9023\u643A\u30B0\u30EB\u30FC\u30D7 (${a}) \u306B\u306F\u7BA1\u7406\u30ED\u30FC\u30EB (${r}) \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 B(r,a,y,d){return new Set(r.filter(l=>a.has(l.roleId)&&l.appKey===y&&l.resourceKey===d).map(l=>l.action))}function Q(r,a){return new Set(r.filter(y=>y.groupId===a).map(y=>y.roleId))}var W=r=>ze.every(a=>r.has(a)),Z=(r,a)=>r.find(y=>y.roleId===a)?.roleKey??`#${a}`;function X(r,a,y,d){let l=[...a].find(h=>W(B(r,new Set([h]),y,d)));return l!==void 0?Z(r,l):[...a].filter(h=>B(r,new Set([h]),y,d).size>0).map(h=>Z(r,h)).join(" + ")}function kr(r,a){return ve(r,a)}function ve(r,a){let y=r;return{...r,withTransaction:(d,l)=>y.withTransaction((s,h)=>d(ve(s,a),h),l),async assignGroupRole(d,l,s,h){await y.withTransaction(async(w,b)=>{let g=await w.listGrants(d),i=Q(await w.listGroupRoles(d),l);if(i.add(s),W(B(g,i,a.adminAppKey,a.adminResourceKey))){let t=await a.linkLookup.externalGroupKey(d,l,b);if(t!==null)throw new Error(a.message(X(g,i,a.adminAppKey,a.adminResourceKey),t))}await w.assignGroupRole(d,l,s,h)})},async updateGrant(d,l,s,h){let w=s.action;if(w===void 0)return y.updateGrant(d,l,s,h);await y.withTransaction(async(b,g)=>{let i=await b.listGrants(d),t=i.find(m=>m.id===l);if(t===void 0)return b.updateGrant(d,l,s,h);let e=(await b.listResources(d)).find(m=>m.id===t.resourceId);if(e?.appKey===a.adminAppKey&&e?.resourceKey===a.adminResourceKey){let m=await b.listGroupRoles(d);for(let u of new Set(m.filter(f=>f.roleId===t.roleId).map(f=>f.groupId))){let f=Q(m,u),S=B(i.filter(A=>A.id!==l),f,a.adminAppKey,a.adminResourceKey);if(f.has(t.roleId)&&S.add(w),!W(S))continue;let T=await a.linkLookup.externalGroupKey(d,u,g);if(T!==null)throw new Error(a.message(X(i,f,a.adminAppKey,a.adminResourceKey)||Z(i,t.roleId),T))}}await b.updateGrant(d,l,s,h)})},async createGrant(d,l,s){await y.withTransaction(async(h,w)=>{let g=(await h.listResources(d)).find(i=>i.id===l.resourceId);if(g?.appKey===a.adminAppKey&&g?.resourceKey===a.adminResourceKey){let i=await h.listGrants(d),t=await h.listGroupRoles(d);for(let o of new Set(t.filter(e=>e.roleId===l.roleId).map(e=>e.groupId))){let e=B(i,Q(t,o),a.adminAppKey,a.adminResourceKey);if(e.add(l.action),!W(e))continue;let m=await a.linkLookup.externalGroupKey(d,o,w);if(m!==null)throw new Error(a.message(X(i,Q(t,o),a.adminAppKey,a.adminResourceKey)||Z(i,l.roleId),m))}}await h.createGrant(d,l,s)})}}}async function Rr(r,a,y,d){let l=(await r.listGroupRoles(a)).filter(b=>b.groupId===y);if(l.length===0)return;let s=await r.listGrants(a),h=new Set(l.map(b=>b.roleId));if(!W(B(s,h,d.adminAppKey,d.adminResourceKey)))return;let w=l[0];if(w)throw new Error(d.message(X(s,h,d.adminAppKey,d.adminResourceKey),w.groupKey))}import{and as Be,eq as H}from"drizzle-orm";async function ee(r,a,y){let{ledger:d,group:l}=a,{tenantId:s,externalUserId:h,email:w}=y;if(h===null&&w==="")return[];let b={ledgerId:d.id,groupId:d.groupId,tenantId:l.tenantId,externalUserId:d.externalUserId},g=async m=>(await r.select(b).from(d).innerJoin(l,Be(H(l.id,d.groupId),H(l.tenantId,s),H(l.isActive,!0))).where(m)).map(f=>({ledgerId:Number(f.ledgerId),groupId:Number(f.groupId),tenantId:String(f.tenantId),externalUserId:f.externalUserId===null?null:String(f.externalUserId)})),i=m=>{let u=new Map;for(let f of m)u.set(f.ledgerId,{ledgerId:f.ledgerId,groupId:f.groupId,tenantId:f.tenantId});return[...u.values()].sort((f,S)=>f.ledgerId-S.ledgerId)},t=h===null?[]:await g(H(d.externalUserId,h));if(w==="")return i(t);let o=await g(H(d.email,w)),e=m=>m.externalUserId!==null&&m.externalUserId!==h;return i([...t,...o.filter(m=>!e(m))])}import{normalizeAuthEmail as ue}from"@aiquants/auth-core";import{assertTenantMembershipPolicy as Fe}from"@aiquants/auth-directory-core";import{assertTenantId as C,assertTenantMatch as je}from"@aiquants/authz-core";import{toInstant as Te}from"@aiquants/mssql";import{and as E,asc as Ae,count as de,eq as v,isNull as re}from"drizzle-orm";var Se=["direct","transitive"];function J(r){if(!Se.includes(r))throw new TypeError(`[@aiquants/auth-directory-drizzle] membershipMode must be one of ${Se.join(" | ")}, received ${JSON.stringify(r)}`);return r}function We(r){let a=typeof r=="object"&&r!==null&&"number"in r?r.number:void 0;return a===2601||a===2627}function Ur(r,a,y,d){let l=r,s=d.ensureConnection??(async()=>{}),h=N(l,d),w=d.policyCacheTtlMs??0;if(!Number.isFinite(w)||w<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] policyCacheTtlMs must be a non-negative number, received ${String(d.policyCacheTtlMs)}`);let b=new Map,g=n=>b.delete(n),{TDGroupExternalLink:i,TDGroupMemberEmail:t,TDTenantMembershipGroup:o,TDAllowlistGroup:e,TDDirectorySyncRequest:m}=a,{TMGroup:u,TDGroupMember:f}=y;async function S(n,c,p){if((await n.select({groupId:o.groupId}).from(o).where(E(v(o.tenantId,c),v(o.groupId,p),v(o.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 T(n,c,p){let[I]=await n.select({id:u.id}).from(u).where(E(v(u.id,p),v(u.tenantId,c)));if(!I)throw new Error(`Group not found in tenant: ${p}`)}function A(n,c){return{linkId:Number(n.linkId),groupId:Number(n.groupId),tenantId:String(n.tenantId),provider:String(n.provider),externalId:String(n.externalId),externalKey:String(n.externalKey),membershipMode:J(String(n.membershipMode)),isSyncEnabled:!!n.isSyncEnabled,...n.syncedAt?{syncedAt:Te(n.syncedAt).toISOString()}:{},...n.syncError?{syncError:String(n.syncError)}:{},unresolvedCount:c}}async function _(n,c){let p=await n.select({groupId:t.groupId,n:de()}).from(t).innerJoin(u,E(v(u.id,t.groupId),v(u.tenantId,c))).where(re(t.resolvedUserId)).groupBy(t.groupId);return new Map(p.map(I=>[Number(I.groupId),Number(I.n)]))}return{async listLinks(n){C(n,"listLinks(tenantId)"),await s();let c=await l.select({linkId:i.id,groupId:i.groupId,tenantId:i.tenantId,provider:i.provider,externalId:i.externalId,externalKey:i.externalKey,membershipMode:i.membershipMode,isSyncEnabled:i.isSyncEnabled,syncedAt:i.syncedAt,syncError:i.syncError}).from(i).where(v(i.tenantId,n)).orderBy(Ae(i.groupId)),p=await _(l,n);return c.map(I=>(je(n,String(I.tenantId),"listLinks(row.tenantId)"),A(I,p.get(Number(I.groupId))??0)))},async externalGroupKey(n,c,p){C(n,"externalGroupKey(tenantId)"),await s();let[I]=await(p??l).select({externalKey:i.externalKey}).from(i).where(E(v(i.groupId,c),v(i.tenantId,n)));return I?String(I.externalKey):null},async linkGroup(n,c,p,I){C(n,"linkGroup(tenantId)");let D=J(p.membershipMode),k=p.externalId.trim(),M=ue(p.externalKey);if(k===""||M==="")throw new TypeError("[@aiquants/auth-directory-drizzle] externalId and externalKey must be non-empty");await s(),await d.linkable.assertLinkableAndWrite(n,c,async Y=>{let G=Y;await T(G,n,c),await S(G,n,c),await G.insert(i).output().values({groupId:c,tenantId:n,provider:p.provider,externalId:k,externalKey:M,membershipMode:D,isSyncEnabled:!0,vanishedObservations:0,createdAt:new Date,createdBy:I})}),g(n)},async unlinkGroup(n,c){C(n,"unlinkGroup(tenantId)"),await s(),await h(async p=>{await T(p,n,c),await p.delete(t).where(v(t.groupId,c)),await p.delete(e).where(E(v(e.groupId,c),v(e.tenantId,n))),await p.delete(i).where(E(v(i.groupId,c),v(i.tenantId,n)))}),g(n)},async setSyncEnabled(n,c,p,I){C(n,"setSyncEnabled(tenantId)"),await s(),await l.update(i).set({isSyncEnabled:p,updatedAt:new Date,updatedBy:I}).output().where(E(v(i.groupId,c),v(i.tenantId,n)))},async requestSync(n,c,p){return C(n,"requestSync(tenantId)"),await s(),l.transaction(async I=>{if(c!==null&&await T(I,n,c),(await I.select({id:m.id}).from(m).where(E(v(m.tenantId,n),c===null?re(m.groupId):v(m.groupId,c),re(m.pickedAt)))).length>0)return"already-pending";try{await I.insert(m).output().values({tenantId:n,groupId:c,requestedBy:p,requestedAt:new Date,createdAt:new Date,createdBy:p})}catch(k){if(!We(k))throw k;return"already-pending"}return"queued"})},async getStatus(n){C(n,"getStatus(tenantId)"),await s();let c=await l.select({groupId:i.groupId,isSyncEnabled:i.isSyncEnabled,syncedAt:i.syncedAt,syncError:i.syncError}).from(i).where(v(i.tenantId,n)),[p]=await l.select({n:de()}).from(m).where(E(v(m.tenantId,n),re(m.finishedAt))),I=c.filter(G=>!!G.isSyncEnabled),D=I.map(G=>G.syncedAt?Te(G.syncedAt).getTime():null),k=D.length>0&&D.every(G=>G!==null)?Math.min(...D):null,Y=c.length>0&&I.length===0||I.length>0&&(k===null||Date.now()-k>d.stalenessThresholdSec*1e3);return{isEnabled:d.isEnabled,provider:d.provider,linkedGroupCount:c.length,...k!==null?{oldestSyncedAt:new Date(k).toISOString()}:{},isStale:Y,pausedGroupCount:c.length-I.length,stalenessThresholdSec:d.stalenessThresholdSec,erroredGroupIds:c.filter(G=>!!G.syncError).map(G=>Number(G.groupId)).sort((G,Le)=>G-Le),pendingRequestCount:Number(p?.n??0)}},async loadTenantMembershipPolicy(n){C(n,"loadTenantMembershipPolicy(tenantId)");let c=w>0?b.get(n):void 0;if(c&&Date.now()-c.loadedAt<w)return c.policy;await s();let p=await l.select({groupId:o.groupId,isBreakGlass:o.isBreakGlass,externalLinkId:i.id}).from(o).innerJoin(u,E(v(u.id,o.groupId),v(u.tenantId,n))).leftJoin(i,E(v(i.groupId,o.groupId),v(i.tenantId,n))).where(v(o.tenantId,n));if(p.length===0){let M={kind:"no-proof-required"};return w>0&&b.set(n,{policy:M,loadedAt:Date.now()}),M}let I=p.map(M=>Number(M.groupId)),D=p.filter(M=>!!M.isBreakGlass);for(let M of D)if(M.externalLinkId!==null)throw new Error(`Break-glass group ${Number(M.groupId)} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`);let k={kind:"require-proof",proofGroupIds:I,breakGlassGroupIds:D.map(M=>Number(M.groupId))};return Fe(k,`tenant ${n}`),w>0&&b.set(n,{policy:k,loadedAt:Date.now()}),k},async setTenantMembershipGroup(n,c,p,I){C(n,"setTenantMembershipGroup(tenantId)"),await s(),await h(async D=>{if(await T(D,n,c),p){let[M]=await D.select({id:i.id}).from(i).where(E(v(i.tenantId,n),v(i.groupId,c)));if(M)throw new Error(`Group ${c} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`)}let k=await D.select({groupId:o.groupId}).from(o).where(E(v(o.tenantId,n),v(o.groupId,c)));if(!p&&!(await D.select({groupId:o.groupId,isBreakGlass:o.isBreakGlass}).from(o).where(v(o.tenantId,n))).filter(G=>Number(G.groupId)!==c).some(G=>!!G.isBreakGlass))throw new Error(`Declaring group ${c} as ordinary proof would leave tenant ${n} with a membership proof but no break-glass path; declare a locally-managed break-glass group first.`);if(k.length>0){await D.update(o).set({isBreakGlass:p,updatedAt:new Date,updatedBy:I}).output().where(E(v(o.tenantId,n),v(o.groupId,c)));return}await D.insert(o).output().values({tenantId:n,groupId:c,isBreakGlass:p,createdAt:new Date,createdBy:I})}),g(n)},async removeTenantMembershipGroup(n,c){C(n,"removeTenantMembershipGroup(tenantId)"),await s(),await h(async p=>{let D=(await p.select({groupId:o.groupId,isBreakGlass:o.isBreakGlass}).from(o).where(v(o.tenantId,n))).filter(k=>Number(k.groupId)!==c);if(D.length>0&&!D.some(k=>!!k.isBreakGlass))throw new Error(`Removing group ${c} would leave tenant ${n} with a membership proof but no break-glass path.`);await p.delete(o).where(E(v(o.tenantId,n),v(o.groupId,c)))}),g(n)},async setGroupActive(n,c,p,I){C(n,"setGroupActive(tenantId)"),await s(),await l.update(u).set({isActive:p,updatedAt:new Date,updatedBy:I}).output().where(E(v(u.id,c),v(u.tenantId,n)))},async listLedgerGroupIds(n,c){if(C(n,"listLedgerGroupIds(tenantId)"),!d.isEnabled)return[];await s();let p=await ee(l,{ledger:t,group:u},{tenantId:n,externalUserId:c.externalUserId?.trim()||null,email:ue(c.email)});return[...new Set(p.map(I=>I.groupId))].sort((I,D)=>I-D)},async isEmailAllowedByGroup(n){if(!d.isEnabled)return!1;let c=ue(n);if(c==="")return!1;await s();let[p]=await l.select({id:t.id}).from(t).innerJoin(e,v(e.groupId,t.groupId)).innerJoin(u,E(v(u.id,t.groupId),v(u.isActive,!0))).where(v(t.email,c));return!!p},async listAllowlistGroups(n){return C(n,"listAllowlistGroups(tenantId)"),await s(),(await l.select({id:e.id,groupId:e.groupId,groupKey:u.groupKey,groupName:u.name,description:e.description}).from(e).innerJoin(u,E(v(u.id,e.groupId),v(u.tenantId,n))).where(v(e.tenantId,n)).orderBy(Ae(u.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:n},readOnly:!1}))},async addAllowlistGroup(n,c,p,I){C(n,"addAllowlistGroup(tenantId)"),await s(),await h(async D=>{await T(D,n,c);let[k]=await D.select({id:i.id}).from(i).where(E(v(i.groupId,c),v(i.tenantId,n)));if(!k)throw new Error(`Group ${c} is locally managed; only externally-sourced groups can gate sign-in, because the decision reads the upstream member ledger.`);await D.insert(e).output().values({tenantId:n,groupId:c,description:p??null,createdAt:new Date,createdBy:I})})},async removeAllowlistGroup(n,c){C(n,"removeAllowlistGroup(tenantId)"),await s(),await l.delete(e).where(E(v(e.id,c),v(e.tenantId,n)))},async countProjectedMembers(n,c){C(n,"countProjectedMembers(tenantId)"),await s();let[p]=await l.select({n:de()}).from(f).innerJoin(u,E(v(u.id,f.groupId),v(u.tenantId,n))).where(v(f.groupId,c));return Number(p?.n??0)}}}import{normalizeAuthEmail as He}from"@aiquants/auth-core";import{assertTenantId as Je,assertTenantMatch as Ye}from"@aiquants/authz-core";async function jr(r,a){let y=Je(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 d=He(a.email),l=a.externalUserId?.trim()||null;if(l===null&&d==="")return{addedGroupIds:[],matchedLedgerIds:[]};let s=await r.findLedgerMatches({tenantId:y,externalUserId:l,email:d});if(s.length===0)return{addedGroupIds:[],matchedLedgerIds:[]};for(let g of s)Ye(y,g.tenantId,"promoteLedgerForUser(findLedgerMatches row)");let h=[...new Set(s.map(g=>g.ledgerId))].sort((g,i)=>g-i),w=[...new Set(s.map(g=>g.groupId))].sort((g,i)=>g-i);return{addedGroupIds:[...await r.attachUserToGroups({tenantId:y,userId:a.userId,ledgerIds:h,groupIds:w})].sort((g,i)=>g-i),matchedLedgerIds:h}}import{assertTenantId as De}from"@aiquants/authz-core";import{chunk as le,chunkByParameters as Ve,MAX_PARAMETERS as Qe}from"@aiquants/mssql";import{and as ce,eq as pe,inArray as te}from"drizzle-orm";var Xe=4,Ze=100;function Xr(r,a,y,d){let l=r,s=d.ensureConnection??(async()=>{}),h=N(l,d),w=d.now??(()=>new Date),b=a.TDGroupMemberEmail,{TMGroup:g,TDGroupMember:i}=y;return{async findLedgerMatches(t){return De(t.tenantId,"findLedgerMatches(tenantId)"),await s(),ee(l,{ledger:b,group:g},t)},async attachUserToGroups({tenantId:t,userId:o,ledgerIds:e,groupIds:m}){return De(t,"attachUserToGroups(tenantId)"),await s(),e.length===0?[]:h(async u=>{let f=new Set;for(let n of le(m)){let c=await u.select({id:g.id}).from(g).where(ce(te(g.id,n),pe(g.tenantId,t),pe(g.isActive,!0)));for(let p of c)f.add(Number(p.id))}let S=m.filter(n=>f.has(n));if(S.length===0)return[];let T=new Set;for(let n of le(S)){let c=await u.select({groupId:i.groupId}).from(i).where(ce(pe(i.userId,o),te(i.groupId,n)));for(let p of c)T.add(Number(p.groupId))}let A=S.filter(n=>!T.has(n)),_=Math.max(1,Qe-Ze-S.length);for(let n of le(e,_))await u.update(b).set({resolvedUserId:o}).output().where(ce(te(b.id,n),te(b.groupId,S)));if(A.length>0){let n=w();for(let c of Ve(A,Xe))await u.insert(i).output().values(c.map(p=>({groupId:p,userId:o,createdAt:n,createdBy:d.actor})))}return A})}}}import{AUTHZ_TENANT_COLUMN as U}from"@aiquants/authz-drizzle";import{bigint as P,bit as xe,datetime2 as K,foreignKey as ne,int as me,mssqlSchema as er,nvarchar as x,primaryKey as q,uniqueIndex as z}from"drizzle-orm/mssql-core";var ye="provider",tt=["TDGroupExternalLink","TDTenantMembershipGroup","TDAllowlistGroup","TDDirectorySyncRequest","TDDirectoryCatalog","TMDirectoryCatalogObservation","TMAllowlist"];function nt(r,a){let y=er(r),d=()=>({createdAt:K("created_at"),createdBy:x("created_by",{length:50}),updatedAt:K("updated_at"),updatedBy:x("updated_by",{length:50})}),l=()=>({createdAt:K("created_at"),createdBy:x("created_by",{length:50})}),s=y.table("TDGroupExternalLink",{id:P("id",{mode:"number"}).identity().notNull(),groupId:P("group_id",{mode:"number"}).notNull(),tenantId:x(U,{length:64}).notNull(),provider:x(ye,{length:32}).notNull(),externalId:x("external_id",{length:256}).notNull(),externalKey:x("external_key",{length:320}).notNull(),membershipMode:x("membership_mode",{length:16}).notNull(),isSyncEnabled:xe("is_sync_enabled").notNull(),syncedAt:K("synced_at"),syncError:x("sync_error",{length:1e3}),vanishedObservations:me("vanished_observations").notNull(),...d()},e=>[q({name:`${r}_TDGroupExternalLink_pk`,columns:[e.id]}),z(`${r}_TDGroupExternalLink_group_uq`).on(e.groupId),z(`${r}_TDGroupExternalLink_external_uq`).on(e.tenantId,e.provider,e.externalId),ne({name:`${r}_TDGroupExternalLink_group_id_fk`,columns:[e.groupId],foreignColumns:[a.groupTable.id]})]),h=y.table("TDGroupMemberEmail",{id:P("id",{mode:"number"}).identity().notNull(),groupId:P("group_id",{mode:"number"}).notNull(),email:x("email",{length:320}).notNull(),externalUserId:x("external_user_id",{length:128}),resolvedUserId:P("resolved_user_id",{mode:"number"}),observedAt:K("observed_at").notNull(),...d()},e=>[q({name:`${r}_TDGroupMemberEmail_pk`,columns:[e.id]}),z(`${r}_TDGroupMemberEmail_group_email_uq`).on(e.groupId,e.email),ne({name:`${r}_TDGroupMemberEmail_group_id_fk`,columns:[e.groupId],foreignColumns:[a.groupTable.id]})]),w=y.table("TDTenantMembershipGroup",{tenantId:x(U,{length:64}).notNull(),groupId:P("group_id",{mode:"number"}).notNull(),isBreakGlass:xe("is_break_glass").notNull(),...l()},e=>[q({name:`${r}_TDTenantMembershipGroup_pk`,columns:[e.tenantId,e.groupId]}),ne({name:`${r}_TDTenantMembershipGroup_group_id_fk`,columns:[e.groupId],foreignColumns:[a.groupTable.id]})]),b=y.table("TDAllowlistGroup",{id:P("id",{mode:"number"}).identity().notNull(),tenantId:x(U,{length:64}).notNull(),groupId:P("group_id",{mode:"number"}).notNull(),description:x("description",{length:512}),...l()},e=>[q({name:`${r}_TDAllowlistGroup_pk`,columns:[e.id]}),z(`${r}_TDAllowlistGroup_tenant_group_uq`).on(e.tenantId,e.groupId),ne({name:`${r}_TDAllowlistGroup_group_id_fk`,columns:[e.groupId],foreignColumns:[a.groupTable.id]})]),g=y.table("TDDirectorySyncRequest",{id:P("id",{mode:"number"}).identity().notNull(),tenantId:x(U,{length:64}).notNull(),groupId:P("group_id",{mode:"number"}),requestedBy:x("requested_by",{length:320}).notNull(),requestedAt:K("requested_at").notNull(),pickedAt:K("picked_at"),finishedAt:K("finished_at"),result:x("result",{length:32}),error:x("error",{length:2e3}),...l()},e=>[q({name:`${r}_TDDirectorySyncRequest_pk`,columns:[e.id]})]),i=y.table("TMAllowlist",{id:P("id",{mode:"number"}).identity().notNull(),tenantId:x(U,{length:64}).notNull(),pattern:x("pattern",{length:256}).notNull(),type:x("type",{length:32}).notNull(),description:x("description",{length:512}),...l()},e=>[q({name:`${r}_TMAllowlist_pk`,columns:[e.id]}),z(`${r}_TMAllowlist_pattern_uq`).on(e.tenantId,e.pattern)]),t=y.table("TDDirectoryCatalog",{id:P("id",{mode:"number"}).identity().notNull(),tenantId:x(U,{length:64}).notNull(),provider:x(ye,{length:32}).notNull(),externalId:x("external_id",{length:256}).notNull(),externalKey:x("external_key",{length:320}).notNull(),name:x("name",{length:256}).notNull(),description:x("description",{length:1e3}),memberCount:me("member_count"),observedAt:K("observed_at").notNull(),...l()},e=>[q({name:`${r}_TDDirectoryCatalog_pk`,columns:[e.id]}),z(`${r}_TDDirectoryCatalog_external_uq`).on(e.tenantId,e.provider,e.externalId)]),o=y.table("TMDirectoryCatalogObservation",{id:P("id",{mode:"number"}).identity().notNull(),tenantId:x(U,{length:64}).notNull(),provider:x(ye,{length:32}).notNull(),observedAt:K("observed_at").notNull(),entryCount:me("entry_count").notNull(),...d()},e=>[q({name:`${r}_TMDirectoryCatalogObservation_pk`,columns:[e.id]}),z(`${r}_TMDirectoryCatalogObservation_target_uq`).on(e.tenantId,e.provider)]);return{TDGroupExternalLink:s,TDGroupMemberEmail:h,TDTenantMembershipGroup:w,TDAllowlistGroup:b,TDDirectorySyncRequest:g,TDDirectoryCatalog:t,TMDirectoryCatalogObservation:o,TMAllowlist:i}}import{assertBlastRadiusPolicy as rr,reconcile as tr}from"@aiquants/auth-directory-core";function oe(r){if(r instanceof Error&&(r.name==="AbortError"||r.name==="TimeoutError"))return"timeout";let a=typeof r=="object"&&r!==null&&"status"in r?r.status:void 0;return typeof a!="number"?"unknown":a===401||a===403?"denied":a===429||a===408?"rateLimited":a>=500?"upstreamError":"unknown"}var ke=r=>`${r.length} \u4EF6`,ge={denied:"\u8CC7\u683C\u60C5\u5831\u304C\u62D2\u5426\u3055\u308C\u307E\u3057\u305F",rateLimited:"\u547C\u3073\u51FA\u3057\u304C\u5236\u9650\u3055\u308C\u3066\u3044\u307E\u3059",upstreamError:"\u76F8\u624B\u5074\u304C\u5185\u90E8\u30A8\u30E9\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F",timeout:"\u5FDC\u7B54\u304C\u3042\u308A\u307E\u305B\u3093\u3067\u3057\u305F",localWriteError:"\u66F8\u304D\u8FBC\u307F\u304C\u62D2\u5426\u3055\u308C\u307E\u3057\u305F",unknown:"\u8A73\u7D30\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059"},Re={upstreamUnreadable:r=>`\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${ge[r]})\u3002`,localWriteFailed:r=>`\u30ED\u30FC\u30AB\u30EB\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${ge[r]})\u3002`,resolverIncomplete:(r,a)=>`\u4E0A\u6D41\u304C ${r} \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:(r,a,y)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (${r})\u3002\u9023\u7D9A ${a} \u56DE\u76EE\u3067\u3059\u3002${y} \u56DE\u306B\u9054\u3057\u305F\u6642\u70B9\u3067\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3059\u3002`,groupVanished:(r,a)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C ${a} \u56DE\u9023\u7D9A\u3067\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F (${r})\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:r=>{if(r.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 ${ke(r.offendingEmails)} \u542B\u307E\u308C\u3066\u3044\u307E\u3059\u3002\u5BFE\u8C61\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059\u3002`;let a=r.axis==="membership"?"\u6A29\u9650\u3092\u5931\u3046\u4EBA\u6570":"\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u306A\u304F\u306A\u308B\u4EBA\u6570",y=r.axis==="membership"?r.withheldProjectionRemoves.map(String):r.withheldLedgerDeletes;return[`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: ${a}\u304C ${r.population} \u4EBA\u4E2D ${r.removalCount} \u4EBA\u306B\u306A\u308A\u307E\u3059`,`(\u4E0A\u9650: ${r.limits.maxRemovalCount} \u4EF6 / \u6BD4\u7387 ${r.limits.maxRemovalRatio})\u3002`,`\u5DEE\u3057\u6B62\u3081\u305F\u5BFE\u8C61: ${ke(y)} (\u4E00\u89A7\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059)\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(" ")},removalWritesFailed:r=>`${r.length} \u4EF6\u306E\u6240\u5C5E\u89E3\u9664\u304C\u66F8\u304D\u8FBC\u3081\u307E\u305B\u3093\u3067\u3057\u305F (${[...new Set(r.map(a=>ge[a.cause]))].join(" / ")})\u3002\u6B21\u56DE\u306E\u5B9F\u884C\u3067\u518D\u8A66\u884C\u3057\u307E\u3059\u3002`,removalsRefused:r=>`${r.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 ${r.map(a=>a.userId).join(", ")})\u3002\u6B8B\u308A\u306E\u5909\u66F4\u306F\u9069\u7528\u6E08\u307F\u3067\u3059\u3002`,foreignProvider:(r,a)=>`\u3053\u306E\u30EA\u30F3\u30AF\u306E\u4F9B\u7D66\u5143\u306F ${r} \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 he=["aborted","missing","vanished","upstream-failed","local-failed","foreign-provider","not-converged"],Ee=[...he,"already-vanished"];function lt(r){let{directory:a,repository:y,policy:d,now:l,logger:s}=r,h=r.messages??Re;if(rr(d),!Number.isInteger(r.vanishedThreshold)||r.vanishedThreshold<1)throw new TypeError(`[@aiquants/auth-directory-drizzle] vanishedThreshold must be a positive integer, received ${String(r.vanishedThreshold)}`);if(typeof r.provider!="string"||r.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 w(t,o){try{await y.recordFailure(t,o)}catch(e){s.error(`[auth-directory] could not record the failure for ${t.externalKey}:`,e)}}async function b(t){try{let o=await a.getGroup(t.externalId);return o===null?{kind:"missing"}:{kind:"ok",group:o,members:await a.listGroupMembers(o,t.membershipMode)}}catch(o){return{kind:"failed",error:o instanceof Error?o:new Error(String(o))}}}async function g(t){if(t.vanishedObservations>=r.vanishedThreshold&&!t.isGroupActive)return s.warn(`[auth-directory] ${t.externalKey} still missing upstream (already deactivated after ${t.vanishedObservations} absences)`),{kind:"already-vanished",link:t};let o=await y.recordVanishedObservation(t,h.groupMissing(t.externalKey,t.vanishedObservations+1,r.vanishedThreshold));return o<r.vanishedThreshold?(s.warn(`[auth-directory] ${t.externalKey} missing upstream (${o}/${r.vanishedThreshold})`),{kind:"missing",link:t,observations:o}):(await y.markGroupInactive(t),await w(t,h.groupVanished(t.externalKey,o)),s.warn(`[auth-directory] ${t.externalKey} deactivated after ${o} consecutive absences`),{kind:"vanished",link:t})}async function i(t){if(t.provider!==r.provider)return await w(t,h.foreignProvider(t.provider,r.provider)),s.warn(`[auth-directory] skipping ${t.externalKey}: belongs to provider ${t.provider}`),{kind:"foreign-provider",link:t};let o=await b(t);if(o.kind==="missing")return g(t);if(o.kind==="failed")return await w(t,h.upstreamUnreadable(oe(o.error))),s.error(`[auth-directory] upstream read failed for ${t.externalKey}:`,o.error),{kind:"upstream-failed",link:t,error:o.error};try{await y.resolveExternalIdentity(t,o.group);let[e,m]=await Promise.all([y.loadGroupSnapshot(t),y.resolveMembers(o.members)]);if(m.length!==o.members.length){let A=h.resolverIncomplete(o.members.length,m.length);return await w(t,A),s.error(`[auth-directory] ${A}`),{kind:"local-failed",link:t,error:new Error(A)}}let u=tr(e,m,d);if(u.aborted){await w(t,h.planRefused(u.aborted));let A=u.aborted.reason==="invalid-upstream-data"?u.aborted.offendingEmails:u.aborted.axis==="membership"?u.aborted.withheldProjectionRemoves.map(String):u.aborted.withheldLedgerDeletes;return s.warn(`[auth-directory] plan refused for ${t.externalKey}: ${u.aborted.reason}; withheld: ${A.join(", ")}`),{kind:"aborted",link:t,plan:u}}let f=u.ledgerUpserts.length>0||u.ledgerDeletes.length>0||u.projectionAdds.length>0||u.projectionRemoves.length>0,S=l(),T={refusals:[],failures:[]};return f&&(T=await y.applyPlan(t,u,S)),await y.recordSuccess(t,o.group,S),T.refusals.length>0&&(await w(t,h.removalsRefused(T.refusals)),s.warn(`[auth-directory] ${T.refusals.length} removal(s) refused for ${t.externalKey}: users ${T.refusals.map(A=>A.userId).join(", ")}`)),T.failures.length>0&&(await w(t,h.removalWritesFailed(T.failures)),s.warn(`[auth-directory] ${T.failures.length} removal(s) could not be written for ${t.externalKey}: users ${T.failures.map(A=>A.userId).join(", ")}`)),u.unresolvedEmails.length>0&&s.info(`[auth-directory] ${t.externalKey}: ${u.unresolvedEmails.length} upstream member(s) have no local user yet`),T.refusals.length>0||T.failures.length>0?{kind:"not-converged",link:t,plan:u,refusals:T.refusals,failures:T.failures}:f?{kind:"applied",link:t,plan:u}:{kind:"unchanged",link:t}}catch(e){let m=e instanceof Error?e:new Error(String(e));return await w(t,h.localWriteFailed("localWriteError")),s.error(`[auth-directory] local write failed for ${t.externalKey}:`,m),{kind:"local-failed",link:t,error:m}}}return{syncOne:i,async run(t){let o=await y.listSyncableLinks(t),e=[];for(let f of o)try{e.push(await i(f))}catch(S){let T=S instanceof Error?S:new Error(String(S));s.error(`[auth-directory] ${f.externalKey} ended abnormally:`,T),e.push({kind:"local-failed",link:f,error:T})}let m=e.filter(f=>Ee.includes(f.kind)).length,u=e.filter(f=>he.includes(f.kind)).length;return s.info(`[auth-directory] run finished: ${e.length} link(s), ${m} unhealthy (${u} new)`),{outcomes:e,unhealthyCount:m,newlyUnhealthyCount:u}}}}import{assertTenantId as nr}from"@aiquants/authz-core";import{toInstant as or}from"@aiquants/mssql";import{and as ie,asc as Ge,eq as F,isNull as ae}from"drizzle-orm";var ir="The job that claimed this request ended without recording an outcome; whether the synchronization ran is unknown.",be=2e3;function gt(r,a,y){let d=r,l=y.ensureConnection??(async()=>{}),s=a.TDDirectorySyncRequest,h=nr(y.tenantId,"createSyncRequestQueue(tenantId)"),w=y.maxErrorLength??be;if(!Number.isInteger(w)||w<1||w>be)throw new TypeError(`[@aiquants/auth-directory-drizzle] maxErrorLength must be an integer within 1..${be}, received ${String(y.maxErrorLength)}`);let b={async claim(){await l();let g=await d.select({id:s.id}).from(s).where(ie(F(s.tenantId,h),ae(s.pickedAt))).orderBy(Ge(s.id));for(let i of g){let o=(await d.update(s).set({pickedAt:y.now()}).output().where(ie(F(s.tenantId,h),F(s.id,Number(i.id)),ae(s.pickedAt))))?.[0];if(!o)continue;if(o.requestedBy===void 0||o.requestedBy===null)throw new TypeError(`[@aiquants/auth-directory-drizzle] the driver returned a row without requestedBy for request ${String(o.id)}`);let e=o.groupId??null;return{id:Number(o.id),tenantId:h,groupId:e===null?null:Number(e),requestedBy:String(o.requestedBy)}}return null},async finish(g,i){await l();let t=i.error?i.error.slice(0,w):null;return!!(await d.update(s).set({finishedAt:y.now(),result:i.result,error:t}).output().where(ie(F(s.tenantId,h),F(s.id,g),ae(s.finishedAt))))?.length},async listStranded(g){await l();let i=await d.select({id:s.id,pickedAt:s.pickedAt,requestedAt:s.requestedAt}).from(s).where(ie(F(s.tenantId,h),ae(s.finishedAt))).orderBy(Ge(s.id)),t=o=>{let e=or(o);return Number.isNaN(e.getTime())||e<g};return{stranded:i.filter(o=>o.pickedAt!==null&&t(o.pickedAt)).map(o=>Number(o.id)),unclaimed:i.filter(o=>o.pickedAt===null&&t(o.requestedAt)).map(o=>Number(o.id))}},async abandonStranded(g){let{stranded:i}=await b.listStranded(g),t=[];for(let o of i)try{await b.finish(o,{result:"failed",error:ir})&&t.push(o)}catch(e){y.onCloseFailed?.(o,e)}return t}};return b}import{normalizeAuthEmail as se}from"@aiquants/auth-core";import{assertTenantId as Me,assertTenantMatch as ar}from"@aiquants/authz-core";import{chunk as Ce,chunkByParameters as _e,MAX_IN_LIST as sr,MAX_PARAMETERS as ur,RESERVED_PARAMETERS as dr}from"@aiquants/mssql";import{and as L,eq as R,inArray as fe,isNull as lr,notInArray as cr,or as pr}from"drizzle-orm";var mr=7,yr=4,Pe=1e3;function gr(r){if(!(r instanceof Error))return!1;let a=r;return a.number===void 0&&a.code===void 0&&r.name==="Error"}function At(r,a,y,d){let l=r,s=d.ensureConnection??(async()=>{}),h=N(l,d),w=d.now??(()=>new Date),{TDGroupExternalLink:b,TDGroupMemberEmail:g}=a,{TMGroup:i,TDGroupMember:t}=y,o=d.actor;return{async listSyncableLinks(e){await s();let m=Me(e.tenantId,"listSyncableLinks(tenantId)"),u=[R(b.isSyncEnabled,!0),R(b.tenantId,m)];return e.groupId!==void 0&&u.push(R(b.groupId,e.groupId)),(await l.select({linkId:b.id,groupId:b.groupId,linkTenantId:b.tenantId,groupTenantId:i.tenantId,groupKey:i.groupKey,isGroupActive:i.isActive,provider:b.provider,externalId:b.externalId,externalKey:b.externalKey,membershipMode:b.membershipMode,vanishedObservations:b.vanishedObservations}).from(b).innerJoin(i,L(R(i.id,b.groupId),R(i.tenantId,m))).where(L(...u))).map(S=>(ar(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:J(String(S.membershipMode)),isGroupActive:!!S.isGroupActive,vanishedObservations:Number(S.vanishedObservations??0)}))},async loadGroupSnapshot(e){await s();let[m,u]=await Promise.all([l.select({email:g.email,externalUserId:g.externalUserId,resolvedUserId:g.resolvedUserId}).from(g).where(R(g.groupId,e.groupId)),l.select({userId:t.userId}).from(t).where(R(t.groupId,e.groupId))]);return{ledger:m.map(f=>({email:String(f.email),...f.externalUserId?{externalUserId:String(f.externalUserId)}:{},resolvedUserId:f.resolvedUserId===null?null:Number(f.resolvedUserId)})),projectedUserIds:u.map(f=>Number(f.userId))}},resolveMembers(e){return d.memberResolver.resolve(e)},async applyPlan(e,m,u){Me(e.tenantId,"applyPlan(link.tenantId)"),await s(),(m.ledgerUpserts.length>0||m.projectionAdds.length>0)&&await h(async A=>{let _=m.ledgerUpserts.map(p=>({...p,email:se(p.email)})),n=new Map;for(let p of Ce(_.map(I=>I.email))){let I=await A.select({id:g.id,email:g.email}).from(g).where(L(R(g.groupId,e.groupId),fe(g.email,p)));for(let D of I)n.set(String(D.email),Number(D.id))}let c=_.filter(p=>!n.has(p.email));for(let p of _e(c,mr))await A.insert(g).output().values(p.map(I=>({groupId:e.groupId,email:I.email,externalUserId:I.externalUserId??null,resolvedUserId:I.resolvedUserId,observedAt:u,createdAt:u,createdBy:o})));for(let p of _){let I=n.get(p.email);I!==void 0&&await A.update(g).set({externalUserId:p.externalUserId??null,resolvedUserId:p.resolvedUserId,observedAt:u,updatedAt:u,updatedBy:o}).output().where(R(g.id,I))}for(let p of _e(m.projectionAdds,yr)){let I=new Set((await A.select({userId:t.userId}).from(t).where(L(R(t.groupId,e.groupId),fe(t.userId,p)))).map(k=>Number(k.userId))),D=p.filter(k=>!I.has(k));D.length>0&&await A.insert(t).output().values(D.map(k=>({groupId:e.groupId,userId:k,createdAt:u,createdBy:o})))}});let f=[],S=[];for(let A of m.projectionRemoves)try{await d.lockoutGuard.guardedWrite(e.tenantId,{removeGroupMembership:{groupId:e.groupId,userId:A}},"remove-group-member",_=>_.delete(t).where(L(R(t.groupId,e.groupId),R(t.userId,A))))}catch(_){gr(_)?f.push({userId:A,applied:!1,refusal:_.message}):S.push({userId:A,cause:oe(_)})}let T=f.map(A=>A.userId);if(m.ledgerDeletes.length>0){let A=Math.max(1,ur-dr-1-T.length);await h(async _=>{for(let n of Ce(m.ledgerDeletes,Math.min(sr,A))){let c=T.length>0?pr(lr(g.resolvedUserId),cr(g.resolvedUserId,T)):void 0;await _.delete(g).where(L(R(g.groupId,e.groupId),fe(g.email,n),...c?[c]:[]))}})}return{refusals:f,failures:S}},async resolveExternalIdentity(e,m){await s(),!(e.externalId===m.externalId&&e.externalKey===se(m.externalKey))&&await l.update(b).set({externalId:m.externalId,externalKey:se(m.externalKey)}).output().where(L(R(b.id,e.linkId),R(b.tenantId,e.tenantId)))},async recordSuccess(e,m,u){await s(),await h(async f=>{await f.update(b).set({syncedAt:u,syncError:null,vanishedObservations:0,externalId:m.externalId,externalKey:se(m.externalKey),updatedAt:u,updatedBy:o}).output().where(L(R(b.id,e.linkId),R(b.tenantId,e.tenantId))),e.isGroupActive||await f.update(i).set({isActive:!0,updatedAt:u,updatedBy:o}).output().where(L(R(i.id,e.groupId),R(i.tenantId,e.tenantId)))})},async recordFailure(e,m){await s(),await l.update(b).set({syncError:m.slice(0,Pe),updatedAt:w(),updatedBy:o}).output().where(L(R(b.id,e.linkId),R(b.tenantId,e.tenantId)))},async recordVanishedObservation(e,m){await s();let u=e.vanishedObservations+1;return await l.update(b).set({vanishedObservations:u,syncError:m.slice(0,Pe),updatedAt:w(),updatedBy:o}).output().where(L(R(b.id,e.linkId),R(b.tenantId,e.tenantId))),u},async markGroupInactive(e){await s(),await l.update(i).set({isActive:!1,updatedAt:w(),updatedBy:o}).output().where(L(R(i.id,e.groupId),R(i.tenantId,e.tenantId),R(i.isActive,!0)))}}}export{ye as AUTH_DIRECTORY_PROVIDER_COLUMN,tt as AUTH_DIRECTORY_TENANT_SCOPED_TABLES,Se as MEMBERSHIP_MODES,he as NEWLY_UNHEALTHY_SYNC_OUTCOMES,ir as STRANDED_REASON,Ee as UNHEALTHY_SYNC_OUTCOMES,Rr as assertGroupMayBeLinked,J as assertMembershipMode,oe as classifySyncFailure,Tr as createDirectoryCatalogReader,Sr as createDirectoryCatalogWriter,Ur as createDirectoryLinkStore,lt as createDirectorySync,At as createDirectorySyncRepository,Xr as createPromotionRepository,gt as createSyncRequestQueue,xr as defaultExternalGroupGuardMessage,Re as defaultSyncMessages,nt as defineAuthDirectorySchema,ee as findLedgerRowsForIdentity,jr as promoteLedgerForUser,N as transactionRunner,kr as withExternalGroupGuards};
|
|
2
|
-
//# sourceMappingURL=index.mjs.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiquants/auth-directory-drizzle",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Drizzle (mssql) adapter for @aiquants/auth-directory-core: the identity-side tables (external group links, upstream member ledger, tenant membership groups, login allowlist), the synchronization writer that runs inside the authz lockout guard, and an admin-store decorator that keeps admin roles off externally-sourced groups.",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -32,16 +32,16 @@
|
|
|
32
32
|
},
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@aiquants/auth-core": "^0.6.
|
|
36
|
-
"@aiquants/auth-directory-core": "^0.2.
|
|
37
|
-
"@aiquants/authz-core": "^0.6.
|
|
35
|
+
"@aiquants/auth-core": "^0.6.1",
|
|
36
|
+
"@aiquants/auth-directory-core": "^0.2.1",
|
|
37
|
+
"@aiquants/authz-core": "^0.6.1",
|
|
38
38
|
"@aiquants/authz-drizzle": "^1.3.0",
|
|
39
39
|
"drizzle-orm": ">=1.0.0-beta.4 <2.0.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@aiquants/auth-core": "0.6.
|
|
43
|
-
"@aiquants/auth-directory-core": "0.2.
|
|
44
|
-
"@aiquants/authz-core": "0.6.
|
|
42
|
+
"@aiquants/auth-core": "0.6.1",
|
|
43
|
+
"@aiquants/auth-directory-core": "0.2.1",
|
|
44
|
+
"@aiquants/authz-core": "0.6.1",
|
|
45
45
|
"@aiquants/authz-drizzle": "1.3.0",
|
|
46
46
|
"drizzle-orm": "1.0.0-rc.4",
|
|
47
47
|
"rimraf": "^6.1.2",
|
|
@@ -57,10 +57,10 @@
|
|
|
57
57
|
"access": "public"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@aiquants/mssql": "^0.1.
|
|
60
|
+
"@aiquants/mssql": "^0.1.2"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
63
|
-
"build": "tsup",
|
|
63
|
+
"build": "tsup && node ../../.config/scripts/strip-dts-comments.mjs dist",
|
|
64
64
|
"build:watch": "tsup --watch",
|
|
65
65
|
"dev": "tsup --watch",
|
|
66
66
|
"typecheck": "tsc --noEmit",
|