@aiquants/auth-directory-drizzle 0.10.0 → 0.11.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/README.md +43 -4
- package/dist/index.d.mts +134 -106
- package/dist/index.d.ts +134 -106
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @aiquants/auth-directory-drizzle
|
|
2
2
|
|
|
3
3
|
Drizzle (SQL Server) adapter for [`@aiquants/auth-directory-core`](../auth-directory-core). It owns
|
|
4
|
-
the **identity-side** tables
|
|
4
|
+
the **identity-side** tables, the four surfaces that operate on them, and the member resolver the
|
|
5
|
+
synchronization and the link store share, which reads the host's own user tables.
|
|
5
6
|
|
|
6
7
|
## Why these tables are not in `@aiquants/authz-drizzle`
|
|
7
8
|
|
|
@@ -40,8 +41,9 @@ sign-in.
|
|
|
40
41
|
| Export | Role |
|
|
41
42
|
| --- | --- |
|
|
42
43
|
| `defineAuthDirectorySchema` | Table factory (schema name and referenced tables injected) |
|
|
43
|
-
| `createDirectoryLinkStore` | Links, tenant membership proof, group allowlist, sync queue, health |
|
|
44
|
+
| `createDirectoryLinkStore` | Links, tenant membership proof, group allowlist, sync queue, health, unlinked members by reason |
|
|
44
45
|
| `createDirectorySyncRepository` | Storage operations for one sync run; **removals go through the authz lockout guard** |
|
|
46
|
+
| `createOpenidMemberResolver` | The identity rule for hosts that map OpenID subjects to users in a table, shared by the sync and the link store |
|
|
45
47
|
| `assertMembershipMode` | The single reader of `membership_mode` — no reader may default it |
|
|
46
48
|
| `promoteLedgerForUser` | First-sign-in promotion of ledger rows to real memberships |
|
|
47
49
|
| `withExternalGroupGuards` / `assertGroupMayBeLinked` | Keep administrative roles off externally-sourced groups, in both directions |
|
|
@@ -62,6 +64,38 @@ The text comes from the deployment: the guard's `message` and the link store's r
|
|
|
62
64
|
what the operator sees on screen. Failures that are not refusals — a group outside the tenant, a
|
|
63
65
|
failed read, a unique-key violation — carry no mark.
|
|
64
66
|
|
|
67
|
+
### Unlinked members are counted by reason, with the synchronization's own resolver
|
|
68
|
+
|
|
69
|
+
The ledger keeps every upstream member, including people the identity rule links to no local user.
|
|
70
|
+
One count under one label is false for some of them: a deactivated user, two users sharing an
|
|
71
|
+
address and someone who never signed in are different facts, cleared in different ways. So the link
|
|
72
|
+
store counts them by reason (`AuthDirectoryUnresolvedCounts` from `@aiquants/auth-core`):
|
|
73
|
+
|
|
74
|
+
| Reason | What the rule finds | Cleared by |
|
|
75
|
+
| --- | --- | --- |
|
|
76
|
+
| `ambiguous` | Two or more active users behind the upstream id's live mappings, or, without such a mapping, behind the address | Tidying the users |
|
|
77
|
+
| `claimedByAnother` | One active user for the address, whose live mappings name another upstream id | Confirming who the person is |
|
|
78
|
+
| `pendingSync` | A user now, while the ledger row still holds none | The next synchronization |
|
|
79
|
+
| `deactivated` | A mapping or an address user, but only inactive ones | Reactivation (nothing, when deliberate) |
|
|
80
|
+
| `unregistered` | Nothing, in any state | The person's first sign-in |
|
|
81
|
+
|
|
82
|
+
`listLinks` and `unresolvedByGroup(tenantId)` read the ledger rows with no resolved user and pass
|
|
83
|
+
them, at read time, through the `memberResolver` the store is given — **the same resolver the
|
|
84
|
+
synchronization uses**. The reason is never stored: it changes when a user is reactivated or
|
|
85
|
+
duplicates are merged, and a stored reason would then be false. A resolver answers every member in
|
|
86
|
+
the order asked; the store refuses to count an answer of another length or order.
|
|
87
|
+
|
|
88
|
+
`createOpenidMemberResolver(db, binding, { ensureConnection })` is that resolver for hosts that map
|
|
89
|
+
OpenID subjects to users in a table. `binding` names the user table and the mapping table through
|
|
90
|
+
their drizzle columns (`id`, `email`, `isActive`; `subject`, `userId`, `isActive`), so the package
|
|
91
|
+
holds no table names. It decides in this order, and the first match answers:
|
|
92
|
+
|
|
93
|
+
1. The upstream id's live mappings (mapping and user both active): one user links, more are `ambiguous`.
|
|
94
|
+
2. The address's active users: more than one is `ambiguous`; one whose live mappings name another
|
|
95
|
+
upstream id is `claimedByAnother`; otherwise that user links.
|
|
96
|
+
3. A mapping or an address user that exists only in an inactive state is `deactivated`.
|
|
97
|
+
4. Anything else is `unregistered`.
|
|
98
|
+
|
|
65
99
|
### Removals go through the lockout guard, and one refusal does not stop the group
|
|
66
100
|
|
|
67
101
|
Group membership reaches roles through `TDGroupRole`, so one upstream departure can take a tenant's
|
|
@@ -117,7 +151,8 @@ This adapter accepts drizzle through structural types, so `tsc` checks neither t
|
|
|
117
151
|
builder calls nor the presence of a tenant predicate: putting `.innerJoin()` before `.from()`,
|
|
118
152
|
dropping `.output()`, or forgetting `WHERE tenant_id = ?` all type-check. `link-store.spec.ts` and
|
|
119
153
|
`sync-repository.spec.ts` therefore record the actual call sequence against a fake builder and walk
|
|
120
|
-
each condition for the columns it references.
|
|
154
|
+
each condition for the columns it references. The link store's tenant-boundary table lists every
|
|
155
|
+
store method, and a method missing from it fails the suite.
|
|
121
156
|
|
|
122
157
|
`sync-repository.ts` is the only module that deletes rows, so it is tested directly rather than
|
|
123
158
|
through the orchestration's fake repository — an orchestration test proves nothing about whether the
|
|
@@ -129,12 +164,16 @@ direction, ignoring `appKey` when deciding what is administrative, dropping `is_
|
|
|
129
164
|
login gate, collapsing the identity precedence back to an `OR`, treating a removal-only run as
|
|
130
165
|
"unchanged", and skipping the resolver completeness check.
|
|
131
166
|
|
|
167
|
+
`openid-member-resolver.spec.ts` binds the resolver to tables whose drizzle names differ from their
|
|
168
|
+
SQL names, so a resolver that reads a column by name instead of through the binding fails. It walks
|
|
169
|
+
every reason, the order of the decision, and the bound parameters of each of the three reads.
|
|
170
|
+
|
|
132
171
|
## Install
|
|
133
172
|
|
|
134
173
|
```bash
|
|
135
174
|
pnpm add @aiquants/auth-directory-drizzle @aiquants/auth-directory-core @aiquants/auth-core @aiquants/authz-core @aiquants/authz-drizzle drizzle-orm zod
|
|
136
175
|
```
|
|
137
176
|
|
|
138
|
-
All
|
|
177
|
+
All four `@aiquants/*` peers are **value** imports and every one is listed in this package's tsup
|
|
139
178
|
`external`, so omitting any of them yields `ERR_MODULE_NOT_FOUND` at runtime rather than a type
|
|
140
179
|
error. `zod` is required transitively by `@aiquants/authz-core`.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { AuthDirectoryCatalogGroup, AuthDirectoryCatalogSnapshot, AuthGroupExternalLink, AuthDirectoryMembershipMode, AuthDirectoryStatus, AuthAllowlistEntry } from "@aiquants/auth-core";
|
|
1
|
+
import { AuthDirectoryCatalogGroup, AuthDirectoryCatalogSnapshot, AuthDirectoryUnresolvedReason, AuthGroupExternalLink, AuthDirectoryMembershipMode as AuthDirectoryMembershipMode$1, AuthDirectoryUnresolvedCounts, AuthDirectoryStatus, AuthAllowlistEntry } from "@aiquants/auth-core";
|
|
2
2
|
import { TransactionRetryOptions } from "@aiquants/mssql";
|
|
3
3
|
import * as drizzle_orm_mssql_core from "drizzle-orm/mssql-core";
|
|
4
4
|
import { AnyMsSqlColumn } from "drizzle-orm/mssql-core";
|
|
5
5
|
import { AuthzAdminStore } from "@aiquants/authz-core";
|
|
6
6
|
import { AuthzTables, createAdminLockoutGuard } from "@aiquants/authz-drizzle";
|
|
7
|
-
import {
|
|
7
|
+
import { SyncAbort, DirectoryMember, AuthDirectoryMembershipMode, LocalGroupSnapshot, GroupSyncPlan, DirectoryGroup, TenantMembershipPolicy, DirectoryProvider, BlastRadiusPolicy } from "@aiquants/auth-directory-core";
|
|
8
8
|
type Rows<T> = PromiseLike<T[]>;
|
|
9
9
|
interface SelectChain<T> extends Rows<T> {
|
|
10
10
|
from(t: unknown): SelectChain<T>;
|
|
@@ -1461,96 +1461,6 @@ declare function findLedgerRowsForIdentity(db: DirectoryDb, tables: LedgerIdenti
|
|
|
1461
1461
|
externalUserId: string | null;
|
|
1462
1462
|
email: string;
|
|
1463
1463
|
}): Promise<LedgerIdentityRow[]>;
|
|
1464
|
-
type LinkableAssertion = {
|
|
1465
|
-
assertLinkableAndWrite(tenantId: string, groupId: number, write: (runner: unknown) => Promise<void>): Promise<void>;
|
|
1466
|
-
};
|
|
1467
|
-
type DirectoryLinkRefusals = {
|
|
1468
|
-
breakGlassLink: (groupKey: string) => string;
|
|
1469
|
-
allowlistNeedsLink: (groupKey: string) => string;
|
|
1470
|
-
};
|
|
1471
|
-
declare const defaultDirectoryLinkRefusals: DirectoryLinkRefusals;
|
|
1472
|
-
type DirectoryLinkStoreOptions = TransactionalOptions & {
|
|
1473
|
-
linkable: LinkableAssertion;
|
|
1474
|
-
refusals: DirectoryLinkRefusals;
|
|
1475
|
-
ensureConnection?: EnsureConnection;
|
|
1476
|
-
stalenessThresholdSec: number;
|
|
1477
|
-
isEnabled: boolean;
|
|
1478
|
-
provider: string;
|
|
1479
|
-
policyCacheTtlMs?: number;
|
|
1480
|
-
};
|
|
1481
|
-
type DirectoryLinkRow = AuthGroupExternalLink & {
|
|
1482
|
-
linkId: number;
|
|
1483
|
-
groupId: number;
|
|
1484
|
-
tenantId: string;
|
|
1485
|
-
};
|
|
1486
|
-
type LinkGroupInput = {
|
|
1487
|
-
provider: string;
|
|
1488
|
-
externalId: string;
|
|
1489
|
-
externalKey: string;
|
|
1490
|
-
membershipMode: AuthDirectoryMembershipMode;
|
|
1491
|
-
};
|
|
1492
|
-
type SyncRequestOutcome = "queued" | "already-pending";
|
|
1493
|
-
declare function createDirectoryLinkStore(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: DirectoryLinkStoreOptions): {
|
|
1494
|
-
listLinks(tenantId: string): Promise<DirectoryLinkRow[]>;
|
|
1495
|
-
externalGroupKey(tenantId: string, groupId: number, runner?: unknown): Promise<string | null>;
|
|
1496
|
-
linkGroup(tenantId: string, groupId: number, input: LinkGroupInput, actor: string): Promise<void>;
|
|
1497
|
-
unlinkGroup(tenantId: string, groupId: number): Promise<void>;
|
|
1498
|
-
setSyncEnabled(tenantId: string, groupId: number, isSyncEnabled: boolean, actor: string): Promise<void>;
|
|
1499
|
-
requestSync(tenantId: string, groupId: number | null, requestedBy: string): Promise<SyncRequestOutcome>;
|
|
1500
|
-
getStatus(tenantId: string): Promise<AuthDirectoryStatus>;
|
|
1501
|
-
loadTenantMembershipPolicy(tenantId: string): Promise<TenantMembershipPolicy>;
|
|
1502
|
-
setTenantMembershipGroup(tenantId: string, groupId: number, isBreakGlass: boolean, actor: string): Promise<void>;
|
|
1503
|
-
removeTenantMembershipGroup(tenantId: string, groupId: number): Promise<void>;
|
|
1504
|
-
setGroupActive(tenantId: string, groupId: number, isActive: boolean, actor: string): Promise<void>;
|
|
1505
|
-
listLedgerGroupIds(tenantId: string, identity: {
|
|
1506
|
-
externalUserId: string | null;
|
|
1507
|
-
email: string | null | undefined;
|
|
1508
|
-
}): Promise<number[]>;
|
|
1509
|
-
isEmailAllowedByGroup(email: string | null | undefined): Promise<boolean>;
|
|
1510
|
-
listAllowlistGroups(tenantId: string): Promise<AuthAllowlistEntry[]>;
|
|
1511
|
-
addAllowlistGroup(tenantId: string, groupId: number, description: string | undefined, actor: string): Promise<void>;
|
|
1512
|
-
removeAllowlistGroup(tenantId: string, id: number): Promise<void>;
|
|
1513
|
-
countProjectedMembers(tenantId: string, groupId: number): Promise<number>;
|
|
1514
|
-
};
|
|
1515
|
-
declare const MEMBERSHIP_MODES: readonly AuthDirectoryMembershipMode[];
|
|
1516
|
-
declare function assertMembershipMode(value: string): AuthDirectoryMembershipMode;
|
|
1517
|
-
declare class DirectoryOperatorError extends Error {
|
|
1518
|
-
readonly operatorFacing = true;
|
|
1519
|
-
constructor(message: string);
|
|
1520
|
-
}
|
|
1521
|
-
type PromotionRepository = {
|
|
1522
|
-
findLedgerMatches(args: {
|
|
1523
|
-
tenantId: string;
|
|
1524
|
-
externalUserId: string | null;
|
|
1525
|
-
email: string;
|
|
1526
|
-
}): Promise<Array<{
|
|
1527
|
-
ledgerId: number;
|
|
1528
|
-
groupId: number;
|
|
1529
|
-
tenantId: string;
|
|
1530
|
-
}>>;
|
|
1531
|
-
attachUserToGroups(args: {
|
|
1532
|
-
tenantId: string;
|
|
1533
|
-
userId: number;
|
|
1534
|
-
ledgerIds: number[];
|
|
1535
|
-
groupIds: number[];
|
|
1536
|
-
}): Promise<number[]>;
|
|
1537
|
-
};
|
|
1538
|
-
type PromotionResult = {
|
|
1539
|
-
addedGroupIds: number[];
|
|
1540
|
-
matchedLedgerIds: number[];
|
|
1541
|
-
};
|
|
1542
|
-
declare function promoteLedgerForUser(repository: PromotionRepository, args: {
|
|
1543
|
-
tenantId: string;
|
|
1544
|
-
userId: number;
|
|
1545
|
-
externalUserId: string | null;
|
|
1546
|
-
email: string | null | undefined;
|
|
1547
|
-
}): Promise<PromotionResult>;
|
|
1548
|
-
type PromotionRepositoryOptions = TransactionalOptions & {
|
|
1549
|
-
ensureConnection?: EnsureConnection;
|
|
1550
|
-
actor: string;
|
|
1551
|
-
now?: () => Date;
|
|
1552
|
-
};
|
|
1553
|
-
declare function createPromotionRepository(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: PromotionRepositoryOptions): PromotionRepository;
|
|
1554
1464
|
type SyncFailureCause = "denied" | "rateLimited" | "upstreamError" | "timeout" | "localWriteError" | "unknown";
|
|
1555
1465
|
declare function classifySyncFailure(error: unknown): SyncFailureCause;
|
|
1556
1466
|
type SyncMessages = {
|
|
@@ -1571,6 +1481,14 @@ type SyncMessages = {
|
|
|
1571
1481
|
foreignProvider: (linkProvider: string, runnerProvider: string) => string;
|
|
1572
1482
|
};
|
|
1573
1483
|
declare const defaultSyncMessages: SyncMessages;
|
|
1484
|
+
type MemberResolutionFailure = Exclude<AuthDirectoryUnresolvedReason, "pendingSync">;
|
|
1485
|
+
type ClassifiedDirectoryMember = DirectoryMember & ({
|
|
1486
|
+
resolvedUserId: number;
|
|
1487
|
+
unresolvedReason: null;
|
|
1488
|
+
} | {
|
|
1489
|
+
resolvedUserId: null;
|
|
1490
|
+
unresolvedReason: MemberResolutionFailure;
|
|
1491
|
+
});
|
|
1574
1492
|
type SyncLink = {
|
|
1575
1493
|
linkId: number;
|
|
1576
1494
|
groupId: number;
|
|
@@ -1579,7 +1497,7 @@ type SyncLink = {
|
|
|
1579
1497
|
provider: string;
|
|
1580
1498
|
externalId: string;
|
|
1581
1499
|
externalKey: string;
|
|
1582
|
-
membershipMode: AuthDirectoryMembershipMode
|
|
1500
|
+
membershipMode: AuthDirectoryMembershipMode;
|
|
1583
1501
|
isGroupActive: boolean;
|
|
1584
1502
|
vanishedObservations: number;
|
|
1585
1503
|
};
|
|
@@ -1644,7 +1562,7 @@ type ApplyReport = {
|
|
|
1644
1562
|
type DirectorySyncRepository = {
|
|
1645
1563
|
listSyncableLinks(scope: SyncScope): Promise<SyncLink[]>;
|
|
1646
1564
|
loadGroupSnapshot(link: SyncLink): Promise<LocalGroupSnapshot>;
|
|
1647
|
-
resolveMembers(members: readonly DirectoryMember[]): Promise<
|
|
1565
|
+
resolveMembers(members: readonly DirectoryMember[]): Promise<ClassifiedDirectoryMember[]>;
|
|
1648
1566
|
applyPlan(link: SyncLink, plan: GroupSyncPlan, observedAt: Date): Promise<ApplyReport>;
|
|
1649
1567
|
resolveExternalIdentity(link: SyncLink, upstreamGroup: DirectoryGroup): Promise<void>;
|
|
1650
1568
|
recordSuccess(link: SyncLink, group: DirectoryGroup, syncedAt: Date): Promise<void>;
|
|
@@ -1657,6 +1575,127 @@ type SyncLoggerPort = {
|
|
|
1657
1575
|
warn(message: string, ...args: unknown[]): void;
|
|
1658
1576
|
error(message: string, ...args: unknown[]): void;
|
|
1659
1577
|
};
|
|
1578
|
+
type MemberResolverPort = {
|
|
1579
|
+
resolve(members: readonly DirectoryMember[]): Promise<ClassifiedDirectoryMember[]>;
|
|
1580
|
+
};
|
|
1581
|
+
type SyncRepositoryOptions = TransactionalOptions & {
|
|
1582
|
+
ensureConnection?: EnsureConnection;
|
|
1583
|
+
now?: () => Date;
|
|
1584
|
+
memberResolver: MemberResolverPort;
|
|
1585
|
+
lockoutGuard: ReturnType<typeof createAdminLockoutGuard>;
|
|
1586
|
+
actor: string;
|
|
1587
|
+
};
|
|
1588
|
+
declare function createDirectorySyncRepository(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: SyncRepositoryOptions): DirectorySyncRepository;
|
|
1589
|
+
type LinkableAssertion = {
|
|
1590
|
+
assertLinkableAndWrite(tenantId: string, groupId: number, write: (runner: unknown) => Promise<void>): Promise<void>;
|
|
1591
|
+
};
|
|
1592
|
+
type DirectoryLinkRefusals = {
|
|
1593
|
+
breakGlassLink: (groupKey: string) => string;
|
|
1594
|
+
allowlistNeedsLink: (groupKey: string) => string;
|
|
1595
|
+
};
|
|
1596
|
+
declare const defaultDirectoryLinkRefusals: DirectoryLinkRefusals;
|
|
1597
|
+
type DirectoryLinkStoreOptions = TransactionalOptions & {
|
|
1598
|
+
linkable: LinkableAssertion;
|
|
1599
|
+
refusals: DirectoryLinkRefusals;
|
|
1600
|
+
ensureConnection?: EnsureConnection;
|
|
1601
|
+
memberResolver: MemberResolverPort;
|
|
1602
|
+
stalenessThresholdSec: number;
|
|
1603
|
+
isEnabled: boolean;
|
|
1604
|
+
provider: string;
|
|
1605
|
+
policyCacheTtlMs?: number;
|
|
1606
|
+
};
|
|
1607
|
+
type DirectoryLinkRow = AuthGroupExternalLink & {
|
|
1608
|
+
linkId: number;
|
|
1609
|
+
groupId: number;
|
|
1610
|
+
tenantId: string;
|
|
1611
|
+
};
|
|
1612
|
+
type LinkGroupInput = {
|
|
1613
|
+
provider: string;
|
|
1614
|
+
externalId: string;
|
|
1615
|
+
externalKey: string;
|
|
1616
|
+
membershipMode: AuthDirectoryMembershipMode$1;
|
|
1617
|
+
};
|
|
1618
|
+
type SyncRequestOutcome = "queued" | "already-pending";
|
|
1619
|
+
declare function createDirectoryLinkStore(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: DirectoryLinkStoreOptions): {
|
|
1620
|
+
listLinks(tenantId: string): Promise<DirectoryLinkRow[]>;
|
|
1621
|
+
unresolvedByGroup(tenantId: string): Promise<ReadonlyMap<number, AuthDirectoryUnresolvedCounts>>;
|
|
1622
|
+
externalGroupKey(tenantId: string, groupId: number, runner?: unknown): Promise<string | null>;
|
|
1623
|
+
linkGroup(tenantId: string, groupId: number, input: LinkGroupInput, actor: string): Promise<void>;
|
|
1624
|
+
unlinkGroup(tenantId: string, groupId: number): Promise<void>;
|
|
1625
|
+
setSyncEnabled(tenantId: string, groupId: number, isSyncEnabled: boolean, actor: string): Promise<void>;
|
|
1626
|
+
requestSync(tenantId: string, groupId: number | null, requestedBy: string): Promise<SyncRequestOutcome>;
|
|
1627
|
+
getStatus(tenantId: string): Promise<AuthDirectoryStatus>;
|
|
1628
|
+
loadTenantMembershipPolicy(tenantId: string): Promise<TenantMembershipPolicy>;
|
|
1629
|
+
setTenantMembershipGroup(tenantId: string, groupId: number, isBreakGlass: boolean, actor: string): Promise<void>;
|
|
1630
|
+
removeTenantMembershipGroup(tenantId: string, groupId: number): Promise<void>;
|
|
1631
|
+
setGroupActive(tenantId: string, groupId: number, isActive: boolean, actor: string): Promise<void>;
|
|
1632
|
+
listLedgerGroupIds(tenantId: string, identity: {
|
|
1633
|
+
externalUserId: string | null;
|
|
1634
|
+
email: string | null | undefined;
|
|
1635
|
+
}): Promise<number[]>;
|
|
1636
|
+
isEmailAllowedByGroup(email: string | null | undefined): Promise<boolean>;
|
|
1637
|
+
listAllowlistGroups(tenantId: string): Promise<AuthAllowlistEntry[]>;
|
|
1638
|
+
addAllowlistGroup(tenantId: string, groupId: number, description: string | undefined, actor: string): Promise<void>;
|
|
1639
|
+
removeAllowlistGroup(tenantId: string, id: number): Promise<void>;
|
|
1640
|
+
countProjectedMembers(tenantId: string, groupId: number): Promise<number>;
|
|
1641
|
+
};
|
|
1642
|
+
declare const MEMBERSHIP_MODES: readonly AuthDirectoryMembershipMode$1[];
|
|
1643
|
+
declare function assertMembershipMode(value: string): AuthDirectoryMembershipMode$1;
|
|
1644
|
+
type OpenidIdentityBinding = {
|
|
1645
|
+
users: {
|
|
1646
|
+
table: unknown;
|
|
1647
|
+
id: AnyMsSqlColumn;
|
|
1648
|
+
email: AnyMsSqlColumn;
|
|
1649
|
+
isActive: AnyMsSqlColumn;
|
|
1650
|
+
};
|
|
1651
|
+
subjects: {
|
|
1652
|
+
table: unknown;
|
|
1653
|
+
subject: AnyMsSqlColumn;
|
|
1654
|
+
userId: AnyMsSqlColumn;
|
|
1655
|
+
isActive: AnyMsSqlColumn;
|
|
1656
|
+
};
|
|
1657
|
+
};
|
|
1658
|
+
type OpenidMemberResolverOptions = {
|
|
1659
|
+
ensureConnection?: EnsureConnection;
|
|
1660
|
+
};
|
|
1661
|
+
declare function createOpenidMemberResolver(dbUnknown: unknown, binding: OpenidIdentityBinding, opts: OpenidMemberResolverOptions): MemberResolverPort;
|
|
1662
|
+
declare class DirectoryOperatorError extends Error {
|
|
1663
|
+
readonly operatorFacing = true;
|
|
1664
|
+
constructor(message: string);
|
|
1665
|
+
}
|
|
1666
|
+
type PromotionRepository = {
|
|
1667
|
+
findLedgerMatches(args: {
|
|
1668
|
+
tenantId: string;
|
|
1669
|
+
externalUserId: string | null;
|
|
1670
|
+
email: string;
|
|
1671
|
+
}): Promise<Array<{
|
|
1672
|
+
ledgerId: number;
|
|
1673
|
+
groupId: number;
|
|
1674
|
+
tenantId: string;
|
|
1675
|
+
}>>;
|
|
1676
|
+
attachUserToGroups(args: {
|
|
1677
|
+
tenantId: string;
|
|
1678
|
+
userId: number;
|
|
1679
|
+
ledgerIds: number[];
|
|
1680
|
+
groupIds: number[];
|
|
1681
|
+
}): Promise<number[]>;
|
|
1682
|
+
};
|
|
1683
|
+
type PromotionResult = {
|
|
1684
|
+
addedGroupIds: number[];
|
|
1685
|
+
matchedLedgerIds: number[];
|
|
1686
|
+
};
|
|
1687
|
+
declare function promoteLedgerForUser(repository: PromotionRepository, args: {
|
|
1688
|
+
tenantId: string;
|
|
1689
|
+
userId: number;
|
|
1690
|
+
externalUserId: string | null;
|
|
1691
|
+
email: string | null | undefined;
|
|
1692
|
+
}): Promise<PromotionResult>;
|
|
1693
|
+
type PromotionRepositoryOptions = TransactionalOptions & {
|
|
1694
|
+
ensureConnection?: EnsureConnection;
|
|
1695
|
+
actor: string;
|
|
1696
|
+
now?: () => Date;
|
|
1697
|
+
};
|
|
1698
|
+
declare function createPromotionRepository(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: PromotionRepositoryOptions): PromotionRepository;
|
|
1660
1699
|
type DirectorySyncConfig = {
|
|
1661
1700
|
provider: string;
|
|
1662
1701
|
directory: DirectoryProvider;
|
|
@@ -1704,15 +1743,4 @@ declare function createSyncRequestQueue(dbUnknown: unknown, tables: AuthDirector
|
|
|
1704
1743
|
listStranded(olderThan: Date): Promise<StrandedRequests>;
|
|
1705
1744
|
abandonStranded(olderThan: Date): Promise<number[]>;
|
|
1706
1745
|
};
|
|
1707
|
-
type MemberResolverPort
|
|
1708
|
-
resolve(members: readonly DirectoryMember[]): Promise<ResolvedDirectoryMember[]>;
|
|
1709
|
-
};
|
|
1710
|
-
type SyncRepositoryOptions = TransactionalOptions & {
|
|
1711
|
-
ensureConnection?: EnsureConnection;
|
|
1712
|
-
now?: () => Date;
|
|
1713
|
-
memberResolver: MemberResolverPort;
|
|
1714
|
-
lockoutGuard: ReturnType<typeof createAdminLockoutGuard>;
|
|
1715
|
-
actor: string;
|
|
1716
|
-
};
|
|
1717
|
-
declare function createDirectorySyncRepository(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: SyncRepositoryOptions): DirectorySyncRepository;
|
|
1718
|
-
export { AUTH_DIRECTORY_PROVIDER_COLUMN, AUTH_DIRECTORY_TENANT_SCOPED_TABLES, type ApplyReport, type AuthDirectoryTables, type ClaimedSyncRequest, type DeleteChain, type DirectoryCatalogEntry, type DirectoryCatalogReaderOptions, type DirectoryCatalogSnapshot, type DirectoryCatalogWriterOptions, type DirectoryDb, type DirectoryLinkRefusals, type DirectoryLinkRow, type DirectoryLinkStoreOptions, DirectoryOperatorError, type DirectorySyncConfig, type DirectorySyncRepository, type DirectorySyncRunResult, type EnsureConnection, type ExternalGroupGuardOptions, type ExternalLinkLookup, type InsertChain, type LedgerIdentityRow, type LedgerIdentityTables, type LinkGroupInput, type LinkableAssertion, MEMBERSHIP_MODES, type MemberResolverPort, NEWLY_UNHEALTHY_SYNC_OUTCOMES, type PromotionRepository, type PromotionRepositoryOptions, type PromotionResult, type ReferencedTable, type RemovalFailure, type RemovalOutcome, STRANDED_REASON, type SelectChain, type StrandedRequests, type SyncFailureCause, type SyncLink, type SyncLoggerPort, type SyncMessages, type SyncOutcome, type SyncRepositoryOptions, type SyncRequestOutcome, type SyncRequestQueueOptions, type SyncRequestResult, type SyncScope, type TransactionalOptions, UNHEALTHY_SYNC_OUTCOMES, type UpdateChain, assertGroupMayBeLinked, assertMembershipMode, classifySyncFailure, createDirectoryCatalogReader, createDirectoryCatalogWriter, createDirectoryLinkStore, createDirectorySync, createDirectorySyncRepository, createPromotionRepository, createSyncRequestQueue, defaultDirectoryLinkRefusals, defaultExternalGroupGuardMessage, defaultSyncMessages, defineAuthDirectorySchema, findLedgerRowsForIdentity, promoteLedgerForUser, transactionRunner, withExternalGroupGuards };
|
|
1746
|
+
export { AUTH_DIRECTORY_PROVIDER_COLUMN, AUTH_DIRECTORY_TENANT_SCOPED_TABLES, type ApplyReport, type AuthDirectoryTables, type ClaimedSyncRequest, type ClassifiedDirectoryMember, type DeleteChain, type DirectoryCatalogEntry, type DirectoryCatalogReaderOptions, type DirectoryCatalogSnapshot, type DirectoryCatalogWriterOptions, type DirectoryDb, type DirectoryLinkRefusals, type DirectoryLinkRow, type DirectoryLinkStoreOptions, DirectoryOperatorError, type DirectorySyncConfig, type DirectorySyncRepository, type DirectorySyncRunResult, type EnsureConnection, type ExternalGroupGuardOptions, type ExternalLinkLookup, type InsertChain, type LedgerIdentityRow, type LedgerIdentityTables, type LinkGroupInput, type LinkableAssertion, MEMBERSHIP_MODES, type MemberResolutionFailure, type MemberResolverPort, NEWLY_UNHEALTHY_SYNC_OUTCOMES, type OpenidIdentityBinding, type OpenidMemberResolverOptions, type PromotionRepository, type PromotionRepositoryOptions, type PromotionResult, type ReferencedTable, type RemovalFailure, type RemovalOutcome, STRANDED_REASON, type SelectChain, type StrandedRequests, type SyncFailureCause, type SyncLink, type SyncLoggerPort, type SyncMessages, type SyncOutcome, type SyncRepositoryOptions, type SyncRequestOutcome, type SyncRequestQueueOptions, type SyncRequestResult, type SyncScope, type TransactionalOptions, UNHEALTHY_SYNC_OUTCOMES, type UpdateChain, assertGroupMayBeLinked, assertMembershipMode, classifySyncFailure, createDirectoryCatalogReader, createDirectoryCatalogWriter, createDirectoryLinkStore, createDirectorySync, createDirectorySyncRepository, createOpenidMemberResolver, createPromotionRepository, createSyncRequestQueue, defaultDirectoryLinkRefusals, defaultExternalGroupGuardMessage, defaultSyncMessages, defineAuthDirectorySchema, findLedgerRowsForIdentity, promoteLedgerForUser, transactionRunner, withExternalGroupGuards };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { AuthDirectoryCatalogGroup, AuthDirectoryCatalogSnapshot, AuthGroupExternalLink, AuthDirectoryMembershipMode, AuthDirectoryStatus, AuthAllowlistEntry } from "@aiquants/auth-core";
|
|
1
|
+
import { AuthDirectoryCatalogGroup, AuthDirectoryCatalogSnapshot, AuthDirectoryUnresolvedReason, AuthGroupExternalLink, AuthDirectoryMembershipMode as AuthDirectoryMembershipMode$1, AuthDirectoryUnresolvedCounts, AuthDirectoryStatus, AuthAllowlistEntry } from "@aiquants/auth-core";
|
|
2
2
|
import { TransactionRetryOptions } from "@aiquants/mssql";
|
|
3
3
|
import * as drizzle_orm_mssql_core from "drizzle-orm/mssql-core";
|
|
4
4
|
import { AnyMsSqlColumn } from "drizzle-orm/mssql-core";
|
|
5
5
|
import { AuthzAdminStore } from "@aiquants/authz-core";
|
|
6
6
|
import { AuthzTables, createAdminLockoutGuard } from "@aiquants/authz-drizzle";
|
|
7
|
-
import {
|
|
7
|
+
import { SyncAbort, DirectoryMember, AuthDirectoryMembershipMode, LocalGroupSnapshot, GroupSyncPlan, DirectoryGroup, TenantMembershipPolicy, DirectoryProvider, BlastRadiusPolicy } from "@aiquants/auth-directory-core";
|
|
8
8
|
type Rows<T> = PromiseLike<T[]>;
|
|
9
9
|
interface SelectChain<T> extends Rows<T> {
|
|
10
10
|
from(t: unknown): SelectChain<T>;
|
|
@@ -1461,96 +1461,6 @@ declare function findLedgerRowsForIdentity(db: DirectoryDb, tables: LedgerIdenti
|
|
|
1461
1461
|
externalUserId: string | null;
|
|
1462
1462
|
email: string;
|
|
1463
1463
|
}): Promise<LedgerIdentityRow[]>;
|
|
1464
|
-
type LinkableAssertion = {
|
|
1465
|
-
assertLinkableAndWrite(tenantId: string, groupId: number, write: (runner: unknown) => Promise<void>): Promise<void>;
|
|
1466
|
-
};
|
|
1467
|
-
type DirectoryLinkRefusals = {
|
|
1468
|
-
breakGlassLink: (groupKey: string) => string;
|
|
1469
|
-
allowlistNeedsLink: (groupKey: string) => string;
|
|
1470
|
-
};
|
|
1471
|
-
declare const defaultDirectoryLinkRefusals: DirectoryLinkRefusals;
|
|
1472
|
-
type DirectoryLinkStoreOptions = TransactionalOptions & {
|
|
1473
|
-
linkable: LinkableAssertion;
|
|
1474
|
-
refusals: DirectoryLinkRefusals;
|
|
1475
|
-
ensureConnection?: EnsureConnection;
|
|
1476
|
-
stalenessThresholdSec: number;
|
|
1477
|
-
isEnabled: boolean;
|
|
1478
|
-
provider: string;
|
|
1479
|
-
policyCacheTtlMs?: number;
|
|
1480
|
-
};
|
|
1481
|
-
type DirectoryLinkRow = AuthGroupExternalLink & {
|
|
1482
|
-
linkId: number;
|
|
1483
|
-
groupId: number;
|
|
1484
|
-
tenantId: string;
|
|
1485
|
-
};
|
|
1486
|
-
type LinkGroupInput = {
|
|
1487
|
-
provider: string;
|
|
1488
|
-
externalId: string;
|
|
1489
|
-
externalKey: string;
|
|
1490
|
-
membershipMode: AuthDirectoryMembershipMode;
|
|
1491
|
-
};
|
|
1492
|
-
type SyncRequestOutcome = "queued" | "already-pending";
|
|
1493
|
-
declare function createDirectoryLinkStore(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: DirectoryLinkStoreOptions): {
|
|
1494
|
-
listLinks(tenantId: string): Promise<DirectoryLinkRow[]>;
|
|
1495
|
-
externalGroupKey(tenantId: string, groupId: number, runner?: unknown): Promise<string | null>;
|
|
1496
|
-
linkGroup(tenantId: string, groupId: number, input: LinkGroupInput, actor: string): Promise<void>;
|
|
1497
|
-
unlinkGroup(tenantId: string, groupId: number): Promise<void>;
|
|
1498
|
-
setSyncEnabled(tenantId: string, groupId: number, isSyncEnabled: boolean, actor: string): Promise<void>;
|
|
1499
|
-
requestSync(tenantId: string, groupId: number | null, requestedBy: string): Promise<SyncRequestOutcome>;
|
|
1500
|
-
getStatus(tenantId: string): Promise<AuthDirectoryStatus>;
|
|
1501
|
-
loadTenantMembershipPolicy(tenantId: string): Promise<TenantMembershipPolicy>;
|
|
1502
|
-
setTenantMembershipGroup(tenantId: string, groupId: number, isBreakGlass: boolean, actor: string): Promise<void>;
|
|
1503
|
-
removeTenantMembershipGroup(tenantId: string, groupId: number): Promise<void>;
|
|
1504
|
-
setGroupActive(tenantId: string, groupId: number, isActive: boolean, actor: string): Promise<void>;
|
|
1505
|
-
listLedgerGroupIds(tenantId: string, identity: {
|
|
1506
|
-
externalUserId: string | null;
|
|
1507
|
-
email: string | null | undefined;
|
|
1508
|
-
}): Promise<number[]>;
|
|
1509
|
-
isEmailAllowedByGroup(email: string | null | undefined): Promise<boolean>;
|
|
1510
|
-
listAllowlistGroups(tenantId: string): Promise<AuthAllowlistEntry[]>;
|
|
1511
|
-
addAllowlistGroup(tenantId: string, groupId: number, description: string | undefined, actor: string): Promise<void>;
|
|
1512
|
-
removeAllowlistGroup(tenantId: string, id: number): Promise<void>;
|
|
1513
|
-
countProjectedMembers(tenantId: string, groupId: number): Promise<number>;
|
|
1514
|
-
};
|
|
1515
|
-
declare const MEMBERSHIP_MODES: readonly AuthDirectoryMembershipMode[];
|
|
1516
|
-
declare function assertMembershipMode(value: string): AuthDirectoryMembershipMode;
|
|
1517
|
-
declare class DirectoryOperatorError extends Error {
|
|
1518
|
-
readonly operatorFacing = true;
|
|
1519
|
-
constructor(message: string);
|
|
1520
|
-
}
|
|
1521
|
-
type PromotionRepository = {
|
|
1522
|
-
findLedgerMatches(args: {
|
|
1523
|
-
tenantId: string;
|
|
1524
|
-
externalUserId: string | null;
|
|
1525
|
-
email: string;
|
|
1526
|
-
}): Promise<Array<{
|
|
1527
|
-
ledgerId: number;
|
|
1528
|
-
groupId: number;
|
|
1529
|
-
tenantId: string;
|
|
1530
|
-
}>>;
|
|
1531
|
-
attachUserToGroups(args: {
|
|
1532
|
-
tenantId: string;
|
|
1533
|
-
userId: number;
|
|
1534
|
-
ledgerIds: number[];
|
|
1535
|
-
groupIds: number[];
|
|
1536
|
-
}): Promise<number[]>;
|
|
1537
|
-
};
|
|
1538
|
-
type PromotionResult = {
|
|
1539
|
-
addedGroupIds: number[];
|
|
1540
|
-
matchedLedgerIds: number[];
|
|
1541
|
-
};
|
|
1542
|
-
declare function promoteLedgerForUser(repository: PromotionRepository, args: {
|
|
1543
|
-
tenantId: string;
|
|
1544
|
-
userId: number;
|
|
1545
|
-
externalUserId: string | null;
|
|
1546
|
-
email: string | null | undefined;
|
|
1547
|
-
}): Promise<PromotionResult>;
|
|
1548
|
-
type PromotionRepositoryOptions = TransactionalOptions & {
|
|
1549
|
-
ensureConnection?: EnsureConnection;
|
|
1550
|
-
actor: string;
|
|
1551
|
-
now?: () => Date;
|
|
1552
|
-
};
|
|
1553
|
-
declare function createPromotionRepository(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: PromotionRepositoryOptions): PromotionRepository;
|
|
1554
1464
|
type SyncFailureCause = "denied" | "rateLimited" | "upstreamError" | "timeout" | "localWriteError" | "unknown";
|
|
1555
1465
|
declare function classifySyncFailure(error: unknown): SyncFailureCause;
|
|
1556
1466
|
type SyncMessages = {
|
|
@@ -1571,6 +1481,14 @@ type SyncMessages = {
|
|
|
1571
1481
|
foreignProvider: (linkProvider: string, runnerProvider: string) => string;
|
|
1572
1482
|
};
|
|
1573
1483
|
declare const defaultSyncMessages: SyncMessages;
|
|
1484
|
+
type MemberResolutionFailure = Exclude<AuthDirectoryUnresolvedReason, "pendingSync">;
|
|
1485
|
+
type ClassifiedDirectoryMember = DirectoryMember & ({
|
|
1486
|
+
resolvedUserId: number;
|
|
1487
|
+
unresolvedReason: null;
|
|
1488
|
+
} | {
|
|
1489
|
+
resolvedUserId: null;
|
|
1490
|
+
unresolvedReason: MemberResolutionFailure;
|
|
1491
|
+
});
|
|
1574
1492
|
type SyncLink = {
|
|
1575
1493
|
linkId: number;
|
|
1576
1494
|
groupId: number;
|
|
@@ -1579,7 +1497,7 @@ type SyncLink = {
|
|
|
1579
1497
|
provider: string;
|
|
1580
1498
|
externalId: string;
|
|
1581
1499
|
externalKey: string;
|
|
1582
|
-
membershipMode: AuthDirectoryMembershipMode
|
|
1500
|
+
membershipMode: AuthDirectoryMembershipMode;
|
|
1583
1501
|
isGroupActive: boolean;
|
|
1584
1502
|
vanishedObservations: number;
|
|
1585
1503
|
};
|
|
@@ -1644,7 +1562,7 @@ type ApplyReport = {
|
|
|
1644
1562
|
type DirectorySyncRepository = {
|
|
1645
1563
|
listSyncableLinks(scope: SyncScope): Promise<SyncLink[]>;
|
|
1646
1564
|
loadGroupSnapshot(link: SyncLink): Promise<LocalGroupSnapshot>;
|
|
1647
|
-
resolveMembers(members: readonly DirectoryMember[]): Promise<
|
|
1565
|
+
resolveMembers(members: readonly DirectoryMember[]): Promise<ClassifiedDirectoryMember[]>;
|
|
1648
1566
|
applyPlan(link: SyncLink, plan: GroupSyncPlan, observedAt: Date): Promise<ApplyReport>;
|
|
1649
1567
|
resolveExternalIdentity(link: SyncLink, upstreamGroup: DirectoryGroup): Promise<void>;
|
|
1650
1568
|
recordSuccess(link: SyncLink, group: DirectoryGroup, syncedAt: Date): Promise<void>;
|
|
@@ -1657,6 +1575,127 @@ type SyncLoggerPort = {
|
|
|
1657
1575
|
warn(message: string, ...args: unknown[]): void;
|
|
1658
1576
|
error(message: string, ...args: unknown[]): void;
|
|
1659
1577
|
};
|
|
1578
|
+
type MemberResolverPort = {
|
|
1579
|
+
resolve(members: readonly DirectoryMember[]): Promise<ClassifiedDirectoryMember[]>;
|
|
1580
|
+
};
|
|
1581
|
+
type SyncRepositoryOptions = TransactionalOptions & {
|
|
1582
|
+
ensureConnection?: EnsureConnection;
|
|
1583
|
+
now?: () => Date;
|
|
1584
|
+
memberResolver: MemberResolverPort;
|
|
1585
|
+
lockoutGuard: ReturnType<typeof createAdminLockoutGuard>;
|
|
1586
|
+
actor: string;
|
|
1587
|
+
};
|
|
1588
|
+
declare function createDirectorySyncRepository(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: SyncRepositoryOptions): DirectorySyncRepository;
|
|
1589
|
+
type LinkableAssertion = {
|
|
1590
|
+
assertLinkableAndWrite(tenantId: string, groupId: number, write: (runner: unknown) => Promise<void>): Promise<void>;
|
|
1591
|
+
};
|
|
1592
|
+
type DirectoryLinkRefusals = {
|
|
1593
|
+
breakGlassLink: (groupKey: string) => string;
|
|
1594
|
+
allowlistNeedsLink: (groupKey: string) => string;
|
|
1595
|
+
};
|
|
1596
|
+
declare const defaultDirectoryLinkRefusals: DirectoryLinkRefusals;
|
|
1597
|
+
type DirectoryLinkStoreOptions = TransactionalOptions & {
|
|
1598
|
+
linkable: LinkableAssertion;
|
|
1599
|
+
refusals: DirectoryLinkRefusals;
|
|
1600
|
+
ensureConnection?: EnsureConnection;
|
|
1601
|
+
memberResolver: MemberResolverPort;
|
|
1602
|
+
stalenessThresholdSec: number;
|
|
1603
|
+
isEnabled: boolean;
|
|
1604
|
+
provider: string;
|
|
1605
|
+
policyCacheTtlMs?: number;
|
|
1606
|
+
};
|
|
1607
|
+
type DirectoryLinkRow = AuthGroupExternalLink & {
|
|
1608
|
+
linkId: number;
|
|
1609
|
+
groupId: number;
|
|
1610
|
+
tenantId: string;
|
|
1611
|
+
};
|
|
1612
|
+
type LinkGroupInput = {
|
|
1613
|
+
provider: string;
|
|
1614
|
+
externalId: string;
|
|
1615
|
+
externalKey: string;
|
|
1616
|
+
membershipMode: AuthDirectoryMembershipMode$1;
|
|
1617
|
+
};
|
|
1618
|
+
type SyncRequestOutcome = "queued" | "already-pending";
|
|
1619
|
+
declare function createDirectoryLinkStore(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: DirectoryLinkStoreOptions): {
|
|
1620
|
+
listLinks(tenantId: string): Promise<DirectoryLinkRow[]>;
|
|
1621
|
+
unresolvedByGroup(tenantId: string): Promise<ReadonlyMap<number, AuthDirectoryUnresolvedCounts>>;
|
|
1622
|
+
externalGroupKey(tenantId: string, groupId: number, runner?: unknown): Promise<string | null>;
|
|
1623
|
+
linkGroup(tenantId: string, groupId: number, input: LinkGroupInput, actor: string): Promise<void>;
|
|
1624
|
+
unlinkGroup(tenantId: string, groupId: number): Promise<void>;
|
|
1625
|
+
setSyncEnabled(tenantId: string, groupId: number, isSyncEnabled: boolean, actor: string): Promise<void>;
|
|
1626
|
+
requestSync(tenantId: string, groupId: number | null, requestedBy: string): Promise<SyncRequestOutcome>;
|
|
1627
|
+
getStatus(tenantId: string): Promise<AuthDirectoryStatus>;
|
|
1628
|
+
loadTenantMembershipPolicy(tenantId: string): Promise<TenantMembershipPolicy>;
|
|
1629
|
+
setTenantMembershipGroup(tenantId: string, groupId: number, isBreakGlass: boolean, actor: string): Promise<void>;
|
|
1630
|
+
removeTenantMembershipGroup(tenantId: string, groupId: number): Promise<void>;
|
|
1631
|
+
setGroupActive(tenantId: string, groupId: number, isActive: boolean, actor: string): Promise<void>;
|
|
1632
|
+
listLedgerGroupIds(tenantId: string, identity: {
|
|
1633
|
+
externalUserId: string | null;
|
|
1634
|
+
email: string | null | undefined;
|
|
1635
|
+
}): Promise<number[]>;
|
|
1636
|
+
isEmailAllowedByGroup(email: string | null | undefined): Promise<boolean>;
|
|
1637
|
+
listAllowlistGroups(tenantId: string): Promise<AuthAllowlistEntry[]>;
|
|
1638
|
+
addAllowlistGroup(tenantId: string, groupId: number, description: string | undefined, actor: string): Promise<void>;
|
|
1639
|
+
removeAllowlistGroup(tenantId: string, id: number): Promise<void>;
|
|
1640
|
+
countProjectedMembers(tenantId: string, groupId: number): Promise<number>;
|
|
1641
|
+
};
|
|
1642
|
+
declare const MEMBERSHIP_MODES: readonly AuthDirectoryMembershipMode$1[];
|
|
1643
|
+
declare function assertMembershipMode(value: string): AuthDirectoryMembershipMode$1;
|
|
1644
|
+
type OpenidIdentityBinding = {
|
|
1645
|
+
users: {
|
|
1646
|
+
table: unknown;
|
|
1647
|
+
id: AnyMsSqlColumn;
|
|
1648
|
+
email: AnyMsSqlColumn;
|
|
1649
|
+
isActive: AnyMsSqlColumn;
|
|
1650
|
+
};
|
|
1651
|
+
subjects: {
|
|
1652
|
+
table: unknown;
|
|
1653
|
+
subject: AnyMsSqlColumn;
|
|
1654
|
+
userId: AnyMsSqlColumn;
|
|
1655
|
+
isActive: AnyMsSqlColumn;
|
|
1656
|
+
};
|
|
1657
|
+
};
|
|
1658
|
+
type OpenidMemberResolverOptions = {
|
|
1659
|
+
ensureConnection?: EnsureConnection;
|
|
1660
|
+
};
|
|
1661
|
+
declare function createOpenidMemberResolver(dbUnknown: unknown, binding: OpenidIdentityBinding, opts: OpenidMemberResolverOptions): MemberResolverPort;
|
|
1662
|
+
declare class DirectoryOperatorError extends Error {
|
|
1663
|
+
readonly operatorFacing = true;
|
|
1664
|
+
constructor(message: string);
|
|
1665
|
+
}
|
|
1666
|
+
type PromotionRepository = {
|
|
1667
|
+
findLedgerMatches(args: {
|
|
1668
|
+
tenantId: string;
|
|
1669
|
+
externalUserId: string | null;
|
|
1670
|
+
email: string;
|
|
1671
|
+
}): Promise<Array<{
|
|
1672
|
+
ledgerId: number;
|
|
1673
|
+
groupId: number;
|
|
1674
|
+
tenantId: string;
|
|
1675
|
+
}>>;
|
|
1676
|
+
attachUserToGroups(args: {
|
|
1677
|
+
tenantId: string;
|
|
1678
|
+
userId: number;
|
|
1679
|
+
ledgerIds: number[];
|
|
1680
|
+
groupIds: number[];
|
|
1681
|
+
}): Promise<number[]>;
|
|
1682
|
+
};
|
|
1683
|
+
type PromotionResult = {
|
|
1684
|
+
addedGroupIds: number[];
|
|
1685
|
+
matchedLedgerIds: number[];
|
|
1686
|
+
};
|
|
1687
|
+
declare function promoteLedgerForUser(repository: PromotionRepository, args: {
|
|
1688
|
+
tenantId: string;
|
|
1689
|
+
userId: number;
|
|
1690
|
+
externalUserId: string | null;
|
|
1691
|
+
email: string | null | undefined;
|
|
1692
|
+
}): Promise<PromotionResult>;
|
|
1693
|
+
type PromotionRepositoryOptions = TransactionalOptions & {
|
|
1694
|
+
ensureConnection?: EnsureConnection;
|
|
1695
|
+
actor: string;
|
|
1696
|
+
now?: () => Date;
|
|
1697
|
+
};
|
|
1698
|
+
declare function createPromotionRepository(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: PromotionRepositoryOptions): PromotionRepository;
|
|
1660
1699
|
type DirectorySyncConfig = {
|
|
1661
1700
|
provider: string;
|
|
1662
1701
|
directory: DirectoryProvider;
|
|
@@ -1704,15 +1743,4 @@ declare function createSyncRequestQueue(dbUnknown: unknown, tables: AuthDirector
|
|
|
1704
1743
|
listStranded(olderThan: Date): Promise<StrandedRequests>;
|
|
1705
1744
|
abandonStranded(olderThan: Date): Promise<number[]>;
|
|
1706
1745
|
};
|
|
1707
|
-
type MemberResolverPort
|
|
1708
|
-
resolve(members: readonly DirectoryMember[]): Promise<ResolvedDirectoryMember[]>;
|
|
1709
|
-
};
|
|
1710
|
-
type SyncRepositoryOptions = TransactionalOptions & {
|
|
1711
|
-
ensureConnection?: EnsureConnection;
|
|
1712
|
-
now?: () => Date;
|
|
1713
|
-
memberResolver: MemberResolverPort;
|
|
1714
|
-
lockoutGuard: ReturnType<typeof createAdminLockoutGuard>;
|
|
1715
|
-
actor: string;
|
|
1716
|
-
};
|
|
1717
|
-
declare function createDirectorySyncRepository(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: SyncRepositoryOptions): DirectorySyncRepository;
|
|
1718
|
-
export { AUTH_DIRECTORY_PROVIDER_COLUMN, AUTH_DIRECTORY_TENANT_SCOPED_TABLES, type ApplyReport, type AuthDirectoryTables, type ClaimedSyncRequest, type DeleteChain, type DirectoryCatalogEntry, type DirectoryCatalogReaderOptions, type DirectoryCatalogSnapshot, type DirectoryCatalogWriterOptions, type DirectoryDb, type DirectoryLinkRefusals, type DirectoryLinkRow, type DirectoryLinkStoreOptions, DirectoryOperatorError, type DirectorySyncConfig, type DirectorySyncRepository, type DirectorySyncRunResult, type EnsureConnection, type ExternalGroupGuardOptions, type ExternalLinkLookup, type InsertChain, type LedgerIdentityRow, type LedgerIdentityTables, type LinkGroupInput, type LinkableAssertion, MEMBERSHIP_MODES, type MemberResolverPort, NEWLY_UNHEALTHY_SYNC_OUTCOMES, type PromotionRepository, type PromotionRepositoryOptions, type PromotionResult, type ReferencedTable, type RemovalFailure, type RemovalOutcome, STRANDED_REASON, type SelectChain, type StrandedRequests, type SyncFailureCause, type SyncLink, type SyncLoggerPort, type SyncMessages, type SyncOutcome, type SyncRepositoryOptions, type SyncRequestOutcome, type SyncRequestQueueOptions, type SyncRequestResult, type SyncScope, type TransactionalOptions, UNHEALTHY_SYNC_OUTCOMES, type UpdateChain, assertGroupMayBeLinked, assertMembershipMode, classifySyncFailure, createDirectoryCatalogReader, createDirectoryCatalogWriter, createDirectoryLinkStore, createDirectorySync, createDirectorySyncRepository, createPromotionRepository, createSyncRequestQueue, defaultDirectoryLinkRefusals, defaultExternalGroupGuardMessage, defaultSyncMessages, defineAuthDirectorySchema, findLedgerRowsForIdentity, promoteLedgerForUser, transactionRunner, withExternalGroupGuards };
|
|
1746
|
+
export { AUTH_DIRECTORY_PROVIDER_COLUMN, AUTH_DIRECTORY_TENANT_SCOPED_TABLES, type ApplyReport, type AuthDirectoryTables, type ClaimedSyncRequest, type ClassifiedDirectoryMember, type DeleteChain, type DirectoryCatalogEntry, type DirectoryCatalogReaderOptions, type DirectoryCatalogSnapshot, type DirectoryCatalogWriterOptions, type DirectoryDb, type DirectoryLinkRefusals, type DirectoryLinkRow, type DirectoryLinkStoreOptions, DirectoryOperatorError, type DirectorySyncConfig, type DirectorySyncRepository, type DirectorySyncRunResult, type EnsureConnection, type ExternalGroupGuardOptions, type ExternalLinkLookup, type InsertChain, type LedgerIdentityRow, type LedgerIdentityTables, type LinkGroupInput, type LinkableAssertion, MEMBERSHIP_MODES, type MemberResolutionFailure, type MemberResolverPort, NEWLY_UNHEALTHY_SYNC_OUTCOMES, type OpenidIdentityBinding, type OpenidMemberResolverOptions, type PromotionRepository, type PromotionRepositoryOptions, type PromotionResult, type ReferencedTable, type RemovalFailure, type RemovalOutcome, STRANDED_REASON, type SelectChain, type StrandedRequests, type SyncFailureCause, type SyncLink, type SyncLoggerPort, type SyncMessages, type SyncOutcome, type SyncRepositoryOptions, type SyncRequestOutcome, type SyncRequestQueueOptions, type SyncRequestResult, type SyncScope, type TransactionalOptions, UNHEALTHY_SYNC_OUTCOMES, type UpdateChain, assertGroupMayBeLinked, assertMembershipMode, classifySyncFailure, createDirectoryCatalogReader, createDirectoryCatalogWriter, createDirectoryLinkStore, createDirectorySync, createDirectorySyncRepository, createOpenidMemberResolver, createPromotionRepository, createSyncRequestQueue, defaultDirectoryLinkRefusals, defaultExternalGroupGuardMessage, defaultSyncMessages, defineAuthDirectorySchema, findLedgerRowsForIdentity, promoteLedgerForUser, transactionRunner, withExternalGroupGuards };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var ue=Object.defineProperty;var Ee=Object.getOwnPropertyDescriptor;var Me=Object.getOwnPropertyNames;var Ce=Object.prototype.hasOwnProperty;var Pe=(e,n)=>{for(var m in n)ue(e,m,{get:n[m],enumerable:!0})},_e=(e,n,m,d)=>{if(n&&typeof n=="object"||typeof n=="function")for(let l of Me(n))!Ce.call(e,l)&&l!==m&&ue(e,l,{get:()=>n[l],enumerable:!(d=Ee(n,l))||d.enumerable});return e};var Le=e=>_e(ue({},"__esModule",{value:!0}),e);var nr={};Pe(nr,{AUTH_DIRECTORY_PROVIDER_COLUMN:()=>oe,AUTH_DIRECTORY_TENANT_SCOPED_TABLES:()=>Ye,DirectoryOperatorError:()=>K,MEMBERSHIP_MODES:()=>le,NEWLY_UNHEALTHY_SYNC_OUTCOMES:()=>ie,STRANDED_REASON:()=>ke,UNHEALTHY_SYNC_OUTCOMES:()=>ge,assertGroupMayBeLinked:()=>Be,assertMembershipMode:()=>B,classifySyncFailure:()=>J,createDirectoryCatalogReader:()=>$e,createDirectoryCatalogWriter:()=>Ne,createDirectoryLinkStore:()=>je,createDirectorySync:()=>Qe,createDirectorySyncRepository:()=>tr,createPromotionRepository:()=>Je,createSyncRequestQueue:()=>Ze,defaultDirectoryLinkRefusals:()=>ze,defaultExternalGroupGuardMessage:()=>qe,defaultSyncMessages:()=>ye,defineAuthDirectorySchema:()=>Ve,findLedgerRowsForIdentity:()=>H,promoteLedgerForUser:()=>We,transactionRunner:()=>O,withExternalGroupGuards:()=>Ue});module.exports=Le(nr);var de=require("@aiquants/authz-core"),Z=require("@aiquants/mssql"),_=require("drizzle-orm");var be=require("@aiquants/mssql");function O(e,n){return m=>(0,be.runWithTransactionRetry)(d=>e.transaction(l=>d(()=>m(l))),n.transactionRetry)}var Ke=10,N={tenantId:64,provider:32,externalId:256,externalKey:320,name:256,description:1e3,actor:50},fe=2147483647;function Oe(e){if(e===void 0||!Number.isFinite(e))return null;let n=Math.trunc(e);return n<0?null:n>fe?fe:n}function j(e,n,m){let d=e.trim();if(d==="")throw new TypeError(`[@aiquants/auth-directory-drizzle] ${m} must not be blank`);if(d.length>n)throw new TypeError(`[@aiquants/auth-directory-drizzle] ${m} exceeds its column length (${n}); truncating it would change which rows it owns`);return d}function Ne(e,n,m){let d=e,l=m.ensureConnection??(async()=>{}),s=O(d,m),f=n.TDDirectoryCatalog,S=n.TMDirectoryCatalogObservation,w=h=>{let a=new Map;for(let t of h)a.set(t.externalId,t);return[...a.values()]};return{async replace(h,a,t){await l();let o=j((0,de.assertTenantId)(h,"replace(tenantId)"),N.tenantId,"tenantId"),r=j(a,N.provider,"provider"),g=j(m.actor,N.actor,"actor"),u=m.now(),I=w(t.map(T=>({tenantId:o,provider:r,externalId:T.externalId.slice(0,N.externalId),externalKey:T.externalKey.slice(0,N.externalKey),name:T.name.slice(0,N.name),description:T.description===void 0?null:T.description.slice(0,N.description),memberCount:Oe(T.memberCount),observedAt:u,createdAt:u,createdBy:g})));return await s(async T=>{await T.delete(f).where((0,_.and)((0,_.eq)(f.tenantId,o),(0,_.eq)(f.provider,r)));for(let A of(0,Z.chunkByParameters)(I,Ke))await T.insert(f).values(A);await T.delete(S).where((0,_.and)((0,_.eq)(S.tenantId,o),(0,_.eq)(S.provider,r))),await T.insert(S).values([{tenantId:o,provider:r,observedAt:u,entryCount:I.length,createdAt:u,createdBy:g,updatedAt:u,updatedBy:g}])}),I.length}}}function $e(e,n,m={}){let d=e,l=m.ensureConnection??(async()=>{}),s=n.TDDirectoryCatalog,f=n.TMDirectoryCatalogObservation;return{async read(S,w){await l();let h=j((0,de.assertTenantId)(S,"read(tenantId)"),N.tenantId,"tenantId"),a=j(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,h),(0,_.eq)(f.provider,a))).orderBy((0,_.asc)(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)}})),r=t[0];if(r===void 0)return{groups:o,observedAt:null};if(Number(r.entryCount)!==o.length)throw new Error(`[@aiquants/auth-directory-drizzle] catalog is inconsistent for (${h}, ${a}): the observation records ${r.entryCount} entries but ${o.length} rows are present`);let g=(0,Z.toInstant)(r.observedAt);if(!Number.isFinite(g.getTime()))throw new Error(`[@aiquants/auth-directory-drizzle] catalog observation for (${h}, ${a}) has an unreadable observed_at`);return{groups:o,observedAt:g}}}}var we=require("@aiquants/authz-core");var K=class extends Error{constructor(m){super(m);this.operatorFacing=!0;this.name="DirectoryOperatorError"}};var qe=(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 U(e,n,m,d){return new Set(e.filter(l=>n.has(l.roleId)&&l.appKey===m&&l.resourceKey===d).map(l=>l.action))}function X(e,n){return new Set(e.filter(m=>m.groupId===n).map(m=>m.roleId))}var W=e=>we.AUTHZ_ACTIONS.every(n=>e.has(n)),re=(e,n)=>e.find(m=>m.roleId===n)?.roleKey??`#${n}`;function ee(e,n,m,d){let l=[...n].find(f=>W(U(e,new Set([f]),m,d)));return l!==void 0?re(e,l):[...n].filter(f=>U(e,new Set([f]),m,d).size>0).map(f=>re(e,f)).join(" + ")}function Ue(e,n){return Ie(e,n)}function Ie(e,n){let m=e;return{...e,withTransaction:(d,l)=>m.withTransaction((s,f)=>d(Ie(s,n),f),l),async assignGroupRole(d,l,s,f){await m.withTransaction(async(S,w)=>{let h=await S.listGrants(d),a=X(await S.listGroupRoles(d),l);if(a.add(s),W(U(h,a,n.adminAppKey,n.adminResourceKey))){let t=await n.linkLookup.externalGroupKey(d,l,w);if(t!==null)throw new K(n.message(ee(h,a,n.adminAppKey,n.adminResourceKey),t))}await S.assignGroupRole(d,l,s,f)})},async updateGrant(d,l,s,f){let S=s.action;if(S===void 0)return m.updateGrant(d,l,s,f);await m.withTransaction(async(w,h)=>{let a=await w.listGrants(d),t=a.find(g=>g.id===l);if(t===void 0)return w.updateGrant(d,l,s,f);let r=(await w.listResources(d)).find(g=>g.id===t.resourceId);if(r?.appKey===n.adminAppKey&&r?.resourceKey===n.adminResourceKey){let g=await w.listGroupRoles(d);for(let u of new Set(g.filter(I=>I.roleId===t.roleId).map(I=>I.groupId))){let I=X(g,u),T=U(a.filter(x=>x.id!==l),I,n.adminAppKey,n.adminResourceKey);if(I.has(t.roleId)&&T.add(S),!W(T))continue;let A=await n.linkLookup.externalGroupKey(d,u,h);if(A!==null)throw new K(n.message(ee(a,I,n.adminAppKey,n.adminResourceKey)||re(a,t.roleId),A))}}await w.updateGrant(d,l,s,f)})},async createGrant(d,l,s){await m.withTransaction(async(f,S)=>{let h=(await f.listResources(d)).find(a=>a.id===l.resourceId);if(h?.appKey===n.adminAppKey&&h?.resourceKey===n.adminResourceKey){let a=await f.listGrants(d),t=await f.listGroupRoles(d);for(let o of new Set(t.filter(r=>r.roleId===l.roleId).map(r=>r.groupId))){let r=U(a,X(t,o),n.adminAppKey,n.adminResourceKey);if(r.add(l.action),!W(r))continue;let g=await n.linkLookup.externalGroupKey(d,o,S);if(g!==null)throw new K(n.message(ee(a,X(t,o),n.adminAppKey,n.adminResourceKey)||re(a,l.roleId),g))}}await f.createGrant(d,l,s)})}}}async function Be(e,n,m,d){let l=(await e.listGroupRoles(n)).filter(w=>w.groupId===m);if(l.length===0)return;let s=await e.listGrants(n),f=new Set(l.map(w=>w.roleId));if(!W(U(s,f,d.adminAppKey,d.adminResourceKey)))return;let S=l[0];if(S)throw new K(d.message(ee(s,f,d.adminAppKey,d.adminResourceKey),S.groupKey))}var $=require("drizzle-orm");async function H(e,n,m){let{ledger:d,group:l}=n,{tenantId:s,externalUserId:f,email:S}=m;if(f===null&&S==="")return[];let w={ledgerId:d.id,groupId:d.groupId,tenantId:l.tenantId,externalUserId:d.externalUserId},h=async g=>(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(g)).map(I=>({ledgerId:Number(I.ledgerId),groupId:Number(I.groupId),tenantId:String(I.tenantId),externalUserId:I.externalUserId===null?null:String(I.externalUserId)})),a=g=>{let u=new Map;for(let I of g)u.set(I.ledgerId,{ledgerId:I.ledgerId,groupId:I.groupId,tenantId:I.tenantId});return[...u.values()].sort((I,T)=>I.ledgerId-T.ledgerId)},t=f===null?[]:await h((0,$.eq)(d.externalUserId,f));if(S==="")return a(t);let o=await h((0,$.eq)(d.email,S)),r=g=>g.externalUserId!==null&&g.externalUserId!==f;return a([...t,...o.filter(g=>!r(g))])}var te=require("@aiquants/auth-core"),ve=require("@aiquants/auth-directory-core"),P=require("@aiquants/authz-core"),ce=require("@aiquants/mssql"),c=require("drizzle-orm");var le=["direct","transitive"];function B(e){if(!le.includes(e))throw new TypeError(`[@aiquants/auth-directory-drizzle] membershipMode must be one of ${le.join(" | ")}, received ${JSON.stringify(e)}`);return e}var ze={breakGlassLink:e=>`\u30B0\u30EB\u30FC\u30D7 (${e}) \u306F\u7DCA\u6025\u7528 (break-glass) \u306E\u30C6\u30CA\u30F3\u30C8\u5E30\u5C5E\u306E\u7D4C\u8DEF\u306B\u6307\u5B9A\u3055\u308C\u3066\u3044\u308B\u305F\u3081\u3001\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3068\u9023\u643A\u3067\u304D\u307E\u305B\u3093\u3002\u3053\u306E\u7D4C\u8DEF\u306F\u3001\u540C\u671F\u304C\u89E6\u308C\u306A\u3044\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u30B0\u30EB\u30FC\u30D7\u306B\u9650\u3063\u3066\u3044\u307E\u3059\u3002`,allowlistNeedsLink:e=>`\u30B0\u30EB\u30FC\u30D7 (${e}) \u306F\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u305F\u3081\u3001\u30B5\u30A4\u30F3\u30A4\u30F3\u8A31\u53EF\u306B\u4F7F\u3048\u307E\u305B\u3093\u3002\u30B0\u30EB\u30FC\u30D7\u5358\u4F4D\u306E\u30B5\u30A4\u30F3\u30A4\u30F3\u8A31\u53EF\u306F\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u30E1\u30F3\u30D0\u30FC\u53F0\u5E33\u3067\u5224\u5B9A\u3059\u308B\u306E\u3067\u3001\u4E0A\u6D41\u3068\u9023\u643A\u3057\u305F\u30B0\u30EB\u30FC\u30D7\u3060\u3051\u3092\u6307\u5B9A\u3067\u304D\u307E\u3059\u3002`};function Fe(e){let n=typeof e=="object"&&e!==null&&"number"in e?e.number:void 0;return n===2601||n===2627}function je(e,n,m,d){let l=e,s=d.ensureConnection??(async()=>{}),f=O(l,d),S=d.policyCacheTtlMs??0;if(!Number.isFinite(S)||S<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] policyCacheTtlMs must be a non-negative number, received ${String(d.policyCacheTtlMs)}`);let w=new Map,h=i=>w.delete(i),{TDGroupExternalLink:a,TDGroupMemberEmail:t,TDTenantMembershipGroup:o,TDAllowlistGroup:r,TDDirectorySyncRequest:g}=n,{TMGroup:u,TDGroupMember:I}=m;async function T(i,y,b,v){if((await i.select({groupId:o.groupId}).from(o).where((0,c.and)((0,c.eq)(o.tenantId,y),(0,c.eq)(o.groupId,b),(0,c.eq)(o.isBreakGlass,!0)))).length>0)throw new K(d.refusals.breakGlassLink(v))}async function A(i,y,b){let[v]=await i.select({groupKey:u.groupKey}).from(u).where((0,c.and)((0,c.eq)(u.id,b),(0,c.eq)(u.tenantId,y)));if(!v)throw new Error(`Group not found in tenant: ${b}`);return String(v.groupKey)}function x(i,y){return{linkId:Number(i.linkId),groupId:Number(i.groupId),tenantId:String(i.tenantId),provider:String(i.provider),externalId:String(i.externalId),externalKey:String(i.externalKey),membershipMode:B(String(i.membershipMode)),isSyncEnabled:!!i.isSyncEnabled,...i.syncedAt?{syncedAt:(0,ce.toInstant)(i.syncedAt).toISOString()}:{},...i.syncError?{syncError:String(i.syncError)}:{},unresolvedCount:y}}async function G(i,y){let b=await i.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,y))).where((0,c.isNull)(t.resolvedUserId)).groupBy(t.groupId);return new Map(b.map(v=>[Number(v.groupId),Number(v.n)]))}return{async listLinks(i){(0,P.assertTenantId)(i,"listLinks(tenantId)"),await s();let y=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,i)).orderBy((0,c.asc)(a.groupId)),b=await G(l,i);return y.map(v=>((0,P.assertTenantMatch)(i,String(v.tenantId),"listLinks(row.tenantId)"),x(v,b.get(Number(v.groupId))??0)))},async externalGroupKey(i,y,b){(0,P.assertTenantId)(i,"externalGroupKey(tenantId)"),await s();let[v]=await(b??l).select({externalKey:a.externalKey}).from(a).where((0,c.and)((0,c.eq)(a.groupId,y),(0,c.eq)(a.tenantId,i)));return v?String(v.externalKey):null},async linkGroup(i,y,b,v){(0,P.assertTenantId)(i,"linkGroup(tenantId)");let k=B(b.membershipMode),R=b.externalId.trim(),E=(0,te.normalizeAuthEmail)(b.externalKey);if(R===""||E==="")throw new TypeError("[@aiquants/auth-directory-drizzle] externalId and externalKey must be non-empty");await s(),await d.linkable.assertLinkableAndWrite(i,y,async Q=>{let M=Q,se=await A(M,i,y);await T(M,i,y,se),await M.insert(a).output().values({groupId:y,tenantId:i,provider:b.provider,externalId:R,externalKey:E,membershipMode:k,isSyncEnabled:!0,vanishedObservations:0,createdAt:new Date,createdBy:v})}),h(i)},async unlinkGroup(i,y){(0,P.assertTenantId)(i,"unlinkGroup(tenantId)"),await s(),await f(async b=>{await A(b,i,y),await b.delete(t).where((0,c.eq)(t.groupId,y)),await b.delete(r).where((0,c.and)((0,c.eq)(r.groupId,y),(0,c.eq)(r.tenantId,i))),await b.delete(a).where((0,c.and)((0,c.eq)(a.groupId,y),(0,c.eq)(a.tenantId,i)))}),h(i)},async setSyncEnabled(i,y,b,v){(0,P.assertTenantId)(i,"setSyncEnabled(tenantId)"),await s(),await l.update(a).set({isSyncEnabled:b,updatedAt:new Date,updatedBy:v}).output().where((0,c.and)((0,c.eq)(a.groupId,y),(0,c.eq)(a.tenantId,i)))},async requestSync(i,y,b){return(0,P.assertTenantId)(i,"requestSync(tenantId)"),await s(),l.transaction(async v=>{if(y!==null&&await A(v,i,y),(await v.select({id:g.id}).from(g).where((0,c.and)((0,c.eq)(g.tenantId,i),y===null?(0,c.isNull)(g.groupId):(0,c.eq)(g.groupId,y),(0,c.isNull)(g.pickedAt)))).length>0)return"already-pending";try{await v.insert(g).output().values({tenantId:i,groupId:y,requestedBy:b,requestedAt:new Date,createdAt:new Date,createdBy:b})}catch(R){if(!Fe(R))throw R;return"already-pending"}return"queued"})},async getStatus(i){(0,P.assertTenantId)(i,"getStatus(tenantId)"),await s();let y=await l.select({groupId:a.groupId,isSyncEnabled:a.isSyncEnabled,syncedAt:a.syncedAt,syncError:a.syncError}).from(a).where((0,c.eq)(a.tenantId,i)),[b]=await l.select({n:(0,c.count)()}).from(g).where((0,c.and)((0,c.eq)(g.tenantId,i),(0,c.isNull)(g.finishedAt))),v=y.filter(M=>!!M.isSyncEnabled),k=v.map(M=>M.syncedAt?(0,ce.toInstant)(M.syncedAt).getTime():null),R=k.length>0&&k.every(M=>M!==null)?Math.min(...k):null,Q=y.length>0&&v.length===0||v.length>0&&(R===null||Date.now()-R>d.stalenessThresholdSec*1e3);return{isEnabled:d.isEnabled,provider:d.provider,linkedGroupCount:y.length,...R!==null?{oldestSyncedAt:new Date(R).toISOString()}:{},isStale:Q,pausedGroupCount:y.length-v.length,stalenessThresholdSec:d.stalenessThresholdSec,erroredGroupIds:y.filter(M=>!!M.syncError).map(M=>Number(M.groupId)).sort((M,se)=>M-se),pendingRequestCount:Number(b?.n??0)}},async loadTenantMembershipPolicy(i){(0,P.assertTenantId)(i,"loadTenantMembershipPolicy(tenantId)");let y=S>0?w.get(i):void 0;if(y&&Date.now()-y.loadedAt<S)return y.policy;await s();let b=await l.select({groupId:o.groupId,isBreakGlass:o.isBreakGlass,externalLinkId:a.id}).from(o).innerJoin(u,(0,c.and)((0,c.eq)(u.id,o.groupId),(0,c.eq)(u.tenantId,i))).leftJoin(a,(0,c.and)((0,c.eq)(a.groupId,o.groupId),(0,c.eq)(a.tenantId,i))).where((0,c.eq)(o.tenantId,i));if(b.length===0){let E={kind:"no-proof-required"};return S>0&&w.set(i,{policy:E,loadedAt:Date.now()}),E}let v=b.map(E=>Number(E.groupId)),k=b.filter(E=>!!E.isBreakGlass);for(let E of k)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 R={kind:"require-proof",proofGroupIds:v,breakGlassGroupIds:k.map(E=>Number(E.groupId))};return(0,ve.assertTenantMembershipPolicy)(R,`tenant ${i}`),S>0&&w.set(i,{policy:R,loadedAt:Date.now()}),R},async setTenantMembershipGroup(i,y,b,v){(0,P.assertTenantId)(i,"setTenantMembershipGroup(tenantId)"),await s(),await f(async k=>{if(await A(k,i,y),b){let[E]=await k.select({id:a.id}).from(a).where((0,c.and)((0,c.eq)(a.tenantId,i),(0,c.eq)(a.groupId,y)));if(E)throw new Error(`Group ${y} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`)}let R=await k.select({groupId:o.groupId}).from(o).where((0,c.and)((0,c.eq)(o.tenantId,i),(0,c.eq)(o.groupId,y)));if(!b&&!(await k.select({groupId:o.groupId,isBreakGlass:o.isBreakGlass}).from(o).where((0,c.eq)(o.tenantId,i))).filter(M=>Number(M.groupId)!==y).some(M=>!!M.isBreakGlass))throw new Error(`Declaring group ${y} as ordinary proof would leave tenant ${i} with a membership proof but no break-glass path; declare a locally-managed break-glass group first.`);if(R.length>0){await k.update(o).set({isBreakGlass:b,updatedAt:new Date,updatedBy:v}).output().where((0,c.and)((0,c.eq)(o.tenantId,i),(0,c.eq)(o.groupId,y)));return}await k.insert(o).output().values({tenantId:i,groupId:y,isBreakGlass:b,createdAt:new Date,createdBy:v})}),h(i)},async removeTenantMembershipGroup(i,y){(0,P.assertTenantId)(i,"removeTenantMembershipGroup(tenantId)"),await s(),await f(async b=>{let k=(await b.select({groupId:o.groupId,isBreakGlass:o.isBreakGlass}).from(o).where((0,c.eq)(o.tenantId,i))).filter(R=>Number(R.groupId)!==y);if(k.length>0&&!k.some(R=>!!R.isBreakGlass))throw new Error(`Removing group ${y} would leave tenant ${i} with a membership proof but no break-glass path.`);await b.delete(o).where((0,c.and)((0,c.eq)(o.tenantId,i),(0,c.eq)(o.groupId,y)))}),h(i)},async setGroupActive(i,y,b,v){(0,P.assertTenantId)(i,"setGroupActive(tenantId)"),await s(),await l.update(u).set({isActive:b,updatedAt:new Date,updatedBy:v}).output().where((0,c.and)((0,c.eq)(u.id,y),(0,c.eq)(u.tenantId,i)))},async listLedgerGroupIds(i,y){if((0,P.assertTenantId)(i,"listLedgerGroupIds(tenantId)"),!d.isEnabled)return[];await s();let b=await H(l,{ledger:t,group:u},{tenantId:i,externalUserId:y.externalUserId?.trim()||null,email:(0,te.normalizeAuthEmail)(y.email)});return[...new Set(b.map(v=>v.groupId))].sort((v,k)=>v-k)},async isEmailAllowedByGroup(i){if(!d.isEnabled)return!1;let y=(0,te.normalizeAuthEmail)(i);if(y==="")return!1;await s();let[b]=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,y));return!!b},async listAllowlistGroups(i){return(0,P.assertTenantId)(i,"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,i))).where((0,c.eq)(r.tenantId,i)).orderBy((0,c.asc)(u.name))).map(b=>({id:Number(b.id),type:"group",groupId:Number(b.groupId),groupKey:String(b.groupKey),groupName:String(b.groupName),...b.description?{description:String(b.description)}:{},owner:{kind:"tenant",tenantId:i},readOnly:!1}))},async addAllowlistGroup(i,y,b,v){(0,P.assertTenantId)(i,"addAllowlistGroup(tenantId)"),await s(),await f(async k=>{let R=await A(k,i,y),[E]=await k.select({id:a.id}).from(a).where((0,c.and)((0,c.eq)(a.groupId,y),(0,c.eq)(a.tenantId,i)));if(!E)throw new K(d.refusals.allowlistNeedsLink(R));await k.insert(r).output().values({tenantId:i,groupId:y,description:b??null,createdAt:new Date,createdBy:v})})},async removeAllowlistGroup(i,y){(0,P.assertTenantId)(i,"removeAllowlistGroup(tenantId)"),await s(),await l.delete(r).where((0,c.and)((0,c.eq)(r.id,y),(0,c.eq)(r.tenantId,i)))},async countProjectedMembers(i,y){(0,P.assertTenantId)(i,"countProjectedMembers(tenantId)"),await s();let[b]=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,i))).where((0,c.eq)(I.groupId,y));return Number(b?.n??0)}}}var Se=require("@aiquants/auth-core"),ne=require("@aiquants/authz-core");async function We(e,n){let m=(0,ne.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,Se.normalizeAuthEmail)(n.email),l=n.externalUserId?.trim()||null;if(l===null&&d==="")return{addedGroupIds:[],matchedLedgerIds:[]};let s=await e.findLedgerMatches({tenantId:m,externalUserId:l,email:d});if(s.length===0)return{addedGroupIds:[],matchedLedgerIds:[]};for(let h of s)(0,ne.assertTenantMatch)(m,h.tenantId,"promoteLedgerForUser(findLedgerMatches row)");let f=[...new Set(s.map(h=>h.ledgerId))].sort((h,a)=>h-a),S=[...new Set(s.map(h=>h.groupId))].sort((h,a)=>h-a);return{addedGroupIds:[...await e.attachUserToGroups({tenantId:m,userId:n.userId,ledgerIds:f,groupIds:S})].sort((h,a)=>h-a),matchedLedgerIds:f}}var pe=require("@aiquants/authz-core"),z=require("@aiquants/mssql"),De=require("@aiquants/mssql/drizzle"),L=require("drizzle-orm");var He=4;function Je(e,n,m,d){let l=e,s=d.ensureConnection??(async()=>{}),f=O(l,d),S=d.now??(()=>new Date),w=n.TDGroupMemberEmail,{TMGroup:h,TDGroupMember:a}=m;return{async findLedgerMatches(t){return(0,pe.assertTenantId)(t.tenantId,"findLedgerMatches(tenantId)"),await s(),H(l,{ledger:w,group:h},t)},async attachUserToGroups({tenantId:t,userId:o,ledgerIds:r,groupIds:g}){return(0,pe.assertTenantId)(t,"attachUserToGroups(tenantId)"),await s(),r.length===0?[]:f(async u=>{let I=new Set;for(let G of(0,z.chunk)(g)){let i=await u.select({id:h.id}).from(h).where((0,L.and)((0,L.inArray)(h.id,G),(0,L.eq)(h.tenantId,t),(0,L.eq)(h.isActive,!0)));for(let y of i)I.add(Number(y.id))}let T=g.filter(G=>I.has(G));if(T.length===0)return[];let A=new Set;for(let G of(0,z.chunk)(T)){let i=await u.select({groupId:a.groupId}).from(a).where((0,L.and)((0,L.eq)(a.userId,o),(0,L.inArray)(a.groupId,G)));for(let y of i)A.add(Number(y.groupId))}let x=T.filter(G=>!A.has(G));for(let G of(0,z.chunk)(r))await u.update(w).set({resolvedUserId:o}).output().where((0,L.and)((0,L.inArray)(w.id,G),(0,De.inJsonList)(w.groupId,T)));if(x.length>0){let G=S();for(let i of(0,z.chunkByParameters)(x,He))await u.insert(a).output().values(i.map(y=>({groupId:y,userId:o,createdAt:G,createdBy:d.actor})))}return x})}}}var q=require("@aiquants/authz-drizzle"),p=require("drizzle-orm/mssql-core"),oe="provider",Ye=["TDGroupExternalLink","TDTenantMembershipGroup","TDAllowlistGroup","TDDirectorySyncRequest","TDDirectoryCatalog","TMDirectoryCatalogObservation","TMAllowlist"];function Ve(e,n){let m=(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=m.table("TDGroupExternalLink",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),tenantId:(0,p.nvarchar)(q.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(oe,{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=m.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]})]),S=m.table("TDTenantMembershipGroup",{tenantId:(0,p.nvarchar)(q.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=m.table("TDAllowlistGroup",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(q.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]})]),h=m.table("TDDirectorySyncRequest",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(q.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=m.table("TMAllowlist",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(q.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=m.table("TDDirectoryCatalog",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(q.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(oe,{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)]),o=m.table("TMDirectoryCatalogObservation",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(q.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(oe,{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:S,TDAllowlistGroup:w,TDDirectorySyncRequest:h,TDDirectoryCatalog:t,TMDirectoryCatalogObservation:o,TMAllowlist:a}}var ae=require("@aiquants/auth-directory-core");function J(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 Te=e=>`${e.length} \u4EF6`,me={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"},ye={upstreamUnreadable:e=>`\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${me[e]})\u3002`,localWriteFailed:e=>`\u30ED\u30FC\u30AB\u30EB\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${me[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,m)=>`\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${m} \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 ${Te(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",m=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: ${Te(m)} (\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=>me[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 ie=["aborted","missing","vanished","upstream-failed","local-failed","foreign-provider","not-converged"],ge=[...ie,"already-vanished"];function Qe(e){let{directory:n,repository:m,policy:d,now:l,logger:s}=e,f=e.messages??ye;if((0,ae.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 S(t,o){try{await m.recordFailure(t,o)}catch(r){s.error(`[auth-directory] could not record the failure for ${t.externalKey}:`,r)}}async function w(t){try{let o=await n.getGroup(t.externalId);return o===null?{kind:"missing"}:{kind:"ok",group:o,members:await n.listGroupMembers(o,t.membershipMode)}}catch(o){return{kind:"failed",error:o instanceof Error?o:new Error(String(o))}}}async function h(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 o=await m.recordVanishedObservation(t,f.groupMissing(t.externalKey,t.vanishedObservations+1,e.vanishedThreshold));return o<e.vanishedThreshold?(s.warn(`[auth-directory] ${t.externalKey} missing upstream (${o}/${e.vanishedThreshold})`),{kind:"missing",link:t,observations:o}):(await m.markGroupInactive(t),await S(t,f.groupVanished(t.externalKey,o)),s.warn(`[auth-directory] ${t.externalKey} deactivated after ${o} consecutive absences`),{kind:"vanished",link:t})}async function a(t){if(t.provider!==e.provider)return await S(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 o=await w(t);if(o.kind==="missing")return h(t);if(o.kind==="failed")return await S(t,f.upstreamUnreadable(J(o.error))),s.error(`[auth-directory] upstream read failed for ${t.externalKey}:`,o.error),{kind:"upstream-failed",link:t,error:o.error};try{await m.resolveExternalIdentity(t,o.group);let[r,g]=await Promise.all([m.loadGroupSnapshot(t),m.resolveMembers(o.members)]);if(g.length!==o.members.length){let x=f.resolverIncomplete(o.members.length,g.length);return await S(t,x),s.error(`[auth-directory] ${x}`),{kind:"local-failed",link:t,error:new Error(x)}}let u=(0,ae.reconcile)(r,g,d);if(u.aborted){await S(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,T=l(),A={refusals:[],failures:[]};return I&&(A=await m.applyPlan(t,u,T)),await m.recordSuccess(t,o.group,T),A.refusals.length>0&&(await S(t,f.removalsRefused(A.refusals)),s.warn(`[auth-directory] ${A.refusals.length} removal(s) refused for ${t.externalKey}: users ${A.refusals.map(x=>x.userId).join(", ")}`)),A.failures.length>0&&(await S(t,f.removalWritesFailed(A.failures)),s.warn(`[auth-directory] ${A.failures.length} removal(s) could not be written for ${t.externalKey}: users ${A.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`),A.refusals.length>0||A.failures.length>0?{kind:"not-converged",link:t,plan:u,refusals:A.refusals,failures:A.failures}:I?{kind:"applied",link:t,plan:u}:{kind:"unchanged",link:t}}catch(r){let g=r instanceof Error?r:new Error(String(r));return await S(t,f.localWriteFailed("localWriteError")),s.error(`[auth-directory] local write failed for ${t.externalKey}:`,g),{kind:"local-failed",link:t,error:g}}}return{syncOne:a,async run(t){let o=await m.listSyncableLinks(t),r=[];for(let I of o)try{r.push(await a(I))}catch(T){let A=T instanceof Error?T:new Error(String(T));s.error(`[auth-directory] ${I.externalKey} ended abnormally:`,A),r.push({kind:"local-failed",link:I,error:A})}let g=r.filter(I=>ge.includes(I.kind)).length,u=r.filter(I=>ie.includes(I.kind)).length;return s.info(`[auth-directory] run finished: ${r.length} link(s), ${g} unhealthy (${u} new)`),{outcomes:r,unhealthyCount:g,newlyUnhealthyCount:u}}}}var Ae=require("@aiquants/authz-core"),xe=require("@aiquants/mssql"),C=require("drizzle-orm"),ke="The job that claimed this request ended without recording an outcome; whether the synchronization ran is unknown.",he=2e3;function Ze(e,n,m){let d=e,l=m.ensureConnection??(async()=>{}),s=n.TDDirectorySyncRequest,f=(0,Ae.assertTenantId)(m.tenantId,"createSyncRequestQueue(tenantId)"),S=m.maxErrorLength??he;if(!Number.isInteger(S)||S<1||S>he)throw new TypeError(`[@aiquants/auth-directory-drizzle] maxErrorLength must be an integer within 1..${he}, received ${String(m.maxErrorLength)}`);let w={async claim(){await l();let h=await d.select({id:s.id}).from(s).where((0,C.and)((0,C.eq)(s.tenantId,f),(0,C.isNull)(s.pickedAt))).orderBy((0,C.asc)(s.id));for(let a of h){let o=(await d.update(s).set({pickedAt:m.now()}).output().where((0,C.and)((0,C.eq)(s.tenantId,f),(0,C.eq)(s.id,Number(a.id)),(0,C.isNull)(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 r=o.groupId??null;return{id:Number(o.id),tenantId:f,groupId:r===null?null:Number(r),requestedBy:String(o.requestedBy)}}return null},async finish(h,a){await l();let t=a.error?a.error.slice(0,S):null;return!!(await d.update(s).set({finishedAt:m.now(),result:a.result,error:t}).output().where((0,C.and)((0,C.eq)(s.tenantId,f),(0,C.eq)(s.id,h),(0,C.isNull)(s.finishedAt))))?.length},async listStranded(h){await l();let a=await d.select({id:s.id,pickedAt:s.pickedAt,requestedAt:s.requestedAt}).from(s).where((0,C.and)((0,C.eq)(s.tenantId,f),(0,C.isNull)(s.finishedAt))).orderBy((0,C.asc)(s.id)),t=o=>{let r=(0,xe.toInstant)(o);return Number.isNaN(r.getTime())||r<h};return{stranded:a.filter(o=>o.pickedAt!==null&&t(o.pickedAt)).map(o=>Number(o.id)),unclaimed:a.filter(o=>o.pickedAt===null&&t(o.requestedAt)).map(o=>Number(o.id))}},async abandonStranded(h){let{stranded:a}=await w.listStranded(h),t=[];for(let o of a)try{await w.finish(o,{result:"failed",error:ke})&&t.push(o)}catch(r){m.onCloseFailed?.(o,r)}return t}};return w}var Y=require("@aiquants/auth-core"),V=require("@aiquants/authz-core"),F=require("@aiquants/mssql"),Ge=require("@aiquants/mssql/drizzle"),D=require("drizzle-orm");var Xe=7,er=4,Re=1e3;function rr(e){if(!(e instanceof Error))return!1;let n=e;return n.number===void 0&&n.code===void 0&&e.name==="Error"}function tr(e,n,m,d){let l=e,s=d.ensureConnection??(async()=>{}),f=O(l,d),S=d.now??(()=>new Date),{TDGroupExternalLink:w,TDGroupMemberEmail:h}=n,{TMGroup:a,TDGroupMember:t}=m,o=d.actor;return{async listSyncableLinks(r){await s();let g=(0,V.assertTenantId)(r.tenantId,"listSyncableLinks(tenantId)"),u=[(0,D.eq)(w.isSyncEnabled,!0),(0,D.eq)(w.tenantId,g)];return r.groupId!==void 0&&u.push((0,D.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,D.and)((0,D.eq)(a.id,w.groupId),(0,D.eq)(a.tenantId,g))).where((0,D.and)(...u))).map(T=>((0,V.assertTenantMatch)(String(T.groupTenantId),String(T.linkTenantId),"listSyncableLinks(link.tenantId vs TMGroup.tenant_id)"),{linkId:Number(T.linkId),groupId:Number(T.groupId),groupKey:String(T.groupKey),tenantId:String(T.groupTenantId),provider:String(T.provider),externalId:String(T.externalId),externalKey:String(T.externalKey),membershipMode:B(String(T.membershipMode)),isGroupActive:!!T.isGroupActive,vanishedObservations:Number(T.vanishedObservations??0)}))},async loadGroupSnapshot(r){await s();let[g,u]=await Promise.all([l.select({email:h.email,externalUserId:h.externalUserId,resolvedUserId:h.resolvedUserId}).from(h).where((0,D.eq)(h.groupId,r.groupId)),l.select({userId:t.userId}).from(t).where((0,D.eq)(t.groupId,r.groupId))]);return{ledger:g.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,g,u){(0,V.assertTenantId)(r.tenantId,"applyPlan(link.tenantId)"),await s(),(g.ledgerUpserts.length>0||g.projectionAdds.length>0)&&await f(async x=>{let G=g.ledgerUpserts.map(b=>({...b,email:(0,Y.normalizeAuthEmail)(b.email)})),i=new Map;for(let b of(0,F.chunk)(G.map(v=>v.email))){let v=await x.select({id:h.id,email:h.email}).from(h).where((0,D.and)((0,D.eq)(h.groupId,r.groupId),(0,D.inArray)(h.email,b)));for(let k of v)i.set(String(k.email),Number(k.id))}let y=G.filter(b=>!i.has(b.email));for(let b of(0,F.chunkByParameters)(y,Xe))await x.insert(h).output().values(b.map(v=>({groupId:r.groupId,email:v.email,externalUserId:v.externalUserId??null,resolvedUserId:v.resolvedUserId,observedAt:u,createdAt:u,createdBy:o})));for(let b of G){let v=i.get(b.email);v!==void 0&&await x.update(h).set({externalUserId:b.externalUserId??null,resolvedUserId:b.resolvedUserId,observedAt:u,updatedAt:u,updatedBy:o}).output().where((0,D.eq)(h.id,v))}for(let b of(0,F.chunkByParameters)(g.projectionAdds,er)){let v=new Set((await x.select({userId:t.userId}).from(t).where((0,D.and)((0,D.eq)(t.groupId,r.groupId),(0,D.inArray)(t.userId,b)))).map(R=>Number(R.userId))),k=b.filter(R=>!v.has(R));k.length>0&&await x.insert(t).output().values(k.map(R=>({groupId:r.groupId,userId:R,createdAt:u,createdBy:o})))}});let I=[],T=[];for(let x of g.projectionRemoves)try{await d.lockoutGuard.guardedWrite(r.tenantId,{removeGroupMembership:{groupId:r.groupId,userId:x}},"remove-group-member",G=>G.delete(t).where((0,D.and)((0,D.eq)(t.groupId,r.groupId),(0,D.eq)(t.userId,x))))}catch(G){rr(G)?I.push({userId:x,applied:!1,refusal:G.message}):T.push({userId:x,cause:J(G)})}let A=I.map(x=>x.userId);return g.ledgerDeletes.length>0&&await f(async x=>{for(let G of(0,F.chunk)(g.ledgerDeletes)){let i=A.length>0?(0,D.or)((0,D.isNull)(h.resolvedUserId),(0,Ge.notInJsonList)(h.resolvedUserId,A)):void 0;await x.delete(h).where((0,D.and)((0,D.eq)(h.groupId,r.groupId),(0,D.inArray)(h.email,G),...i?[i]:[]))}}),{refusals:I,failures:T}},async resolveExternalIdentity(r,g){await s(),!(r.externalId===g.externalId&&r.externalKey===(0,Y.normalizeAuthEmail)(g.externalKey))&&await l.update(w).set({externalId:g.externalId,externalKey:(0,Y.normalizeAuthEmail)(g.externalKey)}).output().where((0,D.and)((0,D.eq)(w.id,r.linkId),(0,D.eq)(w.tenantId,r.tenantId)))},async recordSuccess(r,g,u){await s(),await f(async I=>{await I.update(w).set({syncedAt:u,syncError:null,vanishedObservations:0,externalId:g.externalId,externalKey:(0,Y.normalizeAuthEmail)(g.externalKey),updatedAt:u,updatedBy:o}).output().where((0,D.and)((0,D.eq)(w.id,r.linkId),(0,D.eq)(w.tenantId,r.tenantId))),r.isGroupActive||await I.update(a).set({isActive:!0,updatedAt:u,updatedBy:o}).output().where((0,D.and)((0,D.eq)(a.id,r.groupId),(0,D.eq)(a.tenantId,r.tenantId)))})},async recordFailure(r,g){await s(),await l.update(w).set({syncError:g.slice(0,Re),updatedAt:S(),updatedBy:o}).output().where((0,D.and)((0,D.eq)(w.id,r.linkId),(0,D.eq)(w.tenantId,r.tenantId)))},async recordVanishedObservation(r,g){await s();let u=r.vanishedObservations+1;return await l.update(w).set({vanishedObservations:u,syncError:g.slice(0,Re),updatedAt:S(),updatedBy:o}).output().where((0,D.and)((0,D.eq)(w.id,r.linkId),(0,D.eq)(w.tenantId,r.tenantId))),u},async markGroupInactive(r){await s(),await l.update(a).set({isActive:!1,updatedAt:S(),updatedBy:o}).output().where((0,D.and)((0,D.eq)(a.id,r.groupId),(0,D.eq)(a.tenantId,r.tenantId),(0,D.eq)(a.isActive,!0)))}}}0&&(module.exports={AUTH_DIRECTORY_PROVIDER_COLUMN,AUTH_DIRECTORY_TENANT_SCOPED_TABLES,DirectoryOperatorError,MEMBERSHIP_MODES,NEWLY_UNHEALTHY_SYNC_OUTCOMES,STRANDED_REASON,UNHEALTHY_SYNC_OUTCOMES,assertGroupMayBeLinked,assertMembershipMode,classifySyncFailure,createDirectoryCatalogReader,createDirectoryCatalogWriter,createDirectoryLinkStore,createDirectorySync,createDirectorySyncRepository,createPromotionRepository,createSyncRequestQueue,defaultDirectoryLinkRefusals,defaultExternalGroupGuardMessage,defaultSyncMessages,defineAuthDirectorySchema,findLedgerRowsForIdentity,promoteLedgerForUser,transactionRunner,withExternalGroupGuards});
|
|
1
|
+
"use strict";var me=Object.defineProperty;var Oe=Object.getOwnPropertyDescriptor;var Ue=Object.getOwnPropertyNames;var $e=Object.prototype.hasOwnProperty;var Ne=(e,t)=>{for(var c in t)me(e,c,{get:t[c],enumerable:!0})},qe=(e,t,c,d)=>{if(t&&typeof t=="object"||typeof t=="function")for(let u of Ue(t))!$e.call(e,u)&&u!==c&&me(e,u,{get:()=>t[u],enumerable:!(d=Oe(t,u))||d.enumerable});return e};var ze=e=>qe(me({},"__esModule",{value:!0}),e);var pr={};Ne(pr,{AUTH_DIRECTORY_PROVIDER_COLUMN:()=>de,AUTH_DIRECTORY_TENANT_SCOPED_TABLES:()=>nr,DirectoryOperatorError:()=>K,MEMBERSHIP_MODES:()=>ge,NEWLY_UNHEALTHY_SYNC_OUTCOMES:()=>le,STRANDED_REASON:()=>_e,UNHEALTHY_SYNC_OUTCOMES:()=>Se,assertGroupMayBeLinked:()=>Je,assertMembershipMode:()=>W,classifySyncFailure:()=>Z,createDirectoryCatalogReader:()=>We,createDirectoryCatalogWriter:()=>je,createDirectoryLinkStore:()=>Ze,createDirectorySync:()=>ar,createDirectorySyncRepository:()=>cr,createOpenidMemberResolver:()=>Xe,createPromotionRepository:()=>tr,createSyncRequestQueue:()=>sr,defaultDirectoryLinkRefusals:()=>Ye,defaultExternalGroupGuardMessage:()=>He,defaultSyncMessages:()=>ve,defineAuthDirectorySchema:()=>or,findLedgerRowsForIdentity:()=>Q,promoteLedgerForUser:()=>er,transactionRunner:()=>U,withExternalGroupGuards:()=>Ve});module.exports=ze(pr);var ye=require("@aiquants/authz-core"),te=require("@aiquants/mssql"),_=require("drizzle-orm");var Ae=require("@aiquants/mssql");function U(e,t){return c=>(0,Ae.runWithTransactionRetry)(d=>e.transaction(u=>d(()=>c(u))),t.transactionRetry)}var Be=10,$={tenantId:64,provider:32,externalId:256,externalKey:320,name:256,description:1e3,actor:50},Te=2147483647;function Fe(e){if(e===void 0||!Number.isFinite(e))return null;let t=Math.trunc(e);return t<0?null:t>Te?Te:t}function J(e,t,c){let d=e.trim();if(d==="")throw new TypeError(`[@aiquants/auth-directory-drizzle] ${c} must not be blank`);if(d.length>t)throw new TypeError(`[@aiquants/auth-directory-drizzle] ${c} exceeds its column length (${t}); truncating it would change which rows it owns`);return d}function je(e,t,c){let d=e,u=c.ensureConnection??(async()=>{}),a=U(d,c),y=t.TDDirectoryCatalog,D=t.TMDirectoryCatalogObservation,I=f=>{let s=new Map;for(let n of f)s.set(n.externalId,n);return[...s.values()]};return{async replace(f,s,n){await u();let i=J((0,ye.assertTenantId)(f,"replace(tenantId)"),$.tenantId,"tenantId"),r=J(s,$.provider,"provider"),b=J(c.actor,$.actor,"actor"),l=c.now(),S=I(n.map(w=>({tenantId:i,provider:r,externalId:w.externalId.slice(0,$.externalId),externalKey:w.externalKey.slice(0,$.externalKey),name:w.name.slice(0,$.name),description:w.description===void 0?null:w.description.slice(0,$.description),memberCount:Fe(w.memberCount),observedAt:l,createdAt:l,createdBy:b})));return await a(async w=>{await w.delete(y).where((0,_.and)((0,_.eq)(y.tenantId,i),(0,_.eq)(y.provider,r)));for(let x of(0,te.chunkByParameters)(S,Be))await w.insert(y).values(x);await w.delete(D).where((0,_.and)((0,_.eq)(D.tenantId,i),(0,_.eq)(D.provider,r))),await w.insert(D).values([{tenantId:i,provider:r,observedAt:l,entryCount:S.length,createdAt:l,createdBy:b,updatedAt:l,updatedBy:b}])}),S.length}}}function We(e,t,c={}){let d=e,u=c.ensureConnection??(async()=>{}),a=t.TDDirectoryCatalog,y=t.TMDirectoryCatalogObservation;return{async read(D,I){await u();let f=J((0,ye.assertTenantId)(D,"read(tenantId)"),$.tenantId,"tenantId"),s=J(I,$.provider,"provider"),n=await d.select({observedAt:y.observedAt,entryCount:y.entryCount,externalId:a.externalId,externalKey:a.externalKey,name:a.name,description:a.description,memberCount:a.memberCount}).from(y).leftJoin(a,(0,_.and)((0,_.eq)(a.tenantId,y.tenantId),(0,_.eq)(a.provider,y.provider))).where((0,_.and)((0,_.eq)(y.tenantId,f),(0,_.eq)(y.provider,s))).orderBy((0,_.asc)(a.name)),i=n.filter(l=>l.externalId!==null).map(l=>({externalId:String(l.externalId),externalKey:String(l.externalKey),name:String(l.name),...l.description===null?{}:{description:l.description},...l.memberCount===null?{}:{memberCount:Number(l.memberCount)}})),r=n[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 (${f}, ${s}): the observation records ${r.entryCount} entries but ${i.length} rows are present`);let b=(0,te.toInstant)(r.observedAt);if(!Number.isFinite(b.getTime()))throw new Error(`[@aiquants/auth-directory-drizzle] catalog observation for (${f}, ${s}) has an unreadable observed_at`);return{groups:i,observedAt:b}}}}var ke=require("@aiquants/authz-core");var K=class extends Error{constructor(c){super(c);this.operatorFacing=!0;this.name="DirectoryOperatorError"}};var He=(e,t)=>`\u5916\u90E8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u9023\u643A\u30B0\u30EB\u30FC\u30D7 (${t}) \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 j(e,t,c,d){return new Set(e.filter(u=>t.has(u.roleId)&&u.appKey===c&&u.resourceKey===d).map(u=>u.action))}function ne(e,t){return new Set(e.filter(c=>c.groupId===t).map(c=>c.roleId))}var Y=e=>ke.AUTHZ_ACTIONS.every(t=>e.has(t)),ie=(e,t)=>e.find(c=>c.roleId===t)?.roleKey??`#${t}`;function oe(e,t,c,d){let u=[...t].find(y=>Y(j(e,new Set([y]),c,d)));return u!==void 0?ie(e,u):[...t].filter(y=>j(e,new Set([y]),c,d).size>0).map(y=>ie(e,y)).join(" + ")}function Ve(e,t){return xe(e,t)}function xe(e,t){let c=e;return{...e,withTransaction:(d,u)=>c.withTransaction((a,y)=>d(xe(a,t),y),u),async assignGroupRole(d,u,a,y){await c.withTransaction(async(D,I)=>{let f=await D.listGrants(d),s=ne(await D.listGroupRoles(d),u);if(s.add(a),Y(j(f,s,t.adminAppKey,t.adminResourceKey))){let n=await t.linkLookup.externalGroupKey(d,u,I);if(n!==null)throw new K(t.message(oe(f,s,t.adminAppKey,t.adminResourceKey),n))}await D.assignGroupRole(d,u,a,y)})},async updateGrant(d,u,a,y){let D=a.action;if(D===void 0)return c.updateGrant(d,u,a,y);await c.withTransaction(async(I,f)=>{let s=await I.listGrants(d),n=s.find(b=>b.id===u);if(n===void 0)return I.updateGrant(d,u,a,y);let r=(await I.listResources(d)).find(b=>b.id===n.resourceId);if(r?.appKey===t.adminAppKey&&r?.resourceKey===t.adminResourceKey){let b=await I.listGroupRoles(d);for(let l of new Set(b.filter(S=>S.roleId===n.roleId).map(S=>S.groupId))){let S=ne(b,l),w=j(s.filter(k=>k.id!==u),S,t.adminAppKey,t.adminResourceKey);if(S.has(n.roleId)&&w.add(D),!Y(w))continue;let x=await t.linkLookup.externalGroupKey(d,l,f);if(x!==null)throw new K(t.message(oe(s,S,t.adminAppKey,t.adminResourceKey)||ie(s,n.roleId),x))}}await I.updateGrant(d,u,a,y)})},async createGrant(d,u,a){await c.withTransaction(async(y,D)=>{let f=(await y.listResources(d)).find(s=>s.id===u.resourceId);if(f?.appKey===t.adminAppKey&&f?.resourceKey===t.adminResourceKey){let s=await y.listGrants(d),n=await y.listGroupRoles(d);for(let i of new Set(n.filter(r=>r.roleId===u.roleId).map(r=>r.groupId))){let r=j(s,ne(n,i),t.adminAppKey,t.adminResourceKey);if(r.add(u.action),!Y(r))continue;let b=await t.linkLookup.externalGroupKey(d,i,D);if(b!==null)throw new K(t.message(oe(s,ne(n,i),t.adminAppKey,t.adminResourceKey)||ie(s,u.roleId),b))}}await y.createGrant(d,u,a)})}}}async function Je(e,t,c,d){let u=(await e.listGroupRoles(t)).filter(I=>I.groupId===c);if(u.length===0)return;let a=await e.listGrants(t),y=new Set(u.map(I=>I.roleId));if(!Y(j(a,y,d.adminAppKey,d.adminResourceKey)))return;let D=u[0];if(D)throw new K(d.message(oe(a,y,d.adminAppKey,d.adminResourceKey),D.groupKey))}var z=require("drizzle-orm");async function Q(e,t,c){let{ledger:d,group:u}=t,{tenantId:a,externalUserId:y,email:D}=c;if(y===null&&D==="")return[];let I={ledgerId:d.id,groupId:d.groupId,tenantId:u.tenantId,externalUserId:d.externalUserId},f=async b=>(await e.select(I).from(d).innerJoin(u,(0,z.and)((0,z.eq)(u.id,d.groupId),(0,z.eq)(u.tenantId,a),(0,z.eq)(u.isActive,!0))).where(b)).map(S=>({ledgerId:Number(S.ledgerId),groupId:Number(S.groupId),tenantId:String(S.tenantId),externalUserId:S.externalUserId===null?null:String(S.externalUserId)})),s=b=>{let l=new Map;for(let S of b)l.set(S.ledgerId,{ledgerId:S.ledgerId,groupId:S.groupId,tenantId:S.tenantId});return[...l.values()].sort((S,w)=>S.ledgerId-w.ledgerId)},n=y===null?[]:await f((0,z.eq)(d.externalUserId,y));if(D==="")return s(n);let i=await f((0,z.eq)(d.email,D)),r=b=>b.externalUserId!==null&&b.externalUserId!==y;return s([...n,...i.filter(b=>!r(b))])}var N=require("@aiquants/auth-core"),Re=require("@aiquants/auth-directory-core"),C=require("@aiquants/authz-core"),he=require("@aiquants/mssql"),m=require("drizzle-orm");var ge=["direct","transitive"];function W(e){if(!ge.includes(e))throw new TypeError(`[@aiquants/auth-directory-drizzle] membershipMode must be one of ${ge.join(" | ")}, received ${JSON.stringify(e)}`);return e}var Ye={breakGlassLink:e=>`\u30B0\u30EB\u30FC\u30D7 (${e}) \u306F\u7DCA\u6025\u7528 (break-glass) \u306E\u30C6\u30CA\u30F3\u30C8\u5E30\u5C5E\u306E\u7D4C\u8DEF\u306B\u6307\u5B9A\u3055\u308C\u3066\u3044\u308B\u305F\u3081\u3001\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3068\u9023\u643A\u3067\u304D\u307E\u305B\u3093\u3002\u3053\u306E\u7D4C\u8DEF\u306F\u3001\u540C\u671F\u304C\u89E6\u308C\u306A\u3044\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u30B0\u30EB\u30FC\u30D7\u306B\u9650\u3063\u3066\u3044\u307E\u3059\u3002`,allowlistNeedsLink:e=>`\u30B0\u30EB\u30FC\u30D7 (${e}) \u306F\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u305F\u3081\u3001\u30B5\u30A4\u30F3\u30A4\u30F3\u8A31\u53EF\u306B\u4F7F\u3048\u307E\u305B\u3093\u3002\u30B0\u30EB\u30FC\u30D7\u5358\u4F4D\u306E\u30B5\u30A4\u30F3\u30A4\u30F3\u8A31\u53EF\u306F\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u30E1\u30F3\u30D0\u30FC\u53F0\u5E33\u3067\u5224\u5B9A\u3059\u308B\u306E\u3067\u3001\u4E0A\u6D41\u3068\u9023\u643A\u3057\u305F\u30B0\u30EB\u30FC\u30D7\u3060\u3051\u3092\u6307\u5B9A\u3067\u304D\u307E\u3059\u3002`};function Qe(e){let t=typeof e=="object"&&e!==null&&"number"in e?e.number:void 0;return t===2601||t===2627}function Ze(e,t,c,d){let u=e,a=d.ensureConnection??(async()=>{}),y=U(u,d),D=d.policyCacheTtlMs??0;if(!Number.isFinite(D)||D<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] policyCacheTtlMs must be a non-negative number, received ${String(d.policyCacheTtlMs)}`);let I=new Map,f=o=>I.delete(o),{TDGroupExternalLink:s,TDGroupMemberEmail:n,TDTenantMembershipGroup:i,TDAllowlistGroup:r,TDDirectorySyncRequest:b}=t,{TMGroup:l,TDGroupMember:S}=c;async function w(o,g,h,v){if((await o.select({groupId:i.groupId}).from(i).where((0,m.and)((0,m.eq)(i.tenantId,g),(0,m.eq)(i.groupId,h),(0,m.eq)(i.isBreakGlass,!0)))).length>0)throw new K(d.refusals.breakGlassLink(v))}async function x(o,g,h){let[v]=await o.select({groupKey:l.groupKey}).from(l).where((0,m.and)((0,m.eq)(l.id,h),(0,m.eq)(l.tenantId,g)));if(!v)throw new Error(`Group not found in tenant: ${h}`);return String(v.groupKey)}function k(o,g){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:W(String(o.membershipMode)),isSyncEnabled:!!o.isSyncEnabled,...o.syncedAt?{syncedAt:(0,he.toInstant)(o.syncedAt).toISOString()}:{},...o.syncError?{syncError:String(o.syncError)}:{},unresolved:g}}async function A(o){let g=await u.select({groupId:n.groupId,email:n.email,externalUserId:n.externalUserId}).from(n).innerJoin(l,(0,m.and)((0,m.eq)(l.id,n.groupId),(0,m.eq)(l.tenantId,o))).where((0,m.isNull)(n.resolvedUserId));if(g.length===0)return new Map;let h=g.map(M=>({email:String(M.email),...M.externalUserId?{externalUserId:String(M.externalUserId)}:{}})),v=await d.memberResolver.resolve(h);if(v.length!==h.length)throw new Error(`[@aiquants/auth-directory-drizzle] memberResolver returned ${v.length} member(s) for ${h.length}`);let R=new Map;return g.forEach((M,E)=>{let O=v[E];if((0,N.normalizeAuthEmail)(O.email)!==(0,N.normalizeAuthEmail)(h[E].email))throw new Error(`[@aiquants/auth-directory-drizzle] memberResolver answered ${O.email} at position ${E}, where ${h[E].email} was asked`);let G=O.resolvedUserId===null?O.unresolvedReason:"pendingSync",F=Number(M.groupId),re=R.get(F);re===void 0&&(re={...N.AUTH_DIRECTORY_NO_UNRESOLVED},R.set(F,re)),re[G]+=1}),R}return{async listLinks(o){(0,C.assertTenantId)(o,"listLinks(tenantId)"),await a();let g=await u.select({linkId:s.id,groupId:s.groupId,tenantId:s.tenantId,provider:s.provider,externalId:s.externalId,externalKey:s.externalKey,membershipMode:s.membershipMode,isSyncEnabled:s.isSyncEnabled,syncedAt:s.syncedAt,syncError:s.syncError}).from(s).where((0,m.eq)(s.tenantId,o)).orderBy((0,m.asc)(s.groupId)),h=await A(o);return g.map(v=>((0,C.assertTenantMatch)(o,String(v.tenantId),"listLinks(row.tenantId)"),k(v,h.get(Number(v.groupId))??N.AUTH_DIRECTORY_NO_UNRESOLVED)))},async unresolvedByGroup(o){return(0,C.assertTenantId)(o,"unresolvedByGroup(tenantId)"),await a(),A(o)},async externalGroupKey(o,g,h){(0,C.assertTenantId)(o,"externalGroupKey(tenantId)"),await a();let[v]=await(h??u).select({externalKey:s.externalKey}).from(s).where((0,m.and)((0,m.eq)(s.groupId,g),(0,m.eq)(s.tenantId,o)));return v?String(v.externalKey):null},async linkGroup(o,g,h,v){(0,C.assertTenantId)(o,"linkGroup(tenantId)");let R=W(h.membershipMode),M=h.externalId.trim(),E=(0,N.normalizeAuthEmail)(h.externalKey);if(M===""||E==="")throw new TypeError("[@aiquants/auth-directory-drizzle] externalId and externalKey must be non-empty");await a(),await d.linkable.assertLinkableAndWrite(o,g,async O=>{let G=O,F=await x(G,o,g);await w(G,o,g,F),await G.insert(s).output().values({groupId:g,tenantId:o,provider:h.provider,externalId:M,externalKey:E,membershipMode:R,isSyncEnabled:!0,vanishedObservations:0,createdAt:new Date,createdBy:v})}),f(o)},async unlinkGroup(o,g){(0,C.assertTenantId)(o,"unlinkGroup(tenantId)"),await a(),await y(async h=>{await x(h,o,g),await h.delete(n).where((0,m.eq)(n.groupId,g)),await h.delete(r).where((0,m.and)((0,m.eq)(r.groupId,g),(0,m.eq)(r.tenantId,o))),await h.delete(s).where((0,m.and)((0,m.eq)(s.groupId,g),(0,m.eq)(s.tenantId,o)))}),f(o)},async setSyncEnabled(o,g,h,v){(0,C.assertTenantId)(o,"setSyncEnabled(tenantId)"),await a(),await u.update(s).set({isSyncEnabled:h,updatedAt:new Date,updatedBy:v}).output().where((0,m.and)((0,m.eq)(s.groupId,g),(0,m.eq)(s.tenantId,o)))},async requestSync(o,g,h){return(0,C.assertTenantId)(o,"requestSync(tenantId)"),await a(),u.transaction(async v=>{if(g!==null&&await x(v,o,g),(await v.select({id:b.id}).from(b).where((0,m.and)((0,m.eq)(b.tenantId,o),g===null?(0,m.isNull)(b.groupId):(0,m.eq)(b.groupId,g),(0,m.isNull)(b.pickedAt)))).length>0)return"already-pending";try{await v.insert(b).output().values({tenantId:o,groupId:g,requestedBy:h,requestedAt:new Date,createdAt:new Date,createdBy:h})}catch(M){if(!Qe(M))throw M;return"already-pending"}return"queued"})},async getStatus(o){(0,C.assertTenantId)(o,"getStatus(tenantId)"),await a();let g=await u.select({groupId:s.groupId,isSyncEnabled:s.isSyncEnabled,syncedAt:s.syncedAt,syncError:s.syncError}).from(s).where((0,m.eq)(s.tenantId,o)),[h]=await u.select({n:(0,m.count)()}).from(b).where((0,m.and)((0,m.eq)(b.tenantId,o),(0,m.isNull)(b.finishedAt))),v=g.filter(G=>!!G.isSyncEnabled),R=v.map(G=>G.syncedAt?(0,he.toInstant)(G.syncedAt).getTime():null),M=R.length>0&&R.every(G=>G!==null)?Math.min(...R):null,O=g.length>0&&v.length===0||v.length>0&&(M===null||Date.now()-M>d.stalenessThresholdSec*1e3);return{isEnabled:d.isEnabled,provider:d.provider,linkedGroupCount:g.length,...M!==null?{oldestSyncedAt:new Date(M).toISOString()}:{},isStale:O,pausedGroupCount:g.length-v.length,stalenessThresholdSec:d.stalenessThresholdSec,erroredGroupIds:g.filter(G=>!!G.syncError).map(G=>Number(G.groupId)).sort((G,F)=>G-F),pendingRequestCount:Number(h?.n??0)}},async loadTenantMembershipPolicy(o){(0,C.assertTenantId)(o,"loadTenantMembershipPolicy(tenantId)");let g=D>0?I.get(o):void 0;if(g&&Date.now()-g.loadedAt<D)return g.policy;await a();let h=await u.select({groupId:i.groupId,isBreakGlass:i.isBreakGlass,externalLinkId:s.id}).from(i).innerJoin(l,(0,m.and)((0,m.eq)(l.id,i.groupId),(0,m.eq)(l.tenantId,o))).leftJoin(s,(0,m.and)((0,m.eq)(s.groupId,i.groupId),(0,m.eq)(s.tenantId,o))).where((0,m.eq)(i.tenantId,o));if(h.length===0){let E={kind:"no-proof-required"};return D>0&&I.set(o,{policy:E,loadedAt:Date.now()}),E}let v=h.map(E=>Number(E.groupId)),R=h.filter(E=>!!E.isBreakGlass);for(let E of R)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 M={kind:"require-proof",proofGroupIds:v,breakGlassGroupIds:R.map(E=>Number(E.groupId))};return(0,Re.assertTenantMembershipPolicy)(M,`tenant ${o}`),D>0&&I.set(o,{policy:M,loadedAt:Date.now()}),M},async setTenantMembershipGroup(o,g,h,v){(0,C.assertTenantId)(o,"setTenantMembershipGroup(tenantId)"),await a(),await y(async R=>{if(await x(R,o,g),h){let[E]=await R.select({id:s.id}).from(s).where((0,m.and)((0,m.eq)(s.tenantId,o),(0,m.eq)(s.groupId,g)));if(E)throw new Error(`Group ${g} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`)}let M=await R.select({groupId:i.groupId}).from(i).where((0,m.and)((0,m.eq)(i.tenantId,o),(0,m.eq)(i.groupId,g)));if(!h&&!(await R.select({groupId:i.groupId,isBreakGlass:i.isBreakGlass}).from(i).where((0,m.eq)(i.tenantId,o))).filter(G=>Number(G.groupId)!==g).some(G=>!!G.isBreakGlass))throw new Error(`Declaring group ${g} 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(M.length>0){await R.update(i).set({isBreakGlass:h,updatedAt:new Date,updatedBy:v}).output().where((0,m.and)((0,m.eq)(i.tenantId,o),(0,m.eq)(i.groupId,g)));return}await R.insert(i).output().values({tenantId:o,groupId:g,isBreakGlass:h,createdAt:new Date,createdBy:v})}),f(o)},async removeTenantMembershipGroup(o,g){(0,C.assertTenantId)(o,"removeTenantMembershipGroup(tenantId)"),await a(),await y(async h=>{let R=(await h.select({groupId:i.groupId,isBreakGlass:i.isBreakGlass}).from(i).where((0,m.eq)(i.tenantId,o))).filter(M=>Number(M.groupId)!==g);if(R.length>0&&!R.some(M=>!!M.isBreakGlass))throw new Error(`Removing group ${g} would leave tenant ${o} with a membership proof but no break-glass path.`);await h.delete(i).where((0,m.and)((0,m.eq)(i.tenantId,o),(0,m.eq)(i.groupId,g)))}),f(o)},async setGroupActive(o,g,h,v){(0,C.assertTenantId)(o,"setGroupActive(tenantId)"),await a(),await u.update(l).set({isActive:h,updatedAt:new Date,updatedBy:v}).output().where((0,m.and)((0,m.eq)(l.id,g),(0,m.eq)(l.tenantId,o)))},async listLedgerGroupIds(o,g){if((0,C.assertTenantId)(o,"listLedgerGroupIds(tenantId)"),!d.isEnabled)return[];await a();let h=await Q(u,{ledger:n,group:l},{tenantId:o,externalUserId:g.externalUserId?.trim()||null,email:(0,N.normalizeAuthEmail)(g.email)});return[...new Set(h.map(v=>v.groupId))].sort((v,R)=>v-R)},async isEmailAllowedByGroup(o){if(!d.isEnabled)return!1;let g=(0,N.normalizeAuthEmail)(o);if(g==="")return!1;await a();let[h]=await u.select({id:n.id}).from(n).innerJoin(r,(0,m.eq)(r.groupId,n.groupId)).innerJoin(l,(0,m.and)((0,m.eq)(l.id,n.groupId),(0,m.eq)(l.isActive,!0))).where((0,m.eq)(n.email,g));return!!h},async listAllowlistGroups(o){return(0,C.assertTenantId)(o,"listAllowlistGroups(tenantId)"),await a(),(await u.select({id:r.id,groupId:r.groupId,groupKey:l.groupKey,groupName:l.name,description:r.description}).from(r).innerJoin(l,(0,m.and)((0,m.eq)(l.id,r.groupId),(0,m.eq)(l.tenantId,o))).where((0,m.eq)(r.tenantId,o)).orderBy((0,m.asc)(l.name))).map(h=>({id:Number(h.id),type:"group",groupId:Number(h.groupId),groupKey:String(h.groupKey),groupName:String(h.groupName),...h.description?{description:String(h.description)}:{},owner:{kind:"tenant",tenantId:o},readOnly:!1}))},async addAllowlistGroup(o,g,h,v){(0,C.assertTenantId)(o,"addAllowlistGroup(tenantId)"),await a(),await y(async R=>{let M=await x(R,o,g),[E]=await R.select({id:s.id}).from(s).where((0,m.and)((0,m.eq)(s.groupId,g),(0,m.eq)(s.tenantId,o)));if(!E)throw new K(d.refusals.allowlistNeedsLink(M));await R.insert(r).output().values({tenantId:o,groupId:g,description:h??null,createdAt:new Date,createdBy:v})})},async removeAllowlistGroup(o,g){(0,C.assertTenantId)(o,"removeAllowlistGroup(tenantId)"),await a(),await u.delete(r).where((0,m.and)((0,m.eq)(r.id,g),(0,m.eq)(r.tenantId,o)))},async countProjectedMembers(o,g){(0,C.assertTenantId)(o,"countProjectedMembers(tenantId)"),await a();let[h]=await u.select({n:(0,m.count)()}).from(S).innerJoin(l,(0,m.and)((0,m.eq)(l.id,S.groupId),(0,m.eq)(l.tenantId,o))).where((0,m.eq)(S.groupId,g));return Number(h?.n??0)}}}var ae=require("@aiquants/auth-core"),se=require("@aiquants/mssql"),q=require("drizzle-orm");function be(e,t,c){let d=e.get(t);d===void 0?e.set(t,new Set([c])):d.add(c)}function fe(e){let[t,...c]=e;if(t===void 0||c.length>0)throw new Error(`[@aiquants/auth-directory-drizzle] expected exactly one value, received ${e.size}`);return t}function Xe(e,t,c){let d=e,u=c.ensureConnection??(async()=>{}),{users:a,subjects:y}=t;return{async resolve(D){if(D.length===0)return[];await u();let I=[...new Set(D.map(w=>w.externalUserId?.trim()).filter(w=>!!w))],f=[...new Set(D.map(w=>(0,ae.normalizeAuthEmail)(w.email)).filter(w=>w!==""))],s=new Map,n=new Set;for(let w of(0,se.chunk)(I)){let x=await d.select({subject:y.subject,userId:y.userId,mappingActive:y.isActive,userActive:a.isActive}).from(y.table).innerJoin(a.table,(0,q.eq)(a.id,y.userId)).where((0,q.inArray)(y.subject,w));for(let k of x){let A=String(k.subject);n.add(A),k.mappingActive&&k.userActive&&be(s,A,Number(k.userId))}}let i=new Map,r=new Set;for(let w of(0,se.chunk)(f)){let x=await d.select({id:a.id,email:a.email,isActive:a.isActive}).from(a.table).where((0,q.inArray)(a.email,w));for(let k of x){let A=(0,ae.normalizeAuthEmail)(String(k.email));r.add(A),k.isActive&&be(i,A,Number(k.id))}}let b=[...i.values()].filter(w=>w.size===1).map(fe),l=new Map;for(let w of(0,se.chunk)([...new Set(b)])){let x=await d.select({userId:y.userId,subject:y.subject}).from(y.table).where((0,q.and)((0,q.inArray)(y.userId,w),(0,q.eq)(y.isActive,!0)));for(let k of x)be(l,Number(k.userId),String(k.subject))}let S=w=>{let x=v=>({...w,resolvedUserId:v,unresolvedReason:null}),k=v=>({...w,resolvedUserId:null,unresolvedReason:v}),A=w.externalUserId?.trim(),o=(0,ae.normalizeAuthEmail)(w.email),g=A?s.get(A):void 0;if(g!==void 0)return g.size===1?x(fe(g)):k("ambiguous");let h=i.get(o);if(h!==void 0){if(h.size>1)return k("ambiguous");let v=fe(h),R=l.get(v);return A&&R!==void 0&&!R.has(A)?k("claimedByAnother"):x(v)}return A&&n.has(A)||r.has(o)?k("deactivated"):k("unregistered")};return D.map(S)}}}var Me=require("@aiquants/auth-core"),ue=require("@aiquants/authz-core");async function er(e,t){let c=(0,ue.assertTenantId)(t.tenantId,"promoteLedgerForUser(tenantId)");if(!Number.isInteger(t.userId)||t.userId<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] userId must be a positive integer, received ${String(t.userId)}`);let d=(0,Me.normalizeAuthEmail)(t.email),u=t.externalUserId?.trim()||null;if(u===null&&d==="")return{addedGroupIds:[],matchedLedgerIds:[]};let a=await e.findLedgerMatches({tenantId:c,externalUserId:u,email:d});if(a.length===0)return{addedGroupIds:[],matchedLedgerIds:[]};for(let f of a)(0,ue.assertTenantMatch)(c,f.tenantId,"promoteLedgerForUser(findLedgerMatches row)");let y=[...new Set(a.map(f=>f.ledgerId))].sort((f,s)=>f-s),D=[...new Set(a.map(f=>f.groupId))].sort((f,s)=>f-s);return{addedGroupIds:[...await e.attachUserToGroups({tenantId:c,userId:t.userId,ledgerIds:y,groupIds:D})].sort((f,s)=>f-s),matchedLedgerIds:y}}var we=require("@aiquants/authz-core"),H=require("@aiquants/mssql"),Ee=require("@aiquants/mssql/drizzle"),L=require("drizzle-orm");var rr=4;function tr(e,t,c,d){let u=e,a=d.ensureConnection??(async()=>{}),y=U(u,d),D=d.now??(()=>new Date),I=t.TDGroupMemberEmail,{TMGroup:f,TDGroupMember:s}=c;return{async findLedgerMatches(n){return(0,we.assertTenantId)(n.tenantId,"findLedgerMatches(tenantId)"),await a(),Q(u,{ledger:I,group:f},n)},async attachUserToGroups({tenantId:n,userId:i,ledgerIds:r,groupIds:b}){return(0,we.assertTenantId)(n,"attachUserToGroups(tenantId)"),await a(),r.length===0?[]:y(async l=>{let S=new Set;for(let A of(0,H.chunk)(b)){let o=await l.select({id:f.id}).from(f).where((0,L.and)((0,L.inArray)(f.id,A),(0,L.eq)(f.tenantId,n),(0,L.eq)(f.isActive,!0)));for(let g of o)S.add(Number(g.id))}let w=b.filter(A=>S.has(A));if(w.length===0)return[];let x=new Set;for(let A of(0,H.chunk)(w)){let o=await l.select({groupId:s.groupId}).from(s).where((0,L.and)((0,L.eq)(s.userId,i),(0,L.inArray)(s.groupId,A)));for(let g of o)x.add(Number(g.groupId))}let k=w.filter(A=>!x.has(A));for(let A of(0,H.chunk)(r))await l.update(I).set({resolvedUserId:i}).output().where((0,L.and)((0,L.inArray)(I.id,A),(0,Ee.inJsonList)(I.groupId,w)));if(k.length>0){let A=D();for(let o of(0,H.chunkByParameters)(k,rr))await l.insert(s).output().values(o.map(g=>({groupId:g,userId:i,createdAt:A,createdBy:d.actor})))}return k})}}}var B=require("@aiquants/authz-drizzle"),p=require("drizzle-orm/mssql-core"),de="provider",nr=["TDGroupExternalLink","TDTenantMembershipGroup","TDAllowlistGroup","TDDirectorySyncRequest","TDDirectoryCatalog","TMDirectoryCatalogObservation","TMAllowlist"];function or(e,t){let c=(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})}),u=()=>({createdAt:(0,p.datetime2)("created_at"),createdBy:(0,p.nvarchar)("created_by",{length:50})}),a=c.table("TDGroupExternalLink",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),tenantId:(0,p.nvarchar)(B.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(de,{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:[t.groupTable.id]})]),y=c.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:[t.groupTable.id]})]),D=c.table("TDTenantMembershipGroup",{tenantId:(0,p.nvarchar)(B.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),isBreakGlass:(0,p.bit)("is_break_glass").notNull(),...u()},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:[t.groupTable.id]})]),I=c.table("TDAllowlistGroup",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(B.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),description:(0,p.nvarchar)("description",{length:512}),...u()},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:[t.groupTable.id]})]),f=c.table("TDDirectorySyncRequest",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(B.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}),...u()},r=>[(0,p.primaryKey)({name:`${e}_TDDirectorySyncRequest_pk`,columns:[r.id]})]),s=c.table("TMAllowlist",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(B.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}),...u()},r=>[(0,p.primaryKey)({name:`${e}_TMAllowlist_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TMAllowlist_pattern_uq`).on(r.tenantId,r.pattern)]),n=c.table("TDDirectoryCatalog",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(B.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(de,{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(),...u()},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=c.table("TMDirectoryCatalogObservation",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(B.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(de,{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:a,TDGroupMemberEmail:y,TDTenantMembershipGroup:D,TDAllowlistGroup:I,TDDirectorySyncRequest:f,TDDirectoryCatalog:n,TMDirectoryCatalogObservation:i,TMAllowlist:s}}var ce=require("@aiquants/auth-core"),pe=require("@aiquants/auth-directory-core");function Z(e){if(e instanceof Error&&(e.name==="AbortError"||e.name==="TimeoutError"))return"timeout";let t=typeof e=="object"&&e!==null&&"status"in e?e.status:void 0;return typeof t!="number"?"unknown":t===401||t===403?"denied":t===429||t===408?"rateLimited":t>=500?"upstreamError":"unknown"}var Ge=e=>`${e.length} \u4EF6`,Ie={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"},ve={upstreamUnreadable:e=>`\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${Ie[e]})\u3002`,localWriteFailed:e=>`\u30ED\u30FC\u30AB\u30EB\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${Ie[e]})\u3002`,resolverIncomplete:(e,t)=>`\u4E0A\u6D41\u304C ${e} \u4EF6\u306E\u30E1\u30F3\u30D0\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F\u304C\u3001\u7A81\u5408\u7D50\u679C\u306F ${t} \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,t,c)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (${e})\u3002\u9023\u7D9A ${t} \u56DE\u76EE\u3067\u3059\u3002${c} \u56DE\u306B\u9054\u3057\u305F\u6642\u70B9\u3067\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3059\u3002`,groupVanished:(e,t)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C ${t} \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 ${Ge(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 t=e.axis==="membership"?"\u6A29\u9650\u3092\u5931\u3046\u4EBA\u6570":"\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u306A\u304F\u306A\u308B\u4EBA\u6570",c=e.axis==="membership"?e.withheldProjectionRemoves.map(String):e.withheldLedgerDeletes;return[`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: ${t}\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: ${Ge(c)} (\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(t=>Ie[t.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(t=>t.userId).join(", ")})\u3002\u6B8B\u308A\u306E\u5909\u66F4\u306F\u9069\u7528\u6E08\u307F\u3067\u3059\u3002`,foreignProvider:(e,t)=>`\u3053\u306E\u30EA\u30F3\u30AF\u306E\u4F9B\u7D66\u5143\u306F ${e} \u3067\u3059\u304C\u3001\u5B9F\u884C\u4E2D\u306E\u540C\u671F\u306F ${t} \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 le=["aborted","missing","vanished","upstream-failed","local-failed","foreign-provider","not-converged"],Se=[...le,"already-vanished"];function ir(e){let t=new Map;for(let u of e)u.resolvedUserId===null&&t.set((0,ce.normalizeAuthEmail)(u.email),u.unresolvedReason);let c=[...t.values()],d=ce.AUTH_DIRECTORY_UNRESOLVED_REASONS.map(u=>[u,c.filter(a=>a===u).length]).filter(([,u])=>u>0).map(([u,a])=>`${u}: ${a}`).join(", ");return{total:t.size,breakdown:d}}function ar(e){let{directory:t,repository:c,policy:d,now:u,logger:a}=e,y=e.messages??ve;if((0,pe.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 D(n,i){try{await c.recordFailure(n,i)}catch(r){a.error(`[auth-directory] could not record the failure for ${n.externalKey}:`,r)}}async function I(n){try{let i=await t.getGroup(n.externalId);return i===null?{kind:"missing"}:{kind:"ok",group:i,members:await t.listGroupMembers(i,n.membershipMode)}}catch(i){return{kind:"failed",error:i instanceof Error?i:new Error(String(i))}}}async function f(n){if(n.vanishedObservations>=e.vanishedThreshold&&!n.isGroupActive)return a.warn(`[auth-directory] ${n.externalKey} still missing upstream (already deactivated after ${n.vanishedObservations} absences)`),{kind:"already-vanished",link:n};let i=await c.recordVanishedObservation(n,y.groupMissing(n.externalKey,n.vanishedObservations+1,e.vanishedThreshold));return i<e.vanishedThreshold?(a.warn(`[auth-directory] ${n.externalKey} missing upstream (${i}/${e.vanishedThreshold})`),{kind:"missing",link:n,observations:i}):(await c.markGroupInactive(n),await D(n,y.groupVanished(n.externalKey,i)),a.warn(`[auth-directory] ${n.externalKey} deactivated after ${i} consecutive absences`),{kind:"vanished",link:n})}async function s(n){if(n.provider!==e.provider)return await D(n,y.foreignProvider(n.provider,e.provider)),a.warn(`[auth-directory] skipping ${n.externalKey}: belongs to provider ${n.provider}`),{kind:"foreign-provider",link:n};let i=await I(n);if(i.kind==="missing")return f(n);if(i.kind==="failed")return await D(n,y.upstreamUnreadable(Z(i.error))),a.error(`[auth-directory] upstream read failed for ${n.externalKey}:`,i.error),{kind:"upstream-failed",link:n,error:i.error};try{await c.resolveExternalIdentity(n,i.group);let[r,b]=await Promise.all([c.loadGroupSnapshot(n),c.resolveMembers(i.members)]);if(b.length!==i.members.length){let A=y.resolverIncomplete(i.members.length,b.length);return await D(n,A),a.error(`[auth-directory] ${A}`),{kind:"local-failed",link:n,error:new Error(A)}}let l=(0,pe.reconcile)(r,b,d);if(l.aborted){await D(n,y.planRefused(l.aborted));let A=l.aborted.reason==="invalid-upstream-data"?l.aborted.offendingEmails:l.aborted.axis==="membership"?l.aborted.withheldProjectionRemoves.map(String):l.aborted.withheldLedgerDeletes;return a.warn(`[auth-directory] plan refused for ${n.externalKey}: ${l.aborted.reason}; withheld: ${A.join(", ")}`),{kind:"aborted",link:n,plan:l}}let S=l.ledgerUpserts.length>0||l.ledgerDeletes.length>0||l.projectionAdds.length>0||l.projectionRemoves.length>0,w=u(),x={refusals:[],failures:[]};S&&(x=await c.applyPlan(n,l,w)),await c.recordSuccess(n,i.group,w),x.refusals.length>0&&(await D(n,y.removalsRefused(x.refusals)),a.warn(`[auth-directory] ${x.refusals.length} removal(s) refused for ${n.externalKey}: users ${x.refusals.map(A=>A.userId).join(", ")}`)),x.failures.length>0&&(await D(n,y.removalWritesFailed(x.failures)),a.warn(`[auth-directory] ${x.failures.length} removal(s) could not be written for ${n.externalKey}: users ${x.failures.map(A=>A.userId).join(", ")}`));let k=ir(b);return k.total>0&&a.info(`[auth-directory] ${n.externalKey}: ${k.total} upstream member(s) are not linked to a local user (${k.breakdown})`),x.refusals.length>0||x.failures.length>0?{kind:"not-converged",link:n,plan:l,refusals:x.refusals,failures:x.failures}:S?{kind:"applied",link:n,plan:l}:{kind:"unchanged",link:n}}catch(r){let b=r instanceof Error?r:new Error(String(r));return await D(n,y.localWriteFailed("localWriteError")),a.error(`[auth-directory] local write failed for ${n.externalKey}:`,b),{kind:"local-failed",link:n,error:b}}}return{syncOne:s,async run(n){let i=await c.listSyncableLinks(n),r=[];for(let S of i)try{r.push(await s(S))}catch(w){let x=w instanceof Error?w:new Error(String(w));a.error(`[auth-directory] ${S.externalKey} ended abnormally:`,x),r.push({kind:"local-failed",link:S,error:x})}let b=r.filter(S=>Se.includes(S.kind)).length,l=r.filter(S=>le.includes(S.kind)).length;return a.info(`[auth-directory] run finished: ${r.length} link(s), ${b} unhealthy (${l} new)`),{outcomes:r,unhealthyCount:b,newlyUnhealthyCount:l}}}}var Ce=require("@aiquants/authz-core"),Pe=require("@aiquants/mssql"),P=require("drizzle-orm"),_e="The job that claimed this request ended without recording an outcome; whether the synchronization ran is unknown.",De=2e3;function sr(e,t,c){let d=e,u=c.ensureConnection??(async()=>{}),a=t.TDDirectorySyncRequest,y=(0,Ce.assertTenantId)(c.tenantId,"createSyncRequestQueue(tenantId)"),D=c.maxErrorLength??De;if(!Number.isInteger(D)||D<1||D>De)throw new TypeError(`[@aiquants/auth-directory-drizzle] maxErrorLength must be an integer within 1..${De}, received ${String(c.maxErrorLength)}`);let I={async claim(){await u();let f=await d.select({id:a.id}).from(a).where((0,P.and)((0,P.eq)(a.tenantId,y),(0,P.isNull)(a.pickedAt))).orderBy((0,P.asc)(a.id));for(let s of f){let i=(await d.update(a).set({pickedAt:c.now()}).output().where((0,P.and)((0,P.eq)(a.tenantId,y),(0,P.eq)(a.id,Number(s.id)),(0,P.isNull)(a.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:y,groupId:r===null?null:Number(r),requestedBy:String(i.requestedBy)}}return null},async finish(f,s){await u();let n=s.error?s.error.slice(0,D):null;return!!(await d.update(a).set({finishedAt:c.now(),result:s.result,error:n}).output().where((0,P.and)((0,P.eq)(a.tenantId,y),(0,P.eq)(a.id,f),(0,P.isNull)(a.finishedAt))))?.length},async listStranded(f){await u();let s=await d.select({id:a.id,pickedAt:a.pickedAt,requestedAt:a.requestedAt}).from(a).where((0,P.and)((0,P.eq)(a.tenantId,y),(0,P.isNull)(a.finishedAt))).orderBy((0,P.asc)(a.id)),n=i=>{let r=(0,Pe.toInstant)(i);return Number.isNaN(r.getTime())||r<f};return{stranded:s.filter(i=>i.pickedAt!==null&&n(i.pickedAt)).map(i=>Number(i.id)),unclaimed:s.filter(i=>i.pickedAt===null&&n(i.requestedAt)).map(i=>Number(i.id))}},async abandonStranded(f){let{stranded:s}=await I.listStranded(f),n=[];for(let i of s)try{await I.finish(i,{result:"failed",error:_e})&&n.push(i)}catch(r){c.onCloseFailed?.(i,r)}return n}};return I}var X=require("@aiquants/auth-core"),ee=require("@aiquants/authz-core"),V=require("@aiquants/mssql"),Ke=require("@aiquants/mssql/drizzle"),T=require("drizzle-orm");var ur=7,dr=4,Le=1e3;function lr(e){if(!(e instanceof Error))return!1;let t=e;return t.number===void 0&&t.code===void 0&&e.name==="Error"}function cr(e,t,c,d){let u=e,a=d.ensureConnection??(async()=>{}),y=U(u,d),D=d.now??(()=>new Date),{TDGroupExternalLink:I,TDGroupMemberEmail:f}=t,{TMGroup:s,TDGroupMember:n}=c,i=d.actor;return{async listSyncableLinks(r){await a();let b=(0,ee.assertTenantId)(r.tenantId,"listSyncableLinks(tenantId)"),l=[(0,T.eq)(I.isSyncEnabled,!0),(0,T.eq)(I.tenantId,b)];return r.groupId!==void 0&&l.push((0,T.eq)(I.groupId,r.groupId)),(await u.select({linkId:I.id,groupId:I.groupId,linkTenantId:I.tenantId,groupTenantId:s.tenantId,groupKey:s.groupKey,isGroupActive:s.isActive,provider:I.provider,externalId:I.externalId,externalKey:I.externalKey,membershipMode:I.membershipMode,vanishedObservations:I.vanishedObservations}).from(I).innerJoin(s,(0,T.and)((0,T.eq)(s.id,I.groupId),(0,T.eq)(s.tenantId,b))).where((0,T.and)(...l))).map(w=>((0,ee.assertTenantMatch)(String(w.groupTenantId),String(w.linkTenantId),"listSyncableLinks(link.tenantId vs TMGroup.tenant_id)"),{linkId:Number(w.linkId),groupId:Number(w.groupId),groupKey:String(w.groupKey),tenantId:String(w.groupTenantId),provider:String(w.provider),externalId:String(w.externalId),externalKey:String(w.externalKey),membershipMode:W(String(w.membershipMode)),isGroupActive:!!w.isGroupActive,vanishedObservations:Number(w.vanishedObservations??0)}))},async loadGroupSnapshot(r){await a();let[b,l]=await Promise.all([u.select({email:f.email,externalUserId:f.externalUserId,resolvedUserId:f.resolvedUserId}).from(f).where((0,T.eq)(f.groupId,r.groupId)),u.select({userId:n.userId}).from(n).where((0,T.eq)(n.groupId,r.groupId))]);return{ledger:b.map(S=>({email:String(S.email),...S.externalUserId?{externalUserId:String(S.externalUserId)}:{},resolvedUserId:S.resolvedUserId===null?null:Number(S.resolvedUserId)})),projectedUserIds:l.map(S=>Number(S.userId))}},resolveMembers(r){return d.memberResolver.resolve(r)},async applyPlan(r,b,l){(0,ee.assertTenantId)(r.tenantId,"applyPlan(link.tenantId)"),await a(),(b.ledgerUpserts.length>0||b.projectionAdds.length>0)&&await y(async k=>{let A=b.ledgerUpserts.map(h=>({...h,email:(0,X.normalizeAuthEmail)(h.email)})),o=new Map;for(let h of(0,V.chunk)(A.map(v=>v.email))){let v=await k.select({id:f.id,email:f.email}).from(f).where((0,T.and)((0,T.eq)(f.groupId,r.groupId),(0,T.inArray)(f.email,h)));for(let R of v)o.set(String(R.email),Number(R.id))}let g=A.filter(h=>!o.has(h.email));for(let h of(0,V.chunkByParameters)(g,ur))await k.insert(f).output().values(h.map(v=>({groupId:r.groupId,email:v.email,externalUserId:v.externalUserId??null,resolvedUserId:v.resolvedUserId,observedAt:l,createdAt:l,createdBy:i})));for(let h of A){let v=o.get(h.email);v!==void 0&&await k.update(f).set({externalUserId:h.externalUserId??null,resolvedUserId:h.resolvedUserId,observedAt:l,updatedAt:l,updatedBy:i}).output().where((0,T.eq)(f.id,v))}for(let h of(0,V.chunkByParameters)(b.projectionAdds,dr)){let v=new Set((await k.select({userId:n.userId}).from(n).where((0,T.and)((0,T.eq)(n.groupId,r.groupId),(0,T.inArray)(n.userId,h)))).map(M=>Number(M.userId))),R=h.filter(M=>!v.has(M));R.length>0&&await k.insert(n).output().values(R.map(M=>({groupId:r.groupId,userId:M,createdAt:l,createdBy:i})))}});let S=[],w=[];for(let k of b.projectionRemoves)try{await d.lockoutGuard.guardedWrite(r.tenantId,{removeGroupMembership:{groupId:r.groupId,userId:k}},"remove-group-member",A=>A.delete(n).where((0,T.and)((0,T.eq)(n.groupId,r.groupId),(0,T.eq)(n.userId,k))))}catch(A){lr(A)?S.push({userId:k,applied:!1,refusal:A.message}):w.push({userId:k,cause:Z(A)})}let x=S.map(k=>k.userId);return b.ledgerDeletes.length>0&&await y(async k=>{for(let A of(0,V.chunk)(b.ledgerDeletes)){let o=x.length>0?(0,T.or)((0,T.isNull)(f.resolvedUserId),(0,Ke.notInJsonList)(f.resolvedUserId,x)):void 0;await k.delete(f).where((0,T.and)((0,T.eq)(f.groupId,r.groupId),(0,T.inArray)(f.email,A),...o?[o]:[]))}}),{refusals:S,failures:w}},async resolveExternalIdentity(r,b){await a(),!(r.externalId===b.externalId&&r.externalKey===(0,X.normalizeAuthEmail)(b.externalKey))&&await u.update(I).set({externalId:b.externalId,externalKey:(0,X.normalizeAuthEmail)(b.externalKey)}).output().where((0,T.and)((0,T.eq)(I.id,r.linkId),(0,T.eq)(I.tenantId,r.tenantId)))},async recordSuccess(r,b,l){await a(),await y(async S=>{await S.update(I).set({syncedAt:l,syncError:null,vanishedObservations:0,externalId:b.externalId,externalKey:(0,X.normalizeAuthEmail)(b.externalKey),updatedAt:l,updatedBy:i}).output().where((0,T.and)((0,T.eq)(I.id,r.linkId),(0,T.eq)(I.tenantId,r.tenantId))),r.isGroupActive||await S.update(s).set({isActive:!0,updatedAt:l,updatedBy:i}).output().where((0,T.and)((0,T.eq)(s.id,r.groupId),(0,T.eq)(s.tenantId,r.tenantId)))})},async recordFailure(r,b){await a(),await u.update(I).set({syncError:b.slice(0,Le),updatedAt:D(),updatedBy:i}).output().where((0,T.and)((0,T.eq)(I.id,r.linkId),(0,T.eq)(I.tenantId,r.tenantId)))},async recordVanishedObservation(r,b){await a();let l=r.vanishedObservations+1;return await u.update(I).set({vanishedObservations:l,syncError:b.slice(0,Le),updatedAt:D(),updatedBy:i}).output().where((0,T.and)((0,T.eq)(I.id,r.linkId),(0,T.eq)(I.tenantId,r.tenantId))),l},async markGroupInactive(r){await a(),await u.update(s).set({isActive:!1,updatedAt:D(),updatedBy:i}).output().where((0,T.and)((0,T.eq)(s.id,r.groupId),(0,T.eq)(s.tenantId,r.tenantId),(0,T.eq)(s.isActive,!0)))}}}0&&(module.exports={AUTH_DIRECTORY_PROVIDER_COLUMN,AUTH_DIRECTORY_TENANT_SCOPED_TABLES,DirectoryOperatorError,MEMBERSHIP_MODES,NEWLY_UNHEALTHY_SYNC_OUTCOMES,STRANDED_REASON,UNHEALTHY_SYNC_OUTCOMES,assertGroupMayBeLinked,assertMembershipMode,classifySyncFailure,createDirectoryCatalogReader,createDirectoryCatalogWriter,createDirectoryLinkStore,createDirectorySync,createDirectorySyncRepository,createOpenidMemberResolver,createPromotionRepository,createSyncRequestQueue,defaultDirectoryLinkRefusals,defaultExternalGroupGuardMessage,defaultSyncMessages,defineAuthDirectorySchema,findLedgerRowsForIdentity,promoteLedgerForUser,transactionRunner,withExternalGroupGuards});
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{assertTenantId as Se}from"@aiquants/authz-core";import{chunkByParameters as Ne,toInstant as $e}from"@aiquants/mssql";import{and as Q,asc as qe,eq as q}from"drizzle-orm";import{runWithTransactionRetry as Oe}from"@aiquants/mssql";function $(e,i){return p=>Oe(d=>e.transaction(l=>d(()=>p(l))),i.transactionRetry)}var Ue=10,N={tenantId:64,provider:32,externalId:256,externalKey:320,name:256,description:1e3,actor:50},ve=2147483647;function Be(e){if(e===void 0||!Number.isFinite(e))return null;let i=Math.trunc(e);return i<0?null:i>ve?ve:i}function W(e,i,p){let d=e.trim();if(d==="")throw new TypeError(`[@aiquants/auth-directory-drizzle] ${p} must not be blank`);if(d.length>i)throw new TypeError(`[@aiquants/auth-directory-drizzle] ${p} exceeds its column length (${i}); truncating it would change which rows it owns`);return d}function wr(e,i,p){let d=e,l=p.ensureConnection??(async()=>{}),s=$(d,p),h=i.TDDirectoryCatalog,I=i.TMDirectoryCatalogObservation,b=y=>{let a=new Map;for(let t of y)a.set(t.externalId,t);return[...a.values()]};return{async replace(y,a,t){await l();let n=W(Se(y,"replace(tenantId)"),N.tenantId,"tenantId"),r=W(a,N.provider,"provider"),m=W(p.actor,N.actor,"actor"),u=p.now(),f=b(t.map(S=>({tenantId:n,provider:r,externalId:S.externalId.slice(0,N.externalId),externalKey:S.externalKey.slice(0,N.externalKey),name:S.name.slice(0,N.name),description:S.description===void 0?null:S.description.slice(0,N.description),memberCount:Be(S.memberCount),observedAt:u,createdAt:u,createdBy:m})));return await s(async S=>{await S.delete(h).where(Q(q(h.tenantId,n),q(h.provider,r)));for(let D of Ne(f,Ue))await S.insert(h).values(D);await S.delete(I).where(Q(q(I.tenantId,n),q(I.provider,r))),await S.insert(I).values([{tenantId:n,provider:r,observedAt:u,entryCount:f.length,createdAt:u,createdBy:m,updatedAt:u,updatedBy:m}])}),f.length}}}function Ir(e,i,p={}){let d=e,l=p.ensureConnection??(async()=>{}),s=i.TDDirectoryCatalog,h=i.TMDirectoryCatalogObservation;return{async read(I,b){await l();let y=W(Se(I,"read(tenantId)"),N.tenantId,"tenantId"),a=W(b,N.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,Q(q(s.tenantId,h.tenantId),q(s.provider,h.provider))).where(Q(q(h.tenantId,y),q(h.provider,a))).orderBy(qe(s.name)),n=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:n,observedAt:null};if(Number(r.entryCount)!==n.length)throw new Error(`[@aiquants/auth-directory-drizzle] catalog is inconsistent for (${y}, ${a}): the observation records ${r.entryCount} entries but ${n.length} rows are present`);let m=$e(r.observedAt);if(!Number.isFinite(m.getTime()))throw new Error(`[@aiquants/auth-directory-drizzle] catalog observation for (${y}, ${a}) has an unreadable observed_at`);return{groups:n,observedAt:m}}}}import{AUTHZ_ACTIONS as ze}from"@aiquants/authz-core";var K=class extends Error{constructor(p){super(p);this.operatorFacing=!0;this.name="DirectoryOperatorError"}};var Ar=(e,i)=>`\u5916\u90E8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u9023\u643A\u30B0\u30EB\u30FC\u30D7 (${i}) \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 F(e,i,p,d){return new Set(e.filter(l=>i.has(l.roleId)&&l.appKey===p&&l.resourceKey===d).map(l=>l.action))}function Z(e,i){return new Set(e.filter(p=>p.groupId===i).map(p=>p.roleId))}var H=e=>ze.every(i=>e.has(i)),ee=(e,i)=>e.find(p=>p.roleId===i)?.roleKey??`#${i}`;function X(e,i,p,d){let l=[...i].find(h=>H(F(e,new Set([h]),p,d)));return l!==void 0?ee(e,l):[...i].filter(h=>F(e,new Set([h]),p,d).size>0).map(h=>ee(e,h)).join(" + ")}function xr(e,i){return De(e,i)}function De(e,i){let p=e;return{...e,withTransaction:(d,l)=>p.withTransaction((s,h)=>d(De(s,i),h),l),async assignGroupRole(d,l,s,h){await p.withTransaction(async(I,b)=>{let y=await I.listGrants(d),a=Z(await I.listGroupRoles(d),l);if(a.add(s),H(F(y,a,i.adminAppKey,i.adminResourceKey))){let t=await i.linkLookup.externalGroupKey(d,l,b);if(t!==null)throw new K(i.message(X(y,a,i.adminAppKey,i.adminResourceKey),t))}await I.assignGroupRole(d,l,s,h)})},async updateGrant(d,l,s,h){let I=s.action;if(I===void 0)return p.updateGrant(d,l,s,h);await p.withTransaction(async(b,y)=>{let a=await b.listGrants(d),t=a.find(m=>m.id===l);if(t===void 0)return b.updateGrant(d,l,s,h);let r=(await b.listResources(d)).find(m=>m.id===t.resourceId);if(r?.appKey===i.adminAppKey&&r?.resourceKey===i.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=Z(m,u),S=F(a.filter(T=>T.id!==l),f,i.adminAppKey,i.adminResourceKey);if(f.has(t.roleId)&&S.add(I),!H(S))continue;let D=await i.linkLookup.externalGroupKey(d,u,y);if(D!==null)throw new K(i.message(X(a,f,i.adminAppKey,i.adminResourceKey)||ee(a,t.roleId),D))}}await b.updateGrant(d,l,s,h)})},async createGrant(d,l,s){await p.withTransaction(async(h,I)=>{let y=(await h.listResources(d)).find(a=>a.id===l.resourceId);if(y?.appKey===i.adminAppKey&&y?.resourceKey===i.adminResourceKey){let a=await h.listGrants(d),t=await h.listGroupRoles(d);for(let n of new Set(t.filter(r=>r.roleId===l.roleId).map(r=>r.groupId))){let r=F(a,Z(t,n),i.adminAppKey,i.adminResourceKey);if(r.add(l.action),!H(r))continue;let m=await i.linkLookup.externalGroupKey(d,n,I);if(m!==null)throw new K(i.message(X(a,Z(t,n),i.adminAppKey,i.adminResourceKey)||ee(a,l.roleId),m))}}await h.createGrant(d,l,s)})}}}async function kr(e,i,p,d){let l=(await e.listGroupRoles(i)).filter(b=>b.groupId===p);if(l.length===0)return;let s=await e.listGrants(i),h=new Set(l.map(b=>b.roleId));if(!H(F(s,h,d.adminAppKey,d.adminResourceKey)))return;let I=l[0];if(I)throw new K(d.message(X(s,h,d.adminAppKey,d.adminResourceKey),I.groupKey))}import{and as Fe,eq as J}from"drizzle-orm";async function re(e,i,p){let{ledger:d,group:l}=i,{tenantId:s,externalUserId:h,email:I}=p;if(h===null&&I==="")return[];let b={ledgerId:d.id,groupId:d.groupId,tenantId:l.tenantId,externalUserId:d.externalUserId},y=async m=>(await e.select(b).from(d).innerJoin(l,Fe(J(l.id,d.groupId),J(l.tenantId,s),J(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)})),a=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 y(J(d.externalUserId,h));if(I==="")return a(t);let n=await y(J(d.email,I)),r=m=>m.externalUserId!==null&&m.externalUserId!==h;return a([...t,...n.filter(m=>!r(m))])}import{normalizeAuthEmail as de}from"@aiquants/auth-core";import{assertTenantMembershipPolicy as je}from"@aiquants/auth-directory-core";import{assertTenantId as P,assertTenantMatch as We}from"@aiquants/authz-core";import{toInstant as Ae}from"@aiquants/mssql";import{and as E,asc as xe,count as le,eq as v,isNull as te}from"drizzle-orm";var Te=["direct","transitive"];function Y(e){if(!Te.includes(e))throw new TypeError(`[@aiquants/auth-directory-drizzle] membershipMode must be one of ${Te.join(" | ")}, received ${JSON.stringify(e)}`);return e}var Ur={breakGlassLink:e=>`\u30B0\u30EB\u30FC\u30D7 (${e}) \u306F\u7DCA\u6025\u7528 (break-glass) \u306E\u30C6\u30CA\u30F3\u30C8\u5E30\u5C5E\u306E\u7D4C\u8DEF\u306B\u6307\u5B9A\u3055\u308C\u3066\u3044\u308B\u305F\u3081\u3001\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3068\u9023\u643A\u3067\u304D\u307E\u305B\u3093\u3002\u3053\u306E\u7D4C\u8DEF\u306F\u3001\u540C\u671F\u304C\u89E6\u308C\u306A\u3044\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u30B0\u30EB\u30FC\u30D7\u306B\u9650\u3063\u3066\u3044\u307E\u3059\u3002`,allowlistNeedsLink:e=>`\u30B0\u30EB\u30FC\u30D7 (${e}) \u306F\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u305F\u3081\u3001\u30B5\u30A4\u30F3\u30A4\u30F3\u8A31\u53EF\u306B\u4F7F\u3048\u307E\u305B\u3093\u3002\u30B0\u30EB\u30FC\u30D7\u5358\u4F4D\u306E\u30B5\u30A4\u30F3\u30A4\u30F3\u8A31\u53EF\u306F\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u30E1\u30F3\u30D0\u30FC\u53F0\u5E33\u3067\u5224\u5B9A\u3059\u308B\u306E\u3067\u3001\u4E0A\u6D41\u3068\u9023\u643A\u3057\u305F\u30B0\u30EB\u30FC\u30D7\u3060\u3051\u3092\u6307\u5B9A\u3067\u304D\u307E\u3059\u3002`};function He(e){let i=typeof e=="object"&&e!==null&&"number"in e?e.number:void 0;return i===2601||i===2627}function Br(e,i,p,d){let l=e,s=d.ensureConnection??(async()=>{}),h=$(l,d),I=d.policyCacheTtlMs??0;if(!Number.isFinite(I)||I<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] policyCacheTtlMs must be a non-negative number, received ${String(d.policyCacheTtlMs)}`);let b=new Map,y=o=>b.delete(o),{TDGroupExternalLink:a,TDGroupMemberEmail:t,TDTenantMembershipGroup:n,TDAllowlistGroup:r,TDDirectorySyncRequest:m}=i,{TMGroup:u,TDGroupMember:f}=p;async function S(o,c,g,w){if((await o.select({groupId:n.groupId}).from(n).where(E(v(n.tenantId,c),v(n.groupId,g),v(n.isBreakGlass,!0)))).length>0)throw new K(d.refusals.breakGlassLink(w))}async function D(o,c,g){let[w]=await o.select({groupKey:u.groupKey}).from(u).where(E(v(u.id,g),v(u.tenantId,c)));if(!w)throw new Error(`Group not found in tenant: ${g}`);return String(w.groupKey)}function T(o,c){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:Y(String(o.membershipMode)),isSyncEnabled:!!o.isSyncEnabled,...o.syncedAt?{syncedAt:Ae(o.syncedAt).toISOString()}:{},...o.syncError?{syncError:String(o.syncError)}:{},unresolvedCount:c}}async function G(o,c){let g=await o.select({groupId:t.groupId,n:le()}).from(t).innerJoin(u,E(v(u.id,t.groupId),v(u.tenantId,c))).where(te(t.resolvedUserId)).groupBy(t.groupId);return new Map(g.map(w=>[Number(w.groupId),Number(w.n)]))}return{async listLinks(o){P(o,"listLinks(tenantId)"),await s();let c=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(v(a.tenantId,o)).orderBy(xe(a.groupId)),g=await G(l,o);return c.map(w=>(We(o,String(w.tenantId),"listLinks(row.tenantId)"),T(w,g.get(Number(w.groupId))??0)))},async externalGroupKey(o,c,g){P(o,"externalGroupKey(tenantId)"),await s();let[w]=await(g??l).select({externalKey:a.externalKey}).from(a).where(E(v(a.groupId,c),v(a.tenantId,o)));return w?String(w.externalKey):null},async linkGroup(o,c,g,w){P(o,"linkGroup(tenantId)");let A=Y(g.membershipMode),k=g.externalId.trim(),M=de(g.externalKey);if(k===""||M==="")throw new TypeError("[@aiquants/auth-directory-drizzle] externalId and externalKey must be non-empty");await s(),await d.linkable.assertLinkableAndWrite(o,c,async V=>{let C=V,ue=await D(C,o,c);await S(C,o,c,ue),await C.insert(a).output().values({groupId:c,tenantId:o,provider:g.provider,externalId:k,externalKey:M,membershipMode:A,isSyncEnabled:!0,vanishedObservations:0,createdAt:new Date,createdBy:w})}),y(o)},async unlinkGroup(o,c){P(o,"unlinkGroup(tenantId)"),await s(),await h(async g=>{await D(g,o,c),await g.delete(t).where(v(t.groupId,c)),await g.delete(r).where(E(v(r.groupId,c),v(r.tenantId,o))),await g.delete(a).where(E(v(a.groupId,c),v(a.tenantId,o)))}),y(o)},async setSyncEnabled(o,c,g,w){P(o,"setSyncEnabled(tenantId)"),await s(),await l.update(a).set({isSyncEnabled:g,updatedAt:new Date,updatedBy:w}).output().where(E(v(a.groupId,c),v(a.tenantId,o)))},async requestSync(o,c,g){return P(o,"requestSync(tenantId)"),await s(),l.transaction(async w=>{if(c!==null&&await D(w,o,c),(await w.select({id:m.id}).from(m).where(E(v(m.tenantId,o),c===null?te(m.groupId):v(m.groupId,c),te(m.pickedAt)))).length>0)return"already-pending";try{await w.insert(m).output().values({tenantId:o,groupId:c,requestedBy:g,requestedAt:new Date,createdAt:new Date,createdBy:g})}catch(k){if(!He(k))throw k;return"already-pending"}return"queued"})},async getStatus(o){P(o,"getStatus(tenantId)"),await s();let c=await l.select({groupId:a.groupId,isSyncEnabled:a.isSyncEnabled,syncedAt:a.syncedAt,syncError:a.syncError}).from(a).where(v(a.tenantId,o)),[g]=await l.select({n:le()}).from(m).where(E(v(m.tenantId,o),te(m.finishedAt))),w=c.filter(C=>!!C.isSyncEnabled),A=w.map(C=>C.syncedAt?Ae(C.syncedAt).getTime():null),k=A.length>0&&A.every(C=>C!==null)?Math.min(...A):null,V=c.length>0&&w.length===0||w.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:V,pausedGroupCount:c.length-w.length,stalenessThresholdSec:d.stalenessThresholdSec,erroredGroupIds:c.filter(C=>!!C.syncError).map(C=>Number(C.groupId)).sort((C,ue)=>C-ue),pendingRequestCount:Number(g?.n??0)}},async loadTenantMembershipPolicy(o){P(o,"loadTenantMembershipPolicy(tenantId)");let c=I>0?b.get(o):void 0;if(c&&Date.now()-c.loadedAt<I)return c.policy;await s();let g=await l.select({groupId:n.groupId,isBreakGlass:n.isBreakGlass,externalLinkId:a.id}).from(n).innerJoin(u,E(v(u.id,n.groupId),v(u.tenantId,o))).leftJoin(a,E(v(a.groupId,n.groupId),v(a.tenantId,o))).where(v(n.tenantId,o));if(g.length===0){let M={kind:"no-proof-required"};return I>0&&b.set(o,{policy:M,loadedAt:Date.now()}),M}let w=g.map(M=>Number(M.groupId)),A=g.filter(M=>!!M.isBreakGlass);for(let M of A)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:w,breakGlassGroupIds:A.map(M=>Number(M.groupId))};return je(k,`tenant ${o}`),I>0&&b.set(o,{policy:k,loadedAt:Date.now()}),k},async setTenantMembershipGroup(o,c,g,w){P(o,"setTenantMembershipGroup(tenantId)"),await s(),await h(async A=>{if(await D(A,o,c),g){let[M]=await A.select({id:a.id}).from(a).where(E(v(a.tenantId,o),v(a.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 A.select({groupId:n.groupId}).from(n).where(E(v(n.tenantId,o),v(n.groupId,c)));if(!g&&!(await A.select({groupId:n.groupId,isBreakGlass:n.isBreakGlass}).from(n).where(v(n.tenantId,o))).filter(C=>Number(C.groupId)!==c).some(C=>!!C.isBreakGlass))throw new Error(`Declaring group ${c} 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(k.length>0){await A.update(n).set({isBreakGlass:g,updatedAt:new Date,updatedBy:w}).output().where(E(v(n.tenantId,o),v(n.groupId,c)));return}await A.insert(n).output().values({tenantId:o,groupId:c,isBreakGlass:g,createdAt:new Date,createdBy:w})}),y(o)},async removeTenantMembershipGroup(o,c){P(o,"removeTenantMembershipGroup(tenantId)"),await s(),await h(async g=>{let A=(await g.select({groupId:n.groupId,isBreakGlass:n.isBreakGlass}).from(n).where(v(n.tenantId,o))).filter(k=>Number(k.groupId)!==c);if(A.length>0&&!A.some(k=>!!k.isBreakGlass))throw new Error(`Removing group ${c} would leave tenant ${o} with a membership proof but no break-glass path.`);await g.delete(n).where(E(v(n.tenantId,o),v(n.groupId,c)))}),y(o)},async setGroupActive(o,c,g,w){P(o,"setGroupActive(tenantId)"),await s(),await l.update(u).set({isActive:g,updatedAt:new Date,updatedBy:w}).output().where(E(v(u.id,c),v(u.tenantId,o)))},async listLedgerGroupIds(o,c){if(P(o,"listLedgerGroupIds(tenantId)"),!d.isEnabled)return[];await s();let g=await re(l,{ledger:t,group:u},{tenantId:o,externalUserId:c.externalUserId?.trim()||null,email:de(c.email)});return[...new Set(g.map(w=>w.groupId))].sort((w,A)=>w-A)},async isEmailAllowedByGroup(o){if(!d.isEnabled)return!1;let c=de(o);if(c==="")return!1;await s();let[g]=await l.select({id:t.id}).from(t).innerJoin(r,v(r.groupId,t.groupId)).innerJoin(u,E(v(u.id,t.groupId),v(u.isActive,!0))).where(v(t.email,c));return!!g},async listAllowlistGroups(o){return P(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,E(v(u.id,r.groupId),v(u.tenantId,o))).where(v(r.tenantId,o)).orderBy(xe(u.name))).map(g=>({id:Number(g.id),type:"group",groupId:Number(g.groupId),groupKey:String(g.groupKey),groupName:String(g.groupName),...g.description?{description:String(g.description)}:{},owner:{kind:"tenant",tenantId:o},readOnly:!1}))},async addAllowlistGroup(o,c,g,w){P(o,"addAllowlistGroup(tenantId)"),await s(),await h(async A=>{let k=await D(A,o,c),[M]=await A.select({id:a.id}).from(a).where(E(v(a.groupId,c),v(a.tenantId,o)));if(!M)throw new K(d.refusals.allowlistNeedsLink(k));await A.insert(r).output().values({tenantId:o,groupId:c,description:g??null,createdAt:new Date,createdBy:w})})},async removeAllowlistGroup(o,c){P(o,"removeAllowlistGroup(tenantId)"),await s(),await l.delete(r).where(E(v(r.id,c),v(r.tenantId,o)))},async countProjectedMembers(o,c){P(o,"countProjectedMembers(tenantId)"),await s();let[g]=await l.select({n:le()}).from(f).innerJoin(u,E(v(u.id,f.groupId),v(u.tenantId,o))).where(v(f.groupId,c));return Number(g?.n??0)}}}import{normalizeAuthEmail as Je}from"@aiquants/auth-core";import{assertTenantId as Ye,assertTenantMatch as Ve}from"@aiquants/authz-core";async function Wr(e,i){let p=Ye(i.tenantId,"promoteLedgerForUser(tenantId)");if(!Number.isInteger(i.userId)||i.userId<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] userId must be a positive integer, received ${String(i.userId)}`);let d=Je(i.email),l=i.externalUserId?.trim()||null;if(l===null&&d==="")return{addedGroupIds:[],matchedLedgerIds:[]};let s=await e.findLedgerMatches({tenantId:p,externalUserId:l,email:d});if(s.length===0)return{addedGroupIds:[],matchedLedgerIds:[]};for(let y of s)Ve(p,y.tenantId,"promoteLedgerForUser(findLedgerMatches row)");let h=[...new Set(s.map(y=>y.ledgerId))].sort((y,a)=>y-a),I=[...new Set(s.map(y=>y.groupId))].sort((y,a)=>y-a);return{addedGroupIds:[...await e.attachUserToGroups({tenantId:p,userId:i.userId,ledgerIds:h,groupIds:I})].sort((y,a)=>y-a),matchedLedgerIds:h}}import{assertTenantId as ke}from"@aiquants/authz-core";import{chunk as ce,chunkByParameters as Qe}from"@aiquants/mssql";import{inJsonList as Ze}from"@aiquants/mssql/drizzle";import{and as pe,eq as me,inArray as ye}from"drizzle-orm";var Xe=4;function et(e,i,p,d){let l=e,s=d.ensureConnection??(async()=>{}),h=$(l,d),I=d.now??(()=>new Date),b=i.TDGroupMemberEmail,{TMGroup:y,TDGroupMember:a}=p;return{async findLedgerMatches(t){return ke(t.tenantId,"findLedgerMatches(tenantId)"),await s(),re(l,{ledger:b,group:y},t)},async attachUserToGroups({tenantId:t,userId:n,ledgerIds:r,groupIds:m}){return ke(t,"attachUserToGroups(tenantId)"),await s(),r.length===0?[]:h(async u=>{let f=new Set;for(let G of ce(m)){let o=await u.select({id:y.id}).from(y).where(pe(ye(y.id,G),me(y.tenantId,t),me(y.isActive,!0)));for(let c of o)f.add(Number(c.id))}let S=m.filter(G=>f.has(G));if(S.length===0)return[];let D=new Set;for(let G of ce(S)){let o=await u.select({groupId:a.groupId}).from(a).where(pe(me(a.userId,n),ye(a.groupId,G)));for(let c of o)D.add(Number(c.groupId))}let T=S.filter(G=>!D.has(G));for(let G of ce(r))await u.update(b).set({resolvedUserId:n}).output().where(pe(ye(b.id,G),Ze(b.groupId,S)));if(T.length>0){let G=I();for(let o of Qe(T,Xe))await u.insert(a).output().values(o.map(c=>({groupId:c,userId:n,createdAt:G,createdBy:d.actor})))}return T})}}}import{AUTHZ_TENANT_COLUMN as B}from"@aiquants/authz-drizzle";import{bigint as _,bit as Re,datetime2 as O,foreignKey as ne,int as ge,mssqlSchema as er,nvarchar as x,primaryKey as U,uniqueIndex as z}from"drizzle-orm/mssql-core";var he="provider",ot=["TDGroupExternalLink","TDTenantMembershipGroup","TDAllowlistGroup","TDDirectorySyncRequest","TDDirectoryCatalog","TMDirectoryCatalogObservation","TMAllowlist"];function it(e,i){let p=er(e),d=()=>({createdAt:O("created_at"),createdBy:x("created_by",{length:50}),updatedAt:O("updated_at"),updatedBy:x("updated_by",{length:50})}),l=()=>({createdAt:O("created_at"),createdBy:x("created_by",{length:50})}),s=p.table("TDGroupExternalLink",{id:_("id",{mode:"number"}).identity().notNull(),groupId:_("group_id",{mode:"number"}).notNull(),tenantId:x(B,{length:64}).notNull(),provider:x(he,{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:Re("is_sync_enabled").notNull(),syncedAt:O("synced_at"),syncError:x("sync_error",{length:1e3}),vanishedObservations:ge("vanished_observations").notNull(),...d()},r=>[U({name:`${e}_TDGroupExternalLink_pk`,columns:[r.id]}),z(`${e}_TDGroupExternalLink_group_uq`).on(r.groupId),z(`${e}_TDGroupExternalLink_external_uq`).on(r.tenantId,r.provider,r.externalId),ne({name:`${e}_TDGroupExternalLink_group_id_fk`,columns:[r.groupId],foreignColumns:[i.groupTable.id]})]),h=p.table("TDGroupMemberEmail",{id:_("id",{mode:"number"}).identity().notNull(),groupId:_("group_id",{mode:"number"}).notNull(),email:x("email",{length:320}).notNull(),externalUserId:x("external_user_id",{length:128}),resolvedUserId:_("resolved_user_id",{mode:"number"}),observedAt:O("observed_at").notNull(),...d()},r=>[U({name:`${e}_TDGroupMemberEmail_pk`,columns:[r.id]}),z(`${e}_TDGroupMemberEmail_group_email_uq`).on(r.groupId,r.email),ne({name:`${e}_TDGroupMemberEmail_group_id_fk`,columns:[r.groupId],foreignColumns:[i.groupTable.id]})]),I=p.table("TDTenantMembershipGroup",{tenantId:x(B,{length:64}).notNull(),groupId:_("group_id",{mode:"number"}).notNull(),isBreakGlass:Re("is_break_glass").notNull(),...l()},r=>[U({name:`${e}_TDTenantMembershipGroup_pk`,columns:[r.tenantId,r.groupId]}),ne({name:`${e}_TDTenantMembershipGroup_group_id_fk`,columns:[r.groupId],foreignColumns:[i.groupTable.id]})]),b=p.table("TDAllowlistGroup",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:x(B,{length:64}).notNull(),groupId:_("group_id",{mode:"number"}).notNull(),description:x("description",{length:512}),...l()},r=>[U({name:`${e}_TDAllowlistGroup_pk`,columns:[r.id]}),z(`${e}_TDAllowlistGroup_tenant_group_uq`).on(r.tenantId,r.groupId),ne({name:`${e}_TDAllowlistGroup_group_id_fk`,columns:[r.groupId],foreignColumns:[i.groupTable.id]})]),y=p.table("TDDirectorySyncRequest",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:x(B,{length:64}).notNull(),groupId:_("group_id",{mode:"number"}),requestedBy:x("requested_by",{length:320}).notNull(),requestedAt:O("requested_at").notNull(),pickedAt:O("picked_at"),finishedAt:O("finished_at"),result:x("result",{length:32}),error:x("error",{length:2e3}),...l()},r=>[U({name:`${e}_TDDirectorySyncRequest_pk`,columns:[r.id]})]),a=p.table("TMAllowlist",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:x(B,{length:64}).notNull(),pattern:x("pattern",{length:256}).notNull(),type:x("type",{length:32}).notNull(),description:x("description",{length:512}),...l()},r=>[U({name:`${e}_TMAllowlist_pk`,columns:[r.id]}),z(`${e}_TMAllowlist_pattern_uq`).on(r.tenantId,r.pattern)]),t=p.table("TDDirectoryCatalog",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:x(B,{length:64}).notNull(),provider:x(he,{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:ge("member_count"),observedAt:O("observed_at").notNull(),...l()},r=>[U({name:`${e}_TDDirectoryCatalog_pk`,columns:[r.id]}),z(`${e}_TDDirectoryCatalog_external_uq`).on(r.tenantId,r.provider,r.externalId)]),n=p.table("TMDirectoryCatalogObservation",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:x(B,{length:64}).notNull(),provider:x(he,{length:32}).notNull(),observedAt:O("observed_at").notNull(),entryCount:ge("entry_count").notNull(),...d()},r=>[U({name:`${e}_TMDirectoryCatalogObservation_pk`,columns:[r.id]}),z(`${e}_TMDirectoryCatalogObservation_target_uq`).on(r.tenantId,r.provider)]);return{TDGroupExternalLink:s,TDGroupMemberEmail:h,TDTenantMembershipGroup:I,TDAllowlistGroup:b,TDDirectorySyncRequest:y,TDDirectoryCatalog:t,TMDirectoryCatalogObservation:n,TMAllowlist:a}}import{assertBlastRadiusPolicy as rr,reconcile as tr}from"@aiquants/auth-directory-core";function oe(e){if(e instanceof Error&&(e.name==="AbortError"||e.name==="TimeoutError"))return"timeout";let i=typeof e=="object"&&e!==null&&"status"in e?e.status:void 0;return typeof i!="number"?"unknown":i===401||i===403?"denied":i===429||i===408?"rateLimited":i>=500?"upstreamError":"unknown"}var Ge=e=>`${e.length} \u4EF6`,be={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"},Ee={upstreamUnreadable:e=>`\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${be[e]})\u3002`,localWriteFailed:e=>`\u30ED\u30FC\u30AB\u30EB\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${be[e]})\u3002`,resolverIncomplete:(e,i)=>`\u4E0A\u6D41\u304C ${e} \u4EF6\u306E\u30E1\u30F3\u30D0\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F\u304C\u3001\u7A81\u5408\u7D50\u679C\u306F ${i} \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,i,p)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (${e})\u3002\u9023\u7D9A ${i} \u56DE\u76EE\u3067\u3059\u3002${p} \u56DE\u306B\u9054\u3057\u305F\u6642\u70B9\u3067\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3059\u3002`,groupVanished:(e,i)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C ${i} \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 ${Ge(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 i=e.axis==="membership"?"\u6A29\u9650\u3092\u5931\u3046\u4EBA\u6570":"\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u306A\u304F\u306A\u308B\u4EBA\u6570",p=e.axis==="membership"?e.withheldProjectionRemoves.map(String):e.withheldLedgerDeletes;return[`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: ${i}\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: ${Ge(p)} (\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(i=>be[i.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(i=>i.userId).join(", ")})\u3002\u6B8B\u308A\u306E\u5909\u66F4\u306F\u9069\u7528\u6E08\u307F\u3067\u3059\u3002`,foreignProvider:(e,i)=>`\u3053\u306E\u30EA\u30F3\u30AF\u306E\u4F9B\u7D66\u5143\u306F ${e} \u3067\u3059\u304C\u3001\u5B9F\u884C\u4E2D\u306E\u540C\u671F\u306F ${i} \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 fe=["aborted","missing","vanished","upstream-failed","local-failed","foreign-provider","not-converged"],Me=[...fe,"already-vanished"];function pt(e){let{directory:i,repository:p,policy:d,now:l,logger:s}=e,h=e.messages??Ee;if(rr(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 I(t,n){try{await p.recordFailure(t,n)}catch(r){s.error(`[auth-directory] could not record the failure for ${t.externalKey}:`,r)}}async function b(t){try{let n=await i.getGroup(t.externalId);return n===null?{kind:"missing"}:{kind:"ok",group:n,members:await i.listGroupMembers(n,t.membershipMode)}}catch(n){return{kind:"failed",error:n instanceof Error?n:new Error(String(n))}}}async function y(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 n=await p.recordVanishedObservation(t,h.groupMissing(t.externalKey,t.vanishedObservations+1,e.vanishedThreshold));return n<e.vanishedThreshold?(s.warn(`[auth-directory] ${t.externalKey} missing upstream (${n}/${e.vanishedThreshold})`),{kind:"missing",link:t,observations:n}):(await p.markGroupInactive(t),await I(t,h.groupVanished(t.externalKey,n)),s.warn(`[auth-directory] ${t.externalKey} deactivated after ${n} consecutive absences`),{kind:"vanished",link:t})}async function a(t){if(t.provider!==e.provider)return await I(t,h.foreignProvider(t.provider,e.provider)),s.warn(`[auth-directory] skipping ${t.externalKey}: belongs to provider ${t.provider}`),{kind:"foreign-provider",link:t};let n=await b(t);if(n.kind==="missing")return y(t);if(n.kind==="failed")return await I(t,h.upstreamUnreadable(oe(n.error))),s.error(`[auth-directory] upstream read failed for ${t.externalKey}:`,n.error),{kind:"upstream-failed",link:t,error:n.error};try{await p.resolveExternalIdentity(t,n.group);let[r,m]=await Promise.all([p.loadGroupSnapshot(t),p.resolveMembers(n.members)]);if(m.length!==n.members.length){let T=h.resolverIncomplete(n.members.length,m.length);return await I(t,T),s.error(`[auth-directory] ${T}`),{kind:"local-failed",link:t,error:new Error(T)}}let u=tr(r,m,d);if(u.aborted){await I(t,h.planRefused(u.aborted));let T=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: ${T.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(),D={refusals:[],failures:[]};return f&&(D=await p.applyPlan(t,u,S)),await p.recordSuccess(t,n.group,S),D.refusals.length>0&&(await I(t,h.removalsRefused(D.refusals)),s.warn(`[auth-directory] ${D.refusals.length} removal(s) refused for ${t.externalKey}: users ${D.refusals.map(T=>T.userId).join(", ")}`)),D.failures.length>0&&(await I(t,h.removalWritesFailed(D.failures)),s.warn(`[auth-directory] ${D.failures.length} removal(s) could not be written for ${t.externalKey}: users ${D.failures.map(T=>T.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}:f?{kind:"applied",link:t,plan:u}:{kind:"unchanged",link:t}}catch(r){let m=r instanceof Error?r:new Error(String(r));return await I(t,h.localWriteFailed("localWriteError")),s.error(`[auth-directory] local write failed for ${t.externalKey}:`,m),{kind:"local-failed",link:t,error:m}}}return{syncOne:a,async run(t){let n=await p.listSyncableLinks(t),r=[];for(let f of n)try{r.push(await a(f))}catch(S){let D=S instanceof Error?S:new Error(String(S));s.error(`[auth-directory] ${f.externalKey} ended abnormally:`,D),r.push({kind:"local-failed",link:f,error:D})}let m=r.filter(f=>Me.includes(f.kind)).length,u=r.filter(f=>fe.includes(f.kind)).length;return s.info(`[auth-directory] run finished: ${r.length} link(s), ${m} unhealthy (${u} new)`),{outcomes:r,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 Ce,eq as j,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.",we=2e3;function bt(e,i,p){let d=e,l=p.ensureConnection??(async()=>{}),s=i.TDDirectorySyncRequest,h=nr(p.tenantId,"createSyncRequestQueue(tenantId)"),I=p.maxErrorLength??we;if(!Number.isInteger(I)||I<1||I>we)throw new TypeError(`[@aiquants/auth-directory-drizzle] maxErrorLength must be an integer within 1..${we}, received ${String(p.maxErrorLength)}`);let b={async claim(){await l();let y=await d.select({id:s.id}).from(s).where(ie(j(s.tenantId,h),ae(s.pickedAt))).orderBy(Ce(s.id));for(let a of y){let n=(await d.update(s).set({pickedAt:p.now()}).output().where(ie(j(s.tenantId,h),j(s.id,Number(a.id)),ae(s.pickedAt))))?.[0];if(!n)continue;if(n.requestedBy===void 0||n.requestedBy===null)throw new TypeError(`[@aiquants/auth-directory-drizzle] the driver returned a row without requestedBy for request ${String(n.id)}`);let r=n.groupId??null;return{id:Number(n.id),tenantId:h,groupId:r===null?null:Number(r),requestedBy:String(n.requestedBy)}}return null},async finish(y,a){await l();let t=a.error?a.error.slice(0,I):null;return!!(await d.update(s).set({finishedAt:p.now(),result:a.result,error:t}).output().where(ie(j(s.tenantId,h),j(s.id,y),ae(s.finishedAt))))?.length},async listStranded(y){await l();let a=await d.select({id:s.id,pickedAt:s.pickedAt,requestedAt:s.requestedAt}).from(s).where(ie(j(s.tenantId,h),ae(s.finishedAt))).orderBy(Ce(s.id)),t=n=>{let r=or(n);return Number.isNaN(r.getTime())||r<y};return{stranded:a.filter(n=>n.pickedAt!==null&&t(n.pickedAt)).map(n=>Number(n.id)),unclaimed:a.filter(n=>n.pickedAt===null&&t(n.requestedAt)).map(n=>Number(n.id))}},async abandonStranded(y){let{stranded:a}=await b.listStranded(y),t=[];for(let n of a)try{await b.finish(n,{result:"failed",error:ir})&&t.push(n)}catch(r){p.onCloseFailed?.(n,r)}return t}};return b}import{normalizeAuthEmail as se}from"@aiquants/auth-core";import{assertTenantId as Pe,assertTenantMatch as ar}from"@aiquants/authz-core";import{chunk as _e,chunkByParameters as Le}from"@aiquants/mssql";import{notInJsonList as sr}from"@aiquants/mssql/drizzle";import{and as L,eq as R,inArray as Ie,isNull as ur,or as dr}from"drizzle-orm";var lr=7,cr=4,Ke=1e3;function pr(e){if(!(e instanceof Error))return!1;let i=e;return i.number===void 0&&i.code===void 0&&e.name==="Error"}function kt(e,i,p,d){let l=e,s=d.ensureConnection??(async()=>{}),h=$(l,d),I=d.now??(()=>new Date),{TDGroupExternalLink:b,TDGroupMemberEmail:y}=i,{TMGroup:a,TDGroupMember:t}=p,n=d.actor;return{async listSyncableLinks(r){await s();let m=Pe(r.tenantId,"listSyncableLinks(tenantId)"),u=[R(b.isSyncEnabled,!0),R(b.tenantId,m)];return r.groupId!==void 0&&u.push(R(b.groupId,r.groupId)),(await l.select({linkId:b.id,groupId:b.groupId,linkTenantId:b.tenantId,groupTenantId:a.tenantId,groupKey:a.groupKey,isGroupActive:a.isActive,provider:b.provider,externalId:b.externalId,externalKey:b.externalKey,membershipMode:b.membershipMode,vanishedObservations:b.vanishedObservations}).from(b).innerJoin(a,L(R(a.id,b.groupId),R(a.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:Y(String(S.membershipMode)),isGroupActive:!!S.isGroupActive,vanishedObservations:Number(S.vanishedObservations??0)}))},async loadGroupSnapshot(r){await s();let[m,u]=await Promise.all([l.select({email:y.email,externalUserId:y.externalUserId,resolvedUserId:y.resolvedUserId}).from(y).where(R(y.groupId,r.groupId)),l.select({userId:t.userId}).from(t).where(R(t.groupId,r.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(r){return d.memberResolver.resolve(r)},async applyPlan(r,m,u){Pe(r.tenantId,"applyPlan(link.tenantId)"),await s(),(m.ledgerUpserts.length>0||m.projectionAdds.length>0)&&await h(async T=>{let G=m.ledgerUpserts.map(g=>({...g,email:se(g.email)})),o=new Map;for(let g of _e(G.map(w=>w.email))){let w=await T.select({id:y.id,email:y.email}).from(y).where(L(R(y.groupId,r.groupId),Ie(y.email,g)));for(let A of w)o.set(String(A.email),Number(A.id))}let c=G.filter(g=>!o.has(g.email));for(let g of Le(c,lr))await T.insert(y).output().values(g.map(w=>({groupId:r.groupId,email:w.email,externalUserId:w.externalUserId??null,resolvedUserId:w.resolvedUserId,observedAt:u,createdAt:u,createdBy:n})));for(let g of G){let w=o.get(g.email);w!==void 0&&await T.update(y).set({externalUserId:g.externalUserId??null,resolvedUserId:g.resolvedUserId,observedAt:u,updatedAt:u,updatedBy:n}).output().where(R(y.id,w))}for(let g of Le(m.projectionAdds,cr)){let w=new Set((await T.select({userId:t.userId}).from(t).where(L(R(t.groupId,r.groupId),Ie(t.userId,g)))).map(k=>Number(k.userId))),A=g.filter(k=>!w.has(k));A.length>0&&await T.insert(t).output().values(A.map(k=>({groupId:r.groupId,userId:k,createdAt:u,createdBy:n})))}});let f=[],S=[];for(let T of m.projectionRemoves)try{await d.lockoutGuard.guardedWrite(r.tenantId,{removeGroupMembership:{groupId:r.groupId,userId:T}},"remove-group-member",G=>G.delete(t).where(L(R(t.groupId,r.groupId),R(t.userId,T))))}catch(G){pr(G)?f.push({userId:T,applied:!1,refusal:G.message}):S.push({userId:T,cause:oe(G)})}let D=f.map(T=>T.userId);return m.ledgerDeletes.length>0&&await h(async T=>{for(let G of _e(m.ledgerDeletes)){let o=D.length>0?dr(ur(y.resolvedUserId),sr(y.resolvedUserId,D)):void 0;await T.delete(y).where(L(R(y.groupId,r.groupId),Ie(y.email,G),...o?[o]:[]))}}),{refusals:f,failures:S}},async resolveExternalIdentity(r,m){await s(),!(r.externalId===m.externalId&&r.externalKey===se(m.externalKey))&&await l.update(b).set({externalId:m.externalId,externalKey:se(m.externalKey)}).output().where(L(R(b.id,r.linkId),R(b.tenantId,r.tenantId)))},async recordSuccess(r,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:n}).output().where(L(R(b.id,r.linkId),R(b.tenantId,r.tenantId))),r.isGroupActive||await f.update(a).set({isActive:!0,updatedAt:u,updatedBy:n}).output().where(L(R(a.id,r.groupId),R(a.tenantId,r.tenantId)))})},async recordFailure(r,m){await s(),await l.update(b).set({syncError:m.slice(0,Ke),updatedAt:I(),updatedBy:n}).output().where(L(R(b.id,r.linkId),R(b.tenantId,r.tenantId)))},async recordVanishedObservation(r,m){await s();let u=r.vanishedObservations+1;return await l.update(b).set({vanishedObservations:u,syncError:m.slice(0,Ke),updatedAt:I(),updatedBy:n}).output().where(L(R(b.id,r.linkId),R(b.tenantId,r.tenantId))),u},async markGroupInactive(r){await s(),await l.update(a).set({isActive:!1,updatedAt:I(),updatedBy:n}).output().where(L(R(a.id,r.groupId),R(a.tenantId,r.tenantId),R(a.isActive,!0)))}}}export{he as AUTH_DIRECTORY_PROVIDER_COLUMN,ot as AUTH_DIRECTORY_TENANT_SCOPED_TABLES,K as DirectoryOperatorError,Te as MEMBERSHIP_MODES,fe as NEWLY_UNHEALTHY_SYNC_OUTCOMES,ir as STRANDED_REASON,Me as UNHEALTHY_SYNC_OUTCOMES,kr as assertGroupMayBeLinked,Y as assertMembershipMode,oe as classifySyncFailure,Ir as createDirectoryCatalogReader,wr as createDirectoryCatalogWriter,Br as createDirectoryLinkStore,pt as createDirectorySync,kt as createDirectorySyncRepository,et as createPromotionRepository,bt as createSyncRequestQueue,Ur as defaultDirectoryLinkRefusals,Ar as defaultExternalGroupGuardMessage,Ee as defaultSyncMessages,it as defineAuthDirectorySchema,re as findLedgerRowsForIdentity,Wr as promoteLedgerForUser,$ as transactionRunner,xr as withExternalGroupGuards};
|
|
1
|
+
import{assertTenantId as xe}from"@aiquants/authz-core";import{chunkByParameters as We,toInstant as He}from"@aiquants/mssql";import{and as ee,asc as Ve,eq as q}from"drizzle-orm";import{runWithTransactionRetry as je}from"@aiquants/mssql";function N(e,n){return c=>je(u=>e.transaction(d=>u(()=>c(d))),n.transactionRetry)}var Je=10,$={tenantId:64,provider:32,externalId:256,externalKey:320,name:256,description:1e3,actor:50},ke=2147483647;function Ye(e){if(e===void 0||!Number.isFinite(e))return null;let n=Math.trunc(e);return n<0?null:n>ke?ke:n}function V(e,n,c){let u=e.trim();if(u==="")throw new TypeError(`[@aiquants/auth-directory-drizzle] ${c} must not be blank`);if(u.length>n)throw new TypeError(`[@aiquants/auth-directory-drizzle] ${c} exceeds its column length (${n}); truncating it would change which rows it owns`);return u}function Gr(e,n,c){let u=e,d=c.ensureConnection??(async()=>{}),a=N(u,c),p=n.TDDirectoryCatalog,v=n.TMDirectoryCatalogObservation,f=h=>{let s=new Map;for(let t of h)s.set(t.externalId,t);return[...s.values()]};return{async replace(h,s,t){await d();let i=V(xe(h,"replace(tenantId)"),$.tenantId,"tenantId"),r=V(s,$.provider,"provider"),g=V(c.actor,$.actor,"actor"),l=c.now(),I=f(t.map(b=>({tenantId:i,provider:r,externalId:b.externalId.slice(0,$.externalId),externalKey:b.externalKey.slice(0,$.externalKey),name:b.name.slice(0,$.name),description:b.description===void 0?null:b.description.slice(0,$.description),memberCount:Ye(b.memberCount),observedAt:l,createdAt:l,createdBy:g})));return await a(async b=>{await b.delete(p).where(ee(q(p.tenantId,i),q(p.provider,r)));for(let T of We(I,Je))await b.insert(p).values(T);await b.delete(v).where(ee(q(v.tenantId,i),q(v.provider,r))),await b.insert(v).values([{tenantId:i,provider:r,observedAt:l,entryCount:I.length,createdAt:l,createdBy:g,updatedAt:l,updatedBy:g}])}),I.length}}}function Cr(e,n,c={}){let u=e,d=c.ensureConnection??(async()=>{}),a=n.TDDirectoryCatalog,p=n.TMDirectoryCatalogObservation;return{async read(v,f){await d();let h=V(xe(v,"read(tenantId)"),$.tenantId,"tenantId"),s=V(f,$.provider,"provider"),t=await u.select({observedAt:p.observedAt,entryCount:p.entryCount,externalId:a.externalId,externalKey:a.externalKey,name:a.name,description:a.description,memberCount:a.memberCount}).from(p).leftJoin(a,ee(q(a.tenantId,p.tenantId),q(a.provider,p.provider))).where(ee(q(p.tenantId,h),q(p.provider,s))).orderBy(Ve(a.name)),i=t.filter(l=>l.externalId!==null).map(l=>({externalId:String(l.externalId),externalKey:String(l.externalKey),name:String(l.name),...l.description===null?{}:{description:l.description},...l.memberCount===null?{}:{memberCount:Number(l.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 (${h}, ${s}): the observation records ${r.entryCount} entries but ${i.length} rows are present`);let g=He(r.observedAt);if(!Number.isFinite(g.getTime()))throw new Error(`[@aiquants/auth-directory-drizzle] catalog observation for (${h}, ${s}) has an unreadable observed_at`);return{groups:i,observedAt:g}}}}import{AUTHZ_ACTIONS as Qe}from"@aiquants/authz-core";var K=class extends Error{constructor(c){super(c);this.operatorFacing=!0;this.name="DirectoryOperatorError"}};var Or=(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 W(e,n,c,u){return new Set(e.filter(d=>n.has(d.roleId)&&d.appKey===c&&d.resourceKey===u).map(d=>d.action))}function re(e,n){return new Set(e.filter(c=>c.groupId===n).map(c=>c.roleId))}var J=e=>Qe.every(n=>e.has(n)),ne=(e,n)=>e.find(c=>c.roleId===n)?.roleKey??`#${n}`;function te(e,n,c,u){let d=[...n].find(p=>J(W(e,new Set([p]),c,u)));return d!==void 0?ne(e,d):[...n].filter(p=>W(e,new Set([p]),c,u).size>0).map(p=>ne(e,p)).join(" + ")}function Ur(e,n){return Re(e,n)}function Re(e,n){let c=e;return{...e,withTransaction:(u,d)=>c.withTransaction((a,p)=>u(Re(a,n),p),d),async assignGroupRole(u,d,a,p){await c.withTransaction(async(v,f)=>{let h=await v.listGrants(u),s=re(await v.listGroupRoles(u),d);if(s.add(a),J(W(h,s,n.adminAppKey,n.adminResourceKey))){let t=await n.linkLookup.externalGroupKey(u,d,f);if(t!==null)throw new K(n.message(te(h,s,n.adminAppKey,n.adminResourceKey),t))}await v.assignGroupRole(u,d,a,p)})},async updateGrant(u,d,a,p){let v=a.action;if(v===void 0)return c.updateGrant(u,d,a,p);await c.withTransaction(async(f,h)=>{let s=await f.listGrants(u),t=s.find(g=>g.id===d);if(t===void 0)return f.updateGrant(u,d,a,p);let r=(await f.listResources(u)).find(g=>g.id===t.resourceId);if(r?.appKey===n.adminAppKey&&r?.resourceKey===n.adminResourceKey){let g=await f.listGroupRoles(u);for(let l of new Set(g.filter(I=>I.roleId===t.roleId).map(I=>I.groupId))){let I=re(g,l),b=W(s.filter(A=>A.id!==d),I,n.adminAppKey,n.adminResourceKey);if(I.has(t.roleId)&&b.add(v),!J(b))continue;let T=await n.linkLookup.externalGroupKey(u,l,h);if(T!==null)throw new K(n.message(te(s,I,n.adminAppKey,n.adminResourceKey)||ne(s,t.roleId),T))}}await f.updateGrant(u,d,a,p)})},async createGrant(u,d,a){await c.withTransaction(async(p,v)=>{let h=(await p.listResources(u)).find(s=>s.id===d.resourceId);if(h?.appKey===n.adminAppKey&&h?.resourceKey===n.adminResourceKey){let s=await p.listGrants(u),t=await p.listGroupRoles(u);for(let i of new Set(t.filter(r=>r.roleId===d.roleId).map(r=>r.groupId))){let r=W(s,re(t,i),n.adminAppKey,n.adminResourceKey);if(r.add(d.action),!J(r))continue;let g=await n.linkLookup.externalGroupKey(u,i,v);if(g!==null)throw new K(n.message(te(s,re(t,i),n.adminAppKey,n.adminResourceKey)||ne(s,d.roleId),g))}}await p.createGrant(u,d,a)})}}}async function $r(e,n,c,u){let d=(await e.listGroupRoles(n)).filter(f=>f.groupId===c);if(d.length===0)return;let a=await e.listGrants(n),p=new Set(d.map(f=>f.roleId));if(!J(W(a,p,u.adminAppKey,u.adminResourceKey)))return;let v=d[0];if(v)throw new K(u.message(te(a,p,u.adminAppKey,u.adminResourceKey),v.groupKey))}import{and as Ze,eq as Y}from"drizzle-orm";async function oe(e,n,c){let{ledger:u,group:d}=n,{tenantId:a,externalUserId:p,email:v}=c;if(p===null&&v==="")return[];let f={ledgerId:u.id,groupId:u.groupId,tenantId:d.tenantId,externalUserId:u.externalUserId},h=async g=>(await e.select(f).from(u).innerJoin(d,Ze(Y(d.id,u.groupId),Y(d.tenantId,a),Y(d.isActive,!0))).where(g)).map(I=>({ledgerId:Number(I.ledgerId),groupId:Number(I.groupId),tenantId:String(I.tenantId),externalUserId:I.externalUserId===null?null:String(I.externalUserId)})),s=g=>{let l=new Map;for(let I of g)l.set(I.ledgerId,{ledgerId:I.ledgerId,groupId:I.groupId,tenantId:I.tenantId});return[...l.values()].sort((I,b)=>I.ledgerId-b.ledgerId)},t=p===null?[]:await h(Y(u.externalUserId,p));if(v==="")return s(t);let i=await h(Y(u.email,v)),r=g=>g.externalUserId!==null&&g.externalUserId!==p;return s([...t,...i.filter(g=>!r(g))])}import{AUTH_DIRECTORY_NO_UNRESOLVED as Ee,normalizeAuthEmail as Z}from"@aiquants/auth-core";import{assertTenantMembershipPolicy as Xe}from"@aiquants/auth-directory-core";import{assertTenantId as P,assertTenantMatch as er}from"@aiquants/authz-core";import{toInstant as Ge}from"@aiquants/mssql";import{and as C,asc as Ce,count as Pe,eq as S,isNull as ie}from"drizzle-orm";var Me=["direct","transitive"];function Q(e){if(!Me.includes(e))throw new TypeError(`[@aiquants/auth-directory-drizzle] membershipMode must be one of ${Me.join(" | ")}, received ${JSON.stringify(e)}`);return e}var Xr={breakGlassLink:e=>`\u30B0\u30EB\u30FC\u30D7 (${e}) \u306F\u7DCA\u6025\u7528 (break-glass) \u306E\u30C6\u30CA\u30F3\u30C8\u5E30\u5C5E\u306E\u7D4C\u8DEF\u306B\u6307\u5B9A\u3055\u308C\u3066\u3044\u308B\u305F\u3081\u3001\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3068\u9023\u643A\u3067\u304D\u307E\u305B\u3093\u3002\u3053\u306E\u7D4C\u8DEF\u306F\u3001\u540C\u671F\u304C\u89E6\u308C\u306A\u3044\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u30B0\u30EB\u30FC\u30D7\u306B\u9650\u3063\u3066\u3044\u307E\u3059\u3002`,allowlistNeedsLink:e=>`\u30B0\u30EB\u30FC\u30D7 (${e}) \u306F\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u305F\u3081\u3001\u30B5\u30A4\u30F3\u30A4\u30F3\u8A31\u53EF\u306B\u4F7F\u3048\u307E\u305B\u3093\u3002\u30B0\u30EB\u30FC\u30D7\u5358\u4F4D\u306E\u30B5\u30A4\u30F3\u30A4\u30F3\u8A31\u53EF\u306F\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u30E1\u30F3\u30D0\u30FC\u53F0\u5E33\u3067\u5224\u5B9A\u3059\u308B\u306E\u3067\u3001\u4E0A\u6D41\u3068\u9023\u643A\u3057\u305F\u30B0\u30EB\u30FC\u30D7\u3060\u3051\u3092\u6307\u5B9A\u3067\u304D\u307E\u3059\u3002`};function rr(e){let n=typeof e=="object"&&e!==null&&"number"in e?e.number:void 0;return n===2601||n===2627}function et(e,n,c,u){let d=e,a=u.ensureConnection??(async()=>{}),p=N(d,u),v=u.policyCacheTtlMs??0;if(!Number.isFinite(v)||v<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] policyCacheTtlMs must be a non-negative number, received ${String(u.policyCacheTtlMs)}`);let f=new Map,h=o=>f.delete(o),{TDGroupExternalLink:s,TDGroupMemberEmail:t,TDTenantMembershipGroup:i,TDAllowlistGroup:r,TDDirectorySyncRequest:g}=n,{TMGroup:l,TDGroupMember:I}=c;async function b(o,m,y,w){if((await o.select({groupId:i.groupId}).from(i).where(C(S(i.tenantId,m),S(i.groupId,y),S(i.isBreakGlass,!0)))).length>0)throw new K(u.refusals.breakGlassLink(w))}async function T(o,m,y){let[w]=await o.select({groupKey:l.groupKey}).from(l).where(C(S(l.id,y),S(l.tenantId,m)));if(!w)throw new Error(`Group not found in tenant: ${y}`);return String(w.groupKey)}function A(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:Q(String(o.membershipMode)),isSyncEnabled:!!o.isSyncEnabled,...o.syncedAt?{syncedAt:Ge(o.syncedAt).toISOString()}:{},...o.syncError?{syncError:String(o.syncError)}:{},unresolved:m}}async function D(o){let m=await d.select({groupId:t.groupId,email:t.email,externalUserId:t.externalUserId}).from(t).innerJoin(l,C(S(l.id,t.groupId),S(l.tenantId,o))).where(ie(t.resolvedUserId));if(m.length===0)return new Map;let y=m.map(x=>({email:String(x.email),...x.externalUserId?{externalUserId:String(x.externalUserId)}:{}})),w=await u.memberResolver.resolve(y);if(w.length!==y.length)throw new Error(`[@aiquants/auth-directory-drizzle] memberResolver returned ${w.length} member(s) for ${y.length}`);let k=new Map;return m.forEach((x,E)=>{let U=w[E];if(Z(U.email)!==Z(y[E].email))throw new Error(`[@aiquants/auth-directory-drizzle] memberResolver answered ${U.email} at position ${E}, where ${y[E].email} was asked`);let G=U.resolvedUserId===null?U.unresolvedReason:"pendingSync",j=Number(x.groupId),X=k.get(j);X===void 0&&(X={...Ee},k.set(j,X)),X[G]+=1}),k}return{async listLinks(o){P(o,"listLinks(tenantId)"),await a();let m=await d.select({linkId:s.id,groupId:s.groupId,tenantId:s.tenantId,provider:s.provider,externalId:s.externalId,externalKey:s.externalKey,membershipMode:s.membershipMode,isSyncEnabled:s.isSyncEnabled,syncedAt:s.syncedAt,syncError:s.syncError}).from(s).where(S(s.tenantId,o)).orderBy(Ce(s.groupId)),y=await D(o);return m.map(w=>(er(o,String(w.tenantId),"listLinks(row.tenantId)"),A(w,y.get(Number(w.groupId))??Ee)))},async unresolvedByGroup(o){return P(o,"unresolvedByGroup(tenantId)"),await a(),D(o)},async externalGroupKey(o,m,y){P(o,"externalGroupKey(tenantId)"),await a();let[w]=await(y??d).select({externalKey:s.externalKey}).from(s).where(C(S(s.groupId,m),S(s.tenantId,o)));return w?String(w.externalKey):null},async linkGroup(o,m,y,w){P(o,"linkGroup(tenantId)");let k=Q(y.membershipMode),x=y.externalId.trim(),E=Z(y.externalKey);if(x===""||E==="")throw new TypeError("[@aiquants/auth-directory-drizzle] externalId and externalKey must be non-empty");await a(),await u.linkable.assertLinkableAndWrite(o,m,async U=>{let G=U,j=await T(G,o,m);await b(G,o,m,j),await G.insert(s).output().values({groupId:m,tenantId:o,provider:y.provider,externalId:x,externalKey:E,membershipMode:k,isSyncEnabled:!0,vanishedObservations:0,createdAt:new Date,createdBy:w})}),h(o)},async unlinkGroup(o,m){P(o,"unlinkGroup(tenantId)"),await a(),await p(async y=>{await T(y,o,m),await y.delete(t).where(S(t.groupId,m)),await y.delete(r).where(C(S(r.groupId,m),S(r.tenantId,o))),await y.delete(s).where(C(S(s.groupId,m),S(s.tenantId,o)))}),h(o)},async setSyncEnabled(o,m,y,w){P(o,"setSyncEnabled(tenantId)"),await a(),await d.update(s).set({isSyncEnabled:y,updatedAt:new Date,updatedBy:w}).output().where(C(S(s.groupId,m),S(s.tenantId,o)))},async requestSync(o,m,y){return P(o,"requestSync(tenantId)"),await a(),d.transaction(async w=>{if(m!==null&&await T(w,o,m),(await w.select({id:g.id}).from(g).where(C(S(g.tenantId,o),m===null?ie(g.groupId):S(g.groupId,m),ie(g.pickedAt)))).length>0)return"already-pending";try{await w.insert(g).output().values({tenantId:o,groupId:m,requestedBy:y,requestedAt:new Date,createdAt:new Date,createdBy:y})}catch(x){if(!rr(x))throw x;return"already-pending"}return"queued"})},async getStatus(o){P(o,"getStatus(tenantId)"),await a();let m=await d.select({groupId:s.groupId,isSyncEnabled:s.isSyncEnabled,syncedAt:s.syncedAt,syncError:s.syncError}).from(s).where(S(s.tenantId,o)),[y]=await d.select({n:Pe()}).from(g).where(C(S(g.tenantId,o),ie(g.finishedAt))),w=m.filter(G=>!!G.isSyncEnabled),k=w.map(G=>G.syncedAt?Ge(G.syncedAt).getTime():null),x=k.length>0&&k.every(G=>G!==null)?Math.min(...k):null,U=m.length>0&&w.length===0||w.length>0&&(x===null||Date.now()-x>u.stalenessThresholdSec*1e3);return{isEnabled:u.isEnabled,provider:u.provider,linkedGroupCount:m.length,...x!==null?{oldestSyncedAt:new Date(x).toISOString()}:{},isStale:U,pausedGroupCount:m.length-w.length,stalenessThresholdSec:u.stalenessThresholdSec,erroredGroupIds:m.filter(G=>!!G.syncError).map(G=>Number(G.groupId)).sort((G,j)=>G-j),pendingRequestCount:Number(y?.n??0)}},async loadTenantMembershipPolicy(o){P(o,"loadTenantMembershipPolicy(tenantId)");let m=v>0?f.get(o):void 0;if(m&&Date.now()-m.loadedAt<v)return m.policy;await a();let y=await d.select({groupId:i.groupId,isBreakGlass:i.isBreakGlass,externalLinkId:s.id}).from(i).innerJoin(l,C(S(l.id,i.groupId),S(l.tenantId,o))).leftJoin(s,C(S(s.groupId,i.groupId),S(s.tenantId,o))).where(S(i.tenantId,o));if(y.length===0){let E={kind:"no-proof-required"};return v>0&&f.set(o,{policy:E,loadedAt:Date.now()}),E}let w=y.map(E=>Number(E.groupId)),k=y.filter(E=>!!E.isBreakGlass);for(let E of k)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 x={kind:"require-proof",proofGroupIds:w,breakGlassGroupIds:k.map(E=>Number(E.groupId))};return Xe(x,`tenant ${o}`),v>0&&f.set(o,{policy:x,loadedAt:Date.now()}),x},async setTenantMembershipGroup(o,m,y,w){P(o,"setTenantMembershipGroup(tenantId)"),await a(),await p(async k=>{if(await T(k,o,m),y){let[E]=await k.select({id:s.id}).from(s).where(C(S(s.tenantId,o),S(s.groupId,m)));if(E)throw new Error(`Group ${m} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`)}let x=await k.select({groupId:i.groupId}).from(i).where(C(S(i.tenantId,o),S(i.groupId,m)));if(!y&&!(await k.select({groupId:i.groupId,isBreakGlass:i.isBreakGlass}).from(i).where(S(i.tenantId,o))).filter(G=>Number(G.groupId)!==m).some(G=>!!G.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(x.length>0){await k.update(i).set({isBreakGlass:y,updatedAt:new Date,updatedBy:w}).output().where(C(S(i.tenantId,o),S(i.groupId,m)));return}await k.insert(i).output().values({tenantId:o,groupId:m,isBreakGlass:y,createdAt:new Date,createdBy:w})}),h(o)},async removeTenantMembershipGroup(o,m){P(o,"removeTenantMembershipGroup(tenantId)"),await a(),await p(async y=>{let k=(await y.select({groupId:i.groupId,isBreakGlass:i.isBreakGlass}).from(i).where(S(i.tenantId,o))).filter(x=>Number(x.groupId)!==m);if(k.length>0&&!k.some(x=>!!x.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(C(S(i.tenantId,o),S(i.groupId,m)))}),h(o)},async setGroupActive(o,m,y,w){P(o,"setGroupActive(tenantId)"),await a(),await d.update(l).set({isActive:y,updatedAt:new Date,updatedBy:w}).output().where(C(S(l.id,m),S(l.tenantId,o)))},async listLedgerGroupIds(o,m){if(P(o,"listLedgerGroupIds(tenantId)"),!u.isEnabled)return[];await a();let y=await oe(d,{ledger:t,group:l},{tenantId:o,externalUserId:m.externalUserId?.trim()||null,email:Z(m.email)});return[...new Set(y.map(w=>w.groupId))].sort((w,k)=>w-k)},async isEmailAllowedByGroup(o){if(!u.isEnabled)return!1;let m=Z(o);if(m==="")return!1;await a();let[y]=await d.select({id:t.id}).from(t).innerJoin(r,S(r.groupId,t.groupId)).innerJoin(l,C(S(l.id,t.groupId),S(l.isActive,!0))).where(S(t.email,m));return!!y},async listAllowlistGroups(o){return P(o,"listAllowlistGroups(tenantId)"),await a(),(await d.select({id:r.id,groupId:r.groupId,groupKey:l.groupKey,groupName:l.name,description:r.description}).from(r).innerJoin(l,C(S(l.id,r.groupId),S(l.tenantId,o))).where(S(r.tenantId,o)).orderBy(Ce(l.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,w){P(o,"addAllowlistGroup(tenantId)"),await a(),await p(async k=>{let x=await T(k,o,m),[E]=await k.select({id:s.id}).from(s).where(C(S(s.groupId,m),S(s.tenantId,o)));if(!E)throw new K(u.refusals.allowlistNeedsLink(x));await k.insert(r).output().values({tenantId:o,groupId:m,description:y??null,createdAt:new Date,createdBy:w})})},async removeAllowlistGroup(o,m){P(o,"removeAllowlistGroup(tenantId)"),await a(),await d.delete(r).where(C(S(r.id,m),S(r.tenantId,o)))},async countProjectedMembers(o,m){P(o,"countProjectedMembers(tenantId)"),await a();let[y]=await d.select({n:Pe()}).from(I).innerJoin(l,C(S(l.id,I.groupId),S(l.tenantId,o))).where(S(I.groupId,m));return Number(y?.n??0)}}}import{normalizeAuthEmail as ce}from"@aiquants/auth-core";import{chunk as pe}from"@aiquants/mssql";import{and as tr,eq as _e,inArray as me}from"drizzle-orm";function ye(e,n,c){let u=e.get(n);u===void 0?e.set(n,new Set([c])):u.add(c)}function ge(e){let[n,...c]=e;if(n===void 0||c.length>0)throw new Error(`[@aiquants/auth-directory-drizzle] expected exactly one value, received ${e.size}`);return n}function it(e,n,c){let u=e,d=c.ensureConnection??(async()=>{}),{users:a,subjects:p}=n;return{async resolve(v){if(v.length===0)return[];await d();let f=[...new Set(v.map(b=>b.externalUserId?.trim()).filter(b=>!!b))],h=[...new Set(v.map(b=>ce(b.email)).filter(b=>b!==""))],s=new Map,t=new Set;for(let b of pe(f)){let T=await u.select({subject:p.subject,userId:p.userId,mappingActive:p.isActive,userActive:a.isActive}).from(p.table).innerJoin(a.table,_e(a.id,p.userId)).where(me(p.subject,b));for(let A of T){let D=String(A.subject);t.add(D),A.mappingActive&&A.userActive&&ye(s,D,Number(A.userId))}}let i=new Map,r=new Set;for(let b of pe(h)){let T=await u.select({id:a.id,email:a.email,isActive:a.isActive}).from(a.table).where(me(a.email,b));for(let A of T){let D=ce(String(A.email));r.add(D),A.isActive&&ye(i,D,Number(A.id))}}let g=[...i.values()].filter(b=>b.size===1).map(ge),l=new Map;for(let b of pe([...new Set(g)])){let T=await u.select({userId:p.userId,subject:p.subject}).from(p.table).where(tr(me(p.userId,b),_e(p.isActive,!0)));for(let A of T)ye(l,Number(A.userId),String(A.subject))}let I=b=>{let T=w=>({...b,resolvedUserId:w,unresolvedReason:null}),A=w=>({...b,resolvedUserId:null,unresolvedReason:w}),D=b.externalUserId?.trim(),o=ce(b.email),m=D?s.get(D):void 0;if(m!==void 0)return m.size===1?T(ge(m)):A("ambiguous");let y=i.get(o);if(y!==void 0){if(y.size>1)return A("ambiguous");let w=ge(y),k=l.get(w);return D&&k!==void 0&&!k.has(D)?A("claimedByAnother"):T(w)}return D&&t.has(D)||r.has(o)?A("deactivated"):A("unregistered")};return v.map(I)}}}import{normalizeAuthEmail as nr}from"@aiquants/auth-core";import{assertTenantId as or,assertTenantMatch as ir}from"@aiquants/authz-core";async function dt(e,n){let c=or(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 u=nr(n.email),d=n.externalUserId?.trim()||null;if(d===null&&u==="")return{addedGroupIds:[],matchedLedgerIds:[]};let a=await e.findLedgerMatches({tenantId:c,externalUserId:d,email:u});if(a.length===0)return{addedGroupIds:[],matchedLedgerIds:[]};for(let h of a)ir(c,h.tenantId,"promoteLedgerForUser(findLedgerMatches row)");let p=[...new Set(a.map(h=>h.ledgerId))].sort((h,s)=>h-s),v=[...new Set(a.map(h=>h.groupId))].sort((h,s)=>h-s);return{addedGroupIds:[...await e.attachUserToGroups({tenantId:c,userId:n.userId,ledgerIds:p,groupIds:v})].sort((h,s)=>h-s),matchedLedgerIds:p}}import{assertTenantId as Le}from"@aiquants/authz-core";import{chunk as he,chunkByParameters as ar}from"@aiquants/mssql";import{inJsonList as sr}from"@aiquants/mssql/drizzle";import{and as be,eq as fe,inArray as we}from"drizzle-orm";var ur=4;function bt(e,n,c,u){let d=e,a=u.ensureConnection??(async()=>{}),p=N(d,u),v=u.now??(()=>new Date),f=n.TDGroupMemberEmail,{TMGroup:h,TDGroupMember:s}=c;return{async findLedgerMatches(t){return Le(t.tenantId,"findLedgerMatches(tenantId)"),await a(),oe(d,{ledger:f,group:h},t)},async attachUserToGroups({tenantId:t,userId:i,ledgerIds:r,groupIds:g}){return Le(t,"attachUserToGroups(tenantId)"),await a(),r.length===0?[]:p(async l=>{let I=new Set;for(let D of he(g)){let o=await l.select({id:h.id}).from(h).where(be(we(h.id,D),fe(h.tenantId,t),fe(h.isActive,!0)));for(let m of o)I.add(Number(m.id))}let b=g.filter(D=>I.has(D));if(b.length===0)return[];let T=new Set;for(let D of he(b)){let o=await l.select({groupId:s.groupId}).from(s).where(be(fe(s.userId,i),we(s.groupId,D)));for(let m of o)T.add(Number(m.groupId))}let A=b.filter(D=>!T.has(D));for(let D of he(r))await l.update(f).set({resolvedUserId:i}).output().where(be(we(f.id,D),sr(f.groupId,b)));if(A.length>0){let D=v();for(let o of ar(A,ur))await l.insert(s).output().values(o.map(m=>({groupId:m,userId:i,createdAt:D,createdBy:u.actor})))}return A})}}}import{AUTHZ_TENANT_COLUMN as B}from"@aiquants/authz-drizzle";import{bigint as _,bit as Ke,datetime2 as O,foreignKey as ae,int as Ie,mssqlSchema as dr,nvarchar as R,primaryKey as z,uniqueIndex as F}from"drizzle-orm/mssql-core";var ve="provider",vt=["TDGroupExternalLink","TDTenantMembershipGroup","TDAllowlistGroup","TDDirectorySyncRequest","TDDirectoryCatalog","TMDirectoryCatalogObservation","TMAllowlist"];function St(e,n){let c=dr(e),u=()=>({createdAt:O("created_at"),createdBy:R("created_by",{length:50}),updatedAt:O("updated_at"),updatedBy:R("updated_by",{length:50})}),d=()=>({createdAt:O("created_at"),createdBy:R("created_by",{length:50})}),a=c.table("TDGroupExternalLink",{id:_("id",{mode:"number"}).identity().notNull(),groupId:_("group_id",{mode:"number"}).notNull(),tenantId:R(B,{length:64}).notNull(),provider:R(ve,{length:32}).notNull(),externalId:R("external_id",{length:256}).notNull(),externalKey:R("external_key",{length:320}).notNull(),membershipMode:R("membership_mode",{length:16}).notNull(),isSyncEnabled:Ke("is_sync_enabled").notNull(),syncedAt:O("synced_at"),syncError:R("sync_error",{length:1e3}),vanishedObservations:Ie("vanished_observations").notNull(),...u()},r=>[z({name:`${e}_TDGroupExternalLink_pk`,columns:[r.id]}),F(`${e}_TDGroupExternalLink_group_uq`).on(r.groupId),F(`${e}_TDGroupExternalLink_external_uq`).on(r.tenantId,r.provider,r.externalId),ae({name:`${e}_TDGroupExternalLink_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),p=c.table("TDGroupMemberEmail",{id:_("id",{mode:"number"}).identity().notNull(),groupId:_("group_id",{mode:"number"}).notNull(),email:R("email",{length:320}).notNull(),externalUserId:R("external_user_id",{length:128}),resolvedUserId:_("resolved_user_id",{mode:"number"}),observedAt:O("observed_at").notNull(),...u()},r=>[z({name:`${e}_TDGroupMemberEmail_pk`,columns:[r.id]}),F(`${e}_TDGroupMemberEmail_group_email_uq`).on(r.groupId,r.email),ae({name:`${e}_TDGroupMemberEmail_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),v=c.table("TDTenantMembershipGroup",{tenantId:R(B,{length:64}).notNull(),groupId:_("group_id",{mode:"number"}).notNull(),isBreakGlass:Ke("is_break_glass").notNull(),...d()},r=>[z({name:`${e}_TDTenantMembershipGroup_pk`,columns:[r.tenantId,r.groupId]}),ae({name:`${e}_TDTenantMembershipGroup_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),f=c.table("TDAllowlistGroup",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:R(B,{length:64}).notNull(),groupId:_("group_id",{mode:"number"}).notNull(),description:R("description",{length:512}),...d()},r=>[z({name:`${e}_TDAllowlistGroup_pk`,columns:[r.id]}),F(`${e}_TDAllowlistGroup_tenant_group_uq`).on(r.tenantId,r.groupId),ae({name:`${e}_TDAllowlistGroup_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),h=c.table("TDDirectorySyncRequest",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:R(B,{length:64}).notNull(),groupId:_("group_id",{mode:"number"}),requestedBy:R("requested_by",{length:320}).notNull(),requestedAt:O("requested_at").notNull(),pickedAt:O("picked_at"),finishedAt:O("finished_at"),result:R("result",{length:32}),error:R("error",{length:2e3}),...d()},r=>[z({name:`${e}_TDDirectorySyncRequest_pk`,columns:[r.id]})]),s=c.table("TMAllowlist",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:R(B,{length:64}).notNull(),pattern:R("pattern",{length:256}).notNull(),type:R("type",{length:32}).notNull(),description:R("description",{length:512}),...d()},r=>[z({name:`${e}_TMAllowlist_pk`,columns:[r.id]}),F(`${e}_TMAllowlist_pattern_uq`).on(r.tenantId,r.pattern)]),t=c.table("TDDirectoryCatalog",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:R(B,{length:64}).notNull(),provider:R(ve,{length:32}).notNull(),externalId:R("external_id",{length:256}).notNull(),externalKey:R("external_key",{length:320}).notNull(),name:R("name",{length:256}).notNull(),description:R("description",{length:1e3}),memberCount:Ie("member_count"),observedAt:O("observed_at").notNull(),...d()},r=>[z({name:`${e}_TDDirectoryCatalog_pk`,columns:[r.id]}),F(`${e}_TDDirectoryCatalog_external_uq`).on(r.tenantId,r.provider,r.externalId)]),i=c.table("TMDirectoryCatalogObservation",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:R(B,{length:64}).notNull(),provider:R(ve,{length:32}).notNull(),observedAt:O("observed_at").notNull(),entryCount:Ie("entry_count").notNull(),...u()},r=>[z({name:`${e}_TMDirectoryCatalogObservation_pk`,columns:[r.id]}),F(`${e}_TMDirectoryCatalogObservation_target_uq`).on(r.tenantId,r.provider)]);return{TDGroupExternalLink:a,TDGroupMemberEmail:p,TDTenantMembershipGroup:v,TDAllowlistGroup:f,TDDirectorySyncRequest:h,TDDirectoryCatalog:t,TMDirectoryCatalogObservation:i,TMAllowlist:s}}import{AUTH_DIRECTORY_UNRESOLVED_REASONS as lr,normalizeAuthEmail as cr}from"@aiquants/auth-core";import{assertBlastRadiusPolicy as pr,reconcile as mr}from"@aiquants/auth-directory-core";function se(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 Oe=e=>`${e.length} \u4EF6`,Se={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"},Ue={upstreamUnreadable:e=>`\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${Se[e]})\u3002`,localWriteFailed:e=>`\u30ED\u30FC\u30AB\u30EB\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${Se[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,c)=>`\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${c} \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 ${Oe(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",c=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: ${Oe(c)} (\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=>Se[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 De=["aborted","missing","vanished","upstream-failed","local-failed","foreign-provider","not-converged"],$e=[...De,"already-vanished"];function yr(e){let n=new Map;for(let d of e)d.resolvedUserId===null&&n.set(cr(d.email),d.unresolvedReason);let c=[...n.values()],u=lr.map(d=>[d,c.filter(a=>a===d).length]).filter(([,d])=>d>0).map(([d,a])=>`${d}: ${a}`).join(", ");return{total:n.size,breakdown:u}}function Et(e){let{directory:n,repository:c,policy:u,now:d,logger:a}=e,p=e.messages??Ue;if(pr(u),!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 c.recordFailure(t,i)}catch(r){a.error(`[auth-directory] could not record the failure for ${t.externalKey}:`,r)}}async function f(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 h(t){if(t.vanishedObservations>=e.vanishedThreshold&&!t.isGroupActive)return a.warn(`[auth-directory] ${t.externalKey} still missing upstream (already deactivated after ${t.vanishedObservations} absences)`),{kind:"already-vanished",link:t};let i=await c.recordVanishedObservation(t,p.groupMissing(t.externalKey,t.vanishedObservations+1,e.vanishedThreshold));return i<e.vanishedThreshold?(a.warn(`[auth-directory] ${t.externalKey} missing upstream (${i}/${e.vanishedThreshold})`),{kind:"missing",link:t,observations:i}):(await c.markGroupInactive(t),await v(t,p.groupVanished(t.externalKey,i)),a.warn(`[auth-directory] ${t.externalKey} deactivated after ${i} consecutive absences`),{kind:"vanished",link:t})}async function s(t){if(t.provider!==e.provider)return await v(t,p.foreignProvider(t.provider,e.provider)),a.warn(`[auth-directory] skipping ${t.externalKey}: belongs to provider ${t.provider}`),{kind:"foreign-provider",link:t};let i=await f(t);if(i.kind==="missing")return h(t);if(i.kind==="failed")return await v(t,p.upstreamUnreadable(se(i.error))),a.error(`[auth-directory] upstream read failed for ${t.externalKey}:`,i.error),{kind:"upstream-failed",link:t,error:i.error};try{await c.resolveExternalIdentity(t,i.group);let[r,g]=await Promise.all([c.loadGroupSnapshot(t),c.resolveMembers(i.members)]);if(g.length!==i.members.length){let D=p.resolverIncomplete(i.members.length,g.length);return await v(t,D),a.error(`[auth-directory] ${D}`),{kind:"local-failed",link:t,error:new Error(D)}}let l=mr(r,g,u);if(l.aborted){await v(t,p.planRefused(l.aborted));let D=l.aborted.reason==="invalid-upstream-data"?l.aborted.offendingEmails:l.aborted.axis==="membership"?l.aborted.withheldProjectionRemoves.map(String):l.aborted.withheldLedgerDeletes;return a.warn(`[auth-directory] plan refused for ${t.externalKey}: ${l.aborted.reason}; withheld: ${D.join(", ")}`),{kind:"aborted",link:t,plan:l}}let I=l.ledgerUpserts.length>0||l.ledgerDeletes.length>0||l.projectionAdds.length>0||l.projectionRemoves.length>0,b=d(),T={refusals:[],failures:[]};I&&(T=await c.applyPlan(t,l,b)),await c.recordSuccess(t,i.group,b),T.refusals.length>0&&(await v(t,p.removalsRefused(T.refusals)),a.warn(`[auth-directory] ${T.refusals.length} removal(s) refused for ${t.externalKey}: users ${T.refusals.map(D=>D.userId).join(", ")}`)),T.failures.length>0&&(await v(t,p.removalWritesFailed(T.failures)),a.warn(`[auth-directory] ${T.failures.length} removal(s) could not be written for ${t.externalKey}: users ${T.failures.map(D=>D.userId).join(", ")}`));let A=yr(g);return A.total>0&&a.info(`[auth-directory] ${t.externalKey}: ${A.total} upstream member(s) are not linked to a local user (${A.breakdown})`),T.refusals.length>0||T.failures.length>0?{kind:"not-converged",link:t,plan:l,refusals:T.refusals,failures:T.failures}:I?{kind:"applied",link:t,plan:l}:{kind:"unchanged",link:t}}catch(r){let g=r instanceof Error?r:new Error(String(r));return await v(t,p.localWriteFailed("localWriteError")),a.error(`[auth-directory] local write failed for ${t.externalKey}:`,g),{kind:"local-failed",link:t,error:g}}}return{syncOne:s,async run(t){let i=await c.listSyncableLinks(t),r=[];for(let I of i)try{r.push(await s(I))}catch(b){let T=b instanceof Error?b:new Error(String(b));a.error(`[auth-directory] ${I.externalKey} ended abnormally:`,T),r.push({kind:"local-failed",link:I,error:T})}let g=r.filter(I=>$e.includes(I.kind)).length,l=r.filter(I=>De.includes(I.kind)).length;return a.info(`[auth-directory] run finished: ${r.length} link(s), ${g} unhealthy (${l} new)`),{outcomes:r,unhealthyCount:g,newlyUnhealthyCount:l}}}}import{assertTenantId as gr}from"@aiquants/authz-core";import{toInstant as hr}from"@aiquants/mssql";import{and as ue,asc as Ne,eq as H,isNull as de}from"drizzle-orm";var br="The job that claimed this request ended without recording an outcome; whether the synchronization ran is unknown.",Ae=2e3;function Lt(e,n,c){let u=e,d=c.ensureConnection??(async()=>{}),a=n.TDDirectorySyncRequest,p=gr(c.tenantId,"createSyncRequestQueue(tenantId)"),v=c.maxErrorLength??Ae;if(!Number.isInteger(v)||v<1||v>Ae)throw new TypeError(`[@aiquants/auth-directory-drizzle] maxErrorLength must be an integer within 1..${Ae}, received ${String(c.maxErrorLength)}`);let f={async claim(){await d();let h=await u.select({id:a.id}).from(a).where(ue(H(a.tenantId,p),de(a.pickedAt))).orderBy(Ne(a.id));for(let s of h){let i=(await u.update(a).set({pickedAt:c.now()}).output().where(ue(H(a.tenantId,p),H(a.id,Number(s.id)),de(a.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:p,groupId:r===null?null:Number(r),requestedBy:String(i.requestedBy)}}return null},async finish(h,s){await d();let t=s.error?s.error.slice(0,v):null;return!!(await u.update(a).set({finishedAt:c.now(),result:s.result,error:t}).output().where(ue(H(a.tenantId,p),H(a.id,h),de(a.finishedAt))))?.length},async listStranded(h){await d();let s=await u.select({id:a.id,pickedAt:a.pickedAt,requestedAt:a.requestedAt}).from(a).where(ue(H(a.tenantId,p),de(a.finishedAt))).orderBy(Ne(a.id)),t=i=>{let r=hr(i);return Number.isNaN(r.getTime())||r<h};return{stranded:s.filter(i=>i.pickedAt!==null&&t(i.pickedAt)).map(i=>Number(i.id)),unclaimed:s.filter(i=>i.pickedAt===null&&t(i.requestedAt)).map(i=>Number(i.id))}},async abandonStranded(h){let{stranded:s}=await f.listStranded(h),t=[];for(let i of s)try{await f.finish(i,{result:"failed",error:br})&&t.push(i)}catch(r){c.onCloseFailed?.(i,r)}return t}};return f}import{normalizeAuthEmail as le}from"@aiquants/auth-core";import{assertTenantId as qe,assertTenantMatch as fr}from"@aiquants/authz-core";import{chunk as ze,chunkByParameters as Be}from"@aiquants/mssql";import{notInJsonList as wr}from"@aiquants/mssql/drizzle";import{and as L,eq as M,inArray as Te,isNull as Ir,or as vr}from"drizzle-orm";var Sr=7,Dr=4,Fe=1e3;function Ar(e){if(!(e instanceof Error))return!1;let n=e;return n.number===void 0&&n.code===void 0&&e.name==="Error"}function jt(e,n,c,u){let d=e,a=u.ensureConnection??(async()=>{}),p=N(d,u),v=u.now??(()=>new Date),{TDGroupExternalLink:f,TDGroupMemberEmail:h}=n,{TMGroup:s,TDGroupMember:t}=c,i=u.actor;return{async listSyncableLinks(r){await a();let g=qe(r.tenantId,"listSyncableLinks(tenantId)"),l=[M(f.isSyncEnabled,!0),M(f.tenantId,g)];return r.groupId!==void 0&&l.push(M(f.groupId,r.groupId)),(await d.select({linkId:f.id,groupId:f.groupId,linkTenantId:f.tenantId,groupTenantId:s.tenantId,groupKey:s.groupKey,isGroupActive:s.isActive,provider:f.provider,externalId:f.externalId,externalKey:f.externalKey,membershipMode:f.membershipMode,vanishedObservations:f.vanishedObservations}).from(f).innerJoin(s,L(M(s.id,f.groupId),M(s.tenantId,g))).where(L(...l))).map(b=>(fr(String(b.groupTenantId),String(b.linkTenantId),"listSyncableLinks(link.tenantId vs TMGroup.tenant_id)"),{linkId:Number(b.linkId),groupId:Number(b.groupId),groupKey:String(b.groupKey),tenantId:String(b.groupTenantId),provider:String(b.provider),externalId:String(b.externalId),externalKey:String(b.externalKey),membershipMode:Q(String(b.membershipMode)),isGroupActive:!!b.isGroupActive,vanishedObservations:Number(b.vanishedObservations??0)}))},async loadGroupSnapshot(r){await a();let[g,l]=await Promise.all([d.select({email:h.email,externalUserId:h.externalUserId,resolvedUserId:h.resolvedUserId}).from(h).where(M(h.groupId,r.groupId)),d.select({userId:t.userId}).from(t).where(M(t.groupId,r.groupId))]);return{ledger:g.map(I=>({email:String(I.email),...I.externalUserId?{externalUserId:String(I.externalUserId)}:{},resolvedUserId:I.resolvedUserId===null?null:Number(I.resolvedUserId)})),projectedUserIds:l.map(I=>Number(I.userId))}},resolveMembers(r){return u.memberResolver.resolve(r)},async applyPlan(r,g,l){qe(r.tenantId,"applyPlan(link.tenantId)"),await a(),(g.ledgerUpserts.length>0||g.projectionAdds.length>0)&&await p(async A=>{let D=g.ledgerUpserts.map(y=>({...y,email:le(y.email)})),o=new Map;for(let y of ze(D.map(w=>w.email))){let w=await A.select({id:h.id,email:h.email}).from(h).where(L(M(h.groupId,r.groupId),Te(h.email,y)));for(let k of w)o.set(String(k.email),Number(k.id))}let m=D.filter(y=>!o.has(y.email));for(let y of Be(m,Sr))await A.insert(h).output().values(y.map(w=>({groupId:r.groupId,email:w.email,externalUserId:w.externalUserId??null,resolvedUserId:w.resolvedUserId,observedAt:l,createdAt:l,createdBy:i})));for(let y of D){let w=o.get(y.email);w!==void 0&&await A.update(h).set({externalUserId:y.externalUserId??null,resolvedUserId:y.resolvedUserId,observedAt:l,updatedAt:l,updatedBy:i}).output().where(M(h.id,w))}for(let y of Be(g.projectionAdds,Dr)){let w=new Set((await A.select({userId:t.userId}).from(t).where(L(M(t.groupId,r.groupId),Te(t.userId,y)))).map(x=>Number(x.userId))),k=y.filter(x=>!w.has(x));k.length>0&&await A.insert(t).output().values(k.map(x=>({groupId:r.groupId,userId:x,createdAt:l,createdBy:i})))}});let I=[],b=[];for(let A of g.projectionRemoves)try{await u.lockoutGuard.guardedWrite(r.tenantId,{removeGroupMembership:{groupId:r.groupId,userId:A}},"remove-group-member",D=>D.delete(t).where(L(M(t.groupId,r.groupId),M(t.userId,A))))}catch(D){Ar(D)?I.push({userId:A,applied:!1,refusal:D.message}):b.push({userId:A,cause:se(D)})}let T=I.map(A=>A.userId);return g.ledgerDeletes.length>0&&await p(async A=>{for(let D of ze(g.ledgerDeletes)){let o=T.length>0?vr(Ir(h.resolvedUserId),wr(h.resolvedUserId,T)):void 0;await A.delete(h).where(L(M(h.groupId,r.groupId),Te(h.email,D),...o?[o]:[]))}}),{refusals:I,failures:b}},async resolveExternalIdentity(r,g){await a(),!(r.externalId===g.externalId&&r.externalKey===le(g.externalKey))&&await d.update(f).set({externalId:g.externalId,externalKey:le(g.externalKey)}).output().where(L(M(f.id,r.linkId),M(f.tenantId,r.tenantId)))},async recordSuccess(r,g,l){await a(),await p(async I=>{await I.update(f).set({syncedAt:l,syncError:null,vanishedObservations:0,externalId:g.externalId,externalKey:le(g.externalKey),updatedAt:l,updatedBy:i}).output().where(L(M(f.id,r.linkId),M(f.tenantId,r.tenantId))),r.isGroupActive||await I.update(s).set({isActive:!0,updatedAt:l,updatedBy:i}).output().where(L(M(s.id,r.groupId),M(s.tenantId,r.tenantId)))})},async recordFailure(r,g){await a(),await d.update(f).set({syncError:g.slice(0,Fe),updatedAt:v(),updatedBy:i}).output().where(L(M(f.id,r.linkId),M(f.tenantId,r.tenantId)))},async recordVanishedObservation(r,g){await a();let l=r.vanishedObservations+1;return await d.update(f).set({vanishedObservations:l,syncError:g.slice(0,Fe),updatedAt:v(),updatedBy:i}).output().where(L(M(f.id,r.linkId),M(f.tenantId,r.tenantId))),l},async markGroupInactive(r){await a(),await d.update(s).set({isActive:!1,updatedAt:v(),updatedBy:i}).output().where(L(M(s.id,r.groupId),M(s.tenantId,r.tenantId),M(s.isActive,!0)))}}}export{ve as AUTH_DIRECTORY_PROVIDER_COLUMN,vt as AUTH_DIRECTORY_TENANT_SCOPED_TABLES,K as DirectoryOperatorError,Me as MEMBERSHIP_MODES,De as NEWLY_UNHEALTHY_SYNC_OUTCOMES,br as STRANDED_REASON,$e as UNHEALTHY_SYNC_OUTCOMES,$r as assertGroupMayBeLinked,Q as assertMembershipMode,se as classifySyncFailure,Cr as createDirectoryCatalogReader,Gr as createDirectoryCatalogWriter,et as createDirectoryLinkStore,Et as createDirectorySync,jt as createDirectorySyncRepository,it as createOpenidMemberResolver,bt as createPromotionRepository,Lt as createSyncRequestQueue,Xr as defaultDirectoryLinkRefusals,Or as defaultExternalGroupGuardMessage,Ue as defaultSyncMessages,St as defineAuthDirectorySchema,oe as findLedgerRowsForIdentity,dt as promoteLedgerForUser,N as transactionRunner,Ur as withExternalGroupGuards};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiquants/auth-directory-drizzle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
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,17 +32,17 @@
|
|
|
32
32
|
},
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@aiquants/auth-core": "^0.
|
|
36
|
-
"@aiquants/auth-directory-core": "^0.
|
|
35
|
+
"@aiquants/auth-core": "^0.7.0",
|
|
36
|
+
"@aiquants/auth-directory-core": "^0.3.0",
|
|
37
37
|
"@aiquants/authz-core": "^0.6.1",
|
|
38
38
|
"@aiquants/authz-drizzle": "^1.3.1",
|
|
39
39
|
"drizzle-orm": ">=1.0.0-beta.4 <2.0.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@aiquants/auth-core": "0.
|
|
43
|
-
"@aiquants/auth-directory-core": "0.
|
|
42
|
+
"@aiquants/auth-core": "0.7.0",
|
|
43
|
+
"@aiquants/auth-directory-core": "0.3.0",
|
|
44
44
|
"@aiquants/authz-core": "0.6.1",
|
|
45
|
-
"@aiquants/authz-drizzle": "1.
|
|
45
|
+
"@aiquants/authz-drizzle": "1.4.0",
|
|
46
46
|
"@types/node": "^25.9.1",
|
|
47
47
|
"drizzle-orm": "1.0.0-rc.4",
|
|
48
48
|
"rimraf": "^6.1.2",
|