@wtfalch/authz-store 0.2.0 → 0.3.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 +335 -5
- package/dist/activations.d.ts +60 -0
- package/dist/activations.js +516 -0
- package/dist/alerts.d.ts +86 -0
- package/dist/alerts.js +132 -0
- package/dist/assignments.d.ts +71 -0
- package/dist/assignments.js +103 -0
- package/dist/audit.d.ts +122 -0
- package/dist/audit.js +194 -0
- package/dist/binding.d.ts +17 -0
- package/dist/binding.js +8 -0
- package/dist/boot.d.ts +54 -0
- package/dist/boot.js +143 -0
- package/dist/bootstrap.d.ts +28 -0
- package/dist/bootstrap.js +83 -0
- package/dist/break-glass.d.ts +61 -0
- package/dist/break-glass.js +247 -0
- package/dist/credentials.d.ts +94 -0
- package/dist/credentials.js +230 -0
- package/dist/denial.d.ts +9 -0
- package/dist/denial.js +49 -0
- package/dist/erase.d.ts +58 -0
- package/dist/erase.js +108 -0
- package/dist/events.d.ts +77 -0
- package/dist/events.js +107 -0
- package/dist/export.d.ts +161 -0
- package/dist/export.js +293 -0
- package/dist/index.d.ts +33 -1
- package/dist/index.js +33 -1
- package/dist/install-owner.d.ts +25 -0
- package/dist/install-owner.js +159 -0
- package/dist/invitations.d.ts +160 -0
- package/dist/invitations.js +685 -0
- package/dist/membership-rows.d.ts +206 -0
- package/dist/membership-rows.js +271 -0
- package/dist/memberships.d.ts +87 -0
- package/dist/memberships.js +272 -0
- package/dist/migrate.js +15 -4
- package/dist/nesting.d.ts +124 -0
- package/dist/nesting.js +515 -0
- package/dist/owners.d.ts +6 -1
- package/dist/owners.js +7 -3
- package/dist/person-records.d.ts +186 -0
- package/dist/person-records.js +263 -0
- package/dist/platform.d.ts +20 -0
- package/dist/platform.js +65 -0
- package/dist/policy-access.d.ts +260 -0
- package/dist/policy-access.js +348 -0
- package/dist/policy-resources.d.ts +5 -0
- package/dist/policy-resources.js +46 -0
- package/dist/policy-schema.d.ts +445 -0
- package/dist/policy-schema.js +63 -0
- package/dist/policy.d.ts +64 -0
- package/dist/policy.js +65 -0
- package/dist/propagate.d.ts +43 -0
- package/dist/propagate.js +47 -0
- package/dist/reconcile.d.ts +78 -0
- package/dist/reconcile.js +94 -0
- package/dist/resource-access.d.ts +344 -0
- package/dist/resource-access.js +656 -0
- package/dist/role-keys.d.ts +9 -0
- package/dist/role-keys.js +9 -0
- package/dist/roles.d.ts +36 -0
- package/dist/roles.js +191 -0
- package/dist/schema.d.ts +18 -1
- package/dist/schema.js +8 -1
- package/dist/startup.d.ts +57 -0
- package/dist/startup.js +113 -0
- package/dist/tenants.d.ts +213 -0
- package/dist/tenants.js +808 -0
- package/dist/tree-writes.d.ts +65 -0
- package/dist/tree-writes.js +201 -0
- package/dist/tree.d.ts +272 -0
- package/dist/tree.js +565 -0
- package/dist/types.d.ts +87 -0
- package/dist/types.js +15 -0
- package/migrations/0003_product_tenant_kind.sql +14 -0
- package/migrations/0004_credential_keys_issued_id.sql +33 -0
- package/migrations/0005_activations.sql +71 -0
- package/migrations/0006_erase_person.sql +134 -0
- package/package.json +9 -4
package/dist/events.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { and, desc, eq, inArray, isNull, lt, or, sql } from 'drizzle-orm';
|
|
2
|
+
import { record } from './audit.js';
|
|
3
|
+
import { auditedReadGate, permitsPlatform } from './policy-access.js';
|
|
4
|
+
import { authzEvents } from './schema.js';
|
|
5
|
+
/**
|
|
6
|
+
* One page of the trail, or `null` when this `Access` may not read it.
|
|
7
|
+
*
|
|
8
|
+
* **`null`, never an empty page**, for the reason `membersOf` gives: a caller
|
|
9
|
+
* that cannot tell "you may not see this" from "there is nothing to see"
|
|
10
|
+
* draws a false conclusion, and here the false conclusion is "nothing has
|
|
11
|
+
* happened in this organisation", which is the opposite of what an audit
|
|
12
|
+
* surface exists to say.
|
|
13
|
+
*
|
|
14
|
+
* Two permissions, and the difference is not cosmetic. `audit:read` reads
|
|
15
|
+
* one's own tenant. `audit:read-all` reads every tenant, and only it sees
|
|
16
|
+
* rows with `tenant_visible = false` — the operator-only rows, which is where
|
|
17
|
+
* a break-glass session into somebody's organisation is recorded. A tenant
|
|
18
|
+
* admin reading their own log must not be shown those, or the support session
|
|
19
|
+
* they were never told about becomes visible in a list they can already open.
|
|
20
|
+
* So the flag is applied here, not left to the page: a page that forgot it
|
|
21
|
+
* would leak by omission, and this is the only place that knows both the
|
|
22
|
+
* actor's permissions and the row.
|
|
23
|
+
*/
|
|
24
|
+
export async function eventsFor(access, db, options = {}) {
|
|
25
|
+
const all = permitsPlatform(access, 'platform.audit:read');
|
|
26
|
+
const clauses = access.authority
|
|
27
|
+
.clauses('platform.audit:read')
|
|
28
|
+
.filter((c) => c.applicationId === access.binding.applicationId &&
|
|
29
|
+
c.platformId === access.binding.platformId &&
|
|
30
|
+
c.resourceType === 'audit' &&
|
|
31
|
+
c.relation === 'any');
|
|
32
|
+
if (!(await auditedReadGate(access, db, 'platform.audit:read', clauses.length > 0)))
|
|
33
|
+
return null;
|
|
34
|
+
// A named tenant that is not one's own is an estate-wide read whatever the
|
|
35
|
+
// page thought it was asking for.
|
|
36
|
+
const scope = options.tenantId ?? (all ? null : access.tenant.id);
|
|
37
|
+
if (scope !== null && scope !== access.tenant.id && !all)
|
|
38
|
+
return null;
|
|
39
|
+
const limit = Math.min(options.limit ?? 50, 200);
|
|
40
|
+
const conditions = [
|
|
41
|
+
or(...clauses.map((c) => and(c.organisationId === null
|
|
42
|
+
? isNull(authzEvents.tenantId)
|
|
43
|
+
: eq(authzEvents.tenantId, c.organisationId), c.teamIds === null
|
|
44
|
+
? sql `true`
|
|
45
|
+
: c.teamIds.length
|
|
46
|
+
? inArray(authzEvents.teamId, [...c.teamIds])
|
|
47
|
+
: sql `false`, c.resourceId === undefined
|
|
48
|
+
? sql `true`
|
|
49
|
+
: /^\d{1,15}$/.test(c.resourceId)
|
|
50
|
+
? eq(authzEvents.id, Number(c.resourceId))
|
|
51
|
+
: sql `false`))) ?? sql `false`,
|
|
52
|
+
];
|
|
53
|
+
if (scope !== null)
|
|
54
|
+
conditions.push(eq(authzEvents.tenantId, scope));
|
|
55
|
+
if (!all)
|
|
56
|
+
conditions.push(eq(authzEvents.tenantVisible, true));
|
|
57
|
+
if (options.actorId)
|
|
58
|
+
conditions.push(eq(authzEvents.actorId, options.actorId));
|
|
59
|
+
if (options.action)
|
|
60
|
+
conditions.push(eq(authzEvents.action, options.action));
|
|
61
|
+
if (options.after) {
|
|
62
|
+
const { occurredAt, id } = options.after;
|
|
63
|
+
// Strictly older than the cursor. `id` breaks the tie because two rows
|
|
64
|
+
// can share a timestamp — `now()` is per-statement, so one transaction
|
|
65
|
+
// writing a membership and its event twice lands both in the same
|
|
66
|
+
// microsecond, and a cursor on time alone would skip or repeat one.
|
|
67
|
+
const keyset = or(lt(authzEvents.occurredAt, occurredAt), and(eq(authzEvents.occurredAt, occurredAt), lt(authzEvents.id, id)));
|
|
68
|
+
if (keyset)
|
|
69
|
+
conditions.push(keyset);
|
|
70
|
+
}
|
|
71
|
+
const rows = await db
|
|
72
|
+
.select({
|
|
73
|
+
id: authzEvents.id,
|
|
74
|
+
occurredAt: authzEvents.occurredAt,
|
|
75
|
+
tenantId: authzEvents.tenantId,
|
|
76
|
+
actorClass: authzEvents.actorClass,
|
|
77
|
+
actorId: authzEvents.actorId,
|
|
78
|
+
actorDisplay: authzEvents.actorDisplay,
|
|
79
|
+
action: authzEvents.action,
|
|
80
|
+
targetType: authzEvents.targetType,
|
|
81
|
+
targetId: authzEvents.targetId,
|
|
82
|
+
outcome: authzEvents.outcome,
|
|
83
|
+
context: authzEvents.context,
|
|
84
|
+
reason: authzEvents.reason,
|
|
85
|
+
reference: authzEvents.reference,
|
|
86
|
+
erasedAt: authzEvents.erasedAt,
|
|
87
|
+
})
|
|
88
|
+
.from(authzEvents)
|
|
89
|
+
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
90
|
+
.orderBy(desc(authzEvents.occurredAt), desc(authzEvents.id))
|
|
91
|
+
.limit(limit + 1);
|
|
92
|
+
const page = rows.slice(0, limit);
|
|
93
|
+
const last = page.at(-1);
|
|
94
|
+
const next = rows.length > limit && last ? { occurredAt: last.occurredAt, id: last.id } : null;
|
|
95
|
+
// A read of the audit trail is itself audited (0.9.0), like `securityLogFor`;
|
|
96
|
+
// an estate-wide read is a platform row.
|
|
97
|
+
await record(db, access, {
|
|
98
|
+
action: 'audit_log.viewed',
|
|
99
|
+
tenantId: scope,
|
|
100
|
+
targetType: 'audit',
|
|
101
|
+
targetId: scope ?? 'platform',
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
items: page.map(({ erasedAt, ...row }) => ({ ...row, erased: erasedAt !== null })),
|
|
105
|
+
next,
|
|
106
|
+
};
|
|
107
|
+
}
|
package/dist/export.d.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { type ActorClass } from '@wtfalch/authz';
|
|
2
|
+
import { policyAssignments, policyRoles } from './policy-schema.js';
|
|
3
|
+
import { accessTeamMembers, accessTeams } from './resource-schema.js';
|
|
4
|
+
import type { DbOrTx } from './scoped.js';
|
|
5
|
+
/**
|
|
6
|
+
* Who is exporting, for the `tenant.exported` row `exportTenant` writes on
|
|
7
|
+
* success. Not an `Access`: a caller with no signed-in session (a script
|
|
8
|
+
* running as its own service identity) has no `Access` to build one from,
|
|
9
|
+
* and a caller with a real session can still narrow to this shape. This
|
|
10
|
+
* package's own exports (`authzEventSchema`, `core`, `tenantVisibleByDefault`)
|
|
11
|
+
* are plain data/functions, so building the row here (rather than through
|
|
12
|
+
* `record`/`recordAs` from `./audit.js`, which expects a full `Access` or
|
|
13
|
+
* `Principal`) is what keeps this function free of that requirement.
|
|
14
|
+
*/
|
|
15
|
+
export interface ExportActor {
|
|
16
|
+
readonly class: ActorClass;
|
|
17
|
+
readonly id: string;
|
|
18
|
+
readonly display: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ExportedTenant {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly kind: string;
|
|
23
|
+
readonly slug: string;
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly state: string;
|
|
26
|
+
readonly parentId: string | null;
|
|
27
|
+
readonly ceiling: readonly string[];
|
|
28
|
+
readonly selfDenied: readonly string[];
|
|
29
|
+
readonly createdBy: string | null;
|
|
30
|
+
readonly createdAt: string;
|
|
31
|
+
readonly updatedAt: string;
|
|
32
|
+
}
|
|
33
|
+
export interface ExportedMembership {
|
|
34
|
+
readonly principalId: string;
|
|
35
|
+
readonly principalClass: string;
|
|
36
|
+
readonly source: string;
|
|
37
|
+
readonly viaTenantId: string | null;
|
|
38
|
+
readonly tenantName: string;
|
|
39
|
+
readonly grantedBy: string | null;
|
|
40
|
+
readonly createdAt: string;
|
|
41
|
+
readonly updatedAt: string;
|
|
42
|
+
}
|
|
43
|
+
/** Never `secretHash`: the column is not even named in the `select`, so it is never read out of the database in the first place. */
|
|
44
|
+
export interface ExportedCredential {
|
|
45
|
+
readonly id: string;
|
|
46
|
+
readonly kind: string;
|
|
47
|
+
readonly name: string;
|
|
48
|
+
readonly secretPrefix: string | null;
|
|
49
|
+
readonly createdBy: string | null;
|
|
50
|
+
readonly createdAt: string;
|
|
51
|
+
readonly expiresAt: string | null;
|
|
52
|
+
readonly revokedAt: string | null;
|
|
53
|
+
readonly lastUsedAt: string | null;
|
|
54
|
+
}
|
|
55
|
+
export type ExportedRole = Omit<typeof policyRoles.$inferSelect, 'createdAt' | 'updatedAt'> & {
|
|
56
|
+
createdAt: string;
|
|
57
|
+
updatedAt: string;
|
|
58
|
+
};
|
|
59
|
+
/** Never `tokenHash`, for the same reason as `ExportedCredential`. */
|
|
60
|
+
export interface ExportedInvitation {
|
|
61
|
+
readonly id: string;
|
|
62
|
+
readonly email: string;
|
|
63
|
+
readonly roleId: string;
|
|
64
|
+
readonly status: string;
|
|
65
|
+
readonly mailState: string;
|
|
66
|
+
readonly invitedBy: string | null;
|
|
67
|
+
readonly expiresAt: string;
|
|
68
|
+
readonly acceptedBy: string | null;
|
|
69
|
+
readonly acceptedAt: string | null;
|
|
70
|
+
readonly revokedBy: string | null;
|
|
71
|
+
readonly revokedAt: string | null;
|
|
72
|
+
readonly attachParentId: string | null;
|
|
73
|
+
readonly createdAt: string;
|
|
74
|
+
}
|
|
75
|
+
export interface ExportedAttachProposal {
|
|
76
|
+
readonly id: string;
|
|
77
|
+
readonly parentId: string;
|
|
78
|
+
readonly childId: string;
|
|
79
|
+
readonly status: string;
|
|
80
|
+
readonly reason: string;
|
|
81
|
+
readonly reference: string;
|
|
82
|
+
readonly proposedBy: string;
|
|
83
|
+
readonly parentAcceptedBy: string | null;
|
|
84
|
+
readonly parentAcceptedAt: string | null;
|
|
85
|
+
readonly decidedBy: string | null;
|
|
86
|
+
readonly decidedAt: string | null;
|
|
87
|
+
readonly createdAt: string;
|
|
88
|
+
}
|
|
89
|
+
export interface ExportedAuditRow {
|
|
90
|
+
readonly teamId: string | null;
|
|
91
|
+
readonly subjectId: string | null;
|
|
92
|
+
readonly subjectClass: string | null;
|
|
93
|
+
readonly id: number;
|
|
94
|
+
readonly occurredAt: string;
|
|
95
|
+
readonly tenantId: string | null;
|
|
96
|
+
readonly actorClass: string;
|
|
97
|
+
readonly actorId: string;
|
|
98
|
+
readonly actorDisplay: string;
|
|
99
|
+
readonly action: string;
|
|
100
|
+
readonly targetType: string;
|
|
101
|
+
readonly targetId: string;
|
|
102
|
+
readonly outcome: string;
|
|
103
|
+
readonly context: string;
|
|
104
|
+
readonly sessionId: string | null;
|
|
105
|
+
readonly reason: string | null;
|
|
106
|
+
readonly reference: string | null;
|
|
107
|
+
readonly requestId: string | null;
|
|
108
|
+
readonly ip: string | null;
|
|
109
|
+
readonly userAgent: string | null;
|
|
110
|
+
readonly tenantVisible: boolean;
|
|
111
|
+
readonly before: unknown;
|
|
112
|
+
readonly after: unknown;
|
|
113
|
+
readonly erasedAt: string | null;
|
|
114
|
+
readonly schemaVersion: number;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* A tenant's whole record, as a plain JSON-serialisable object (D9, D12,
|
|
118
|
+
* X6 M3). `null` for an id that does not resolve to a tenant, never an empty
|
|
119
|
+
* shell with every collection blank, so a caller cannot mistake "nothing to
|
|
120
|
+
* export" for "no such tenant".
|
|
121
|
+
*
|
|
122
|
+
* `generatedAt` is supplied by the caller rather than read from the clock in
|
|
123
|
+
* here, so two calls against an unchanged database, given the same instant,
|
|
124
|
+
* produce byte-identical JSON: a diff between two exports then means the
|
|
125
|
+
* data actually changed, not that time passed between them.
|
|
126
|
+
*
|
|
127
|
+
* Every collection is read with an explicit `ORDER BY` on a column that is
|
|
128
|
+
* unique for the rows in play (a primary key, or `(tenant, key)` for roles,
|
|
129
|
+
* which `roles_tenant_key_unique` already guarantees), never left to
|
|
130
|
+
* whatever order Postgres happens to return without one. That is what makes
|
|
131
|
+
* the ordering deterministic across runs, not just plausible.
|
|
132
|
+
*
|
|
133
|
+
* `credentials.secret_hash` and `invitations.token_hash` are never named in
|
|
134
|
+
* either `select`, so they are never read out of the database into this
|
|
135
|
+
* process at all; nothing here spreads a full row or nests one inside
|
|
136
|
+
* another, which is the shape that would let either slip back in by
|
|
137
|
+
* accident. Every other table this module owns is scoped to `tenantId`
|
|
138
|
+
* alone; `attach_proposals` is the one exception, scoped to either side of a
|
|
139
|
+
* proposal, since a tenant that has proposed to hold another or been asked
|
|
140
|
+
* to be held by one is still part of that tenant's own record.
|
|
141
|
+
*/
|
|
142
|
+
export declare function exportTenant(handle: DbOrTx, tenantId: string, generatedAt: Date, actor: ExportActor): Promise<null | {
|
|
143
|
+
readonly policyVersion: 2;
|
|
144
|
+
readonly generatedAt: string;
|
|
145
|
+
readonly tenant: ExportedTenant;
|
|
146
|
+
readonly memberships: readonly ExportedMembership[];
|
|
147
|
+
readonly credentials: readonly ExportedCredential[];
|
|
148
|
+
readonly roles: readonly ExportedRole[];
|
|
149
|
+
readonly invitations: readonly ExportedInvitation[];
|
|
150
|
+
readonly attachProposals: readonly ExportedAttachProposal[];
|
|
151
|
+
readonly auditRows: readonly ExportedAuditRow[];
|
|
152
|
+
readonly teams: readonly (Omit<typeof accessTeams.$inferSelect, 'createdAt' | 'archivedAt'> & {
|
|
153
|
+
createdAt: string;
|
|
154
|
+
archivedAt: string | null;
|
|
155
|
+
})[];
|
|
156
|
+
readonly teamParticipants: readonly (typeof accessTeamMembers.$inferSelect)[];
|
|
157
|
+
readonly assignments: readonly (Omit<typeof policyAssignments.$inferSelect, 'createdAt' | 'expiresAt'> & {
|
|
158
|
+
createdAt: string;
|
|
159
|
+
expiresAt: string | null;
|
|
160
|
+
})[];
|
|
161
|
+
}>;
|
package/dist/export.js
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// The permission gate is deliberately NOT in this file. `exportTenant` takes
|
|
2
|
+
// a handle and a tenant id and only ever reads; it does not know or care who
|
|
3
|
+
// is asking. A caller gates it (`requirePermission(tenantId, 'tenant:export')`
|
|
4
|
+
// or equivalent) before calling this function, the same shape every other
|
|
5
|
+
// gated write in this package follows -- this module opens no connection of
|
|
6
|
+
// its own and holds no permission logic to duplicate that caller's decision.
|
|
7
|
+
import { authzEventSchema, core, tenantVisibleByDefault } from '@wtfalch/authz';
|
|
8
|
+
import { asc, eq, or } from 'drizzle-orm';
|
|
9
|
+
import { policyAssignments, policyRoles } from './policy-schema.js';
|
|
10
|
+
import { accessTeamMembers, accessTeams } from './resource-schema.js';
|
|
11
|
+
import { attachProposals, authzEvents, credentials, invitations, memberships, tenants, } from './schema.js';
|
|
12
|
+
import { isUuid } from './types.js';
|
|
13
|
+
/**
|
|
14
|
+
* A tenant's whole record, as a plain JSON-serialisable object (D9, D12,
|
|
15
|
+
* X6 M3). `null` for an id that does not resolve to a tenant, never an empty
|
|
16
|
+
* shell with every collection blank, so a caller cannot mistake "nothing to
|
|
17
|
+
* export" for "no such tenant".
|
|
18
|
+
*
|
|
19
|
+
* `generatedAt` is supplied by the caller rather than read from the clock in
|
|
20
|
+
* here, so two calls against an unchanged database, given the same instant,
|
|
21
|
+
* produce byte-identical JSON: a diff between two exports then means the
|
|
22
|
+
* data actually changed, not that time passed between them.
|
|
23
|
+
*
|
|
24
|
+
* Every collection is read with an explicit `ORDER BY` on a column that is
|
|
25
|
+
* unique for the rows in play (a primary key, or `(tenant, key)` for roles,
|
|
26
|
+
* which `roles_tenant_key_unique` already guarantees), never left to
|
|
27
|
+
* whatever order Postgres happens to return without one. That is what makes
|
|
28
|
+
* the ordering deterministic across runs, not just plausible.
|
|
29
|
+
*
|
|
30
|
+
* `credentials.secret_hash` and `invitations.token_hash` are never named in
|
|
31
|
+
* either `select`, so they are never read out of the database into this
|
|
32
|
+
* process at all; nothing here spreads a full row or nests one inside
|
|
33
|
+
* another, which is the shape that would let either slip back in by
|
|
34
|
+
* accident. Every other table this module owns is scoped to `tenantId`
|
|
35
|
+
* alone; `attach_proposals` is the one exception, scoped to either side of a
|
|
36
|
+
* proposal, since a tenant that has proposed to hold another or been asked
|
|
37
|
+
* to be held by one is still part of that tenant's own record.
|
|
38
|
+
*/
|
|
39
|
+
export async function exportTenant(handle, tenantId, generatedAt, actor) {
|
|
40
|
+
if (!isUuid(tenantId))
|
|
41
|
+
return null;
|
|
42
|
+
const [tenantRow] = await handle
|
|
43
|
+
.select({
|
|
44
|
+
id: tenants.id,
|
|
45
|
+
kind: tenants.kind,
|
|
46
|
+
slug: tenants.slug,
|
|
47
|
+
name: tenants.name,
|
|
48
|
+
state: tenants.state,
|
|
49
|
+
parentId: tenants.parentId,
|
|
50
|
+
ceiling: tenants.ceiling,
|
|
51
|
+
selfDenied: tenants.selfDenied,
|
|
52
|
+
createdBy: tenants.createdBy,
|
|
53
|
+
createdAt: tenants.createdAt,
|
|
54
|
+
updatedAt: tenants.updatedAt,
|
|
55
|
+
})
|
|
56
|
+
.from(tenants)
|
|
57
|
+
.where(eq(tenants.id, tenantId))
|
|
58
|
+
.limit(1);
|
|
59
|
+
if (!tenantRow)
|
|
60
|
+
return null;
|
|
61
|
+
const membershipRows = await handle
|
|
62
|
+
.select({
|
|
63
|
+
principalId: memberships.principalId,
|
|
64
|
+
principalClass: memberships.principalClass,
|
|
65
|
+
source: memberships.source,
|
|
66
|
+
viaTenantId: memberships.viaTenantId,
|
|
67
|
+
tenantName: memberships.tenantName,
|
|
68
|
+
grantedBy: memberships.grantedBy,
|
|
69
|
+
createdAt: memberships.createdAt,
|
|
70
|
+
updatedAt: memberships.updatedAt,
|
|
71
|
+
})
|
|
72
|
+
.from(memberships)
|
|
73
|
+
.where(eq(memberships.tenantId, tenantId))
|
|
74
|
+
// principal_id is the other half of this table's primary key, so it is
|
|
75
|
+
// already unique within one tenant: no tiebreaker column is needed.
|
|
76
|
+
.orderBy(asc(memberships.principalClass), asc(memberships.principalId));
|
|
77
|
+
const credentialRows = await handle
|
|
78
|
+
.select({
|
|
79
|
+
id: credentials.id,
|
|
80
|
+
kind: credentials.kind,
|
|
81
|
+
name: credentials.name,
|
|
82
|
+
secretPrefix: credentials.secretPrefix,
|
|
83
|
+
createdBy: credentials.createdBy,
|
|
84
|
+
issuerId: credentials.issuerId,
|
|
85
|
+
issuerClass: credentials.issuerClass,
|
|
86
|
+
createdAt: credentials.createdAt,
|
|
87
|
+
expiresAt: credentials.expiresAt,
|
|
88
|
+
revokedAt: credentials.revokedAt,
|
|
89
|
+
lastUsedAt: credentials.lastUsedAt,
|
|
90
|
+
})
|
|
91
|
+
.from(credentials)
|
|
92
|
+
.where(eq(credentials.tenantId, tenantId))
|
|
93
|
+
.orderBy(asc(credentials.id));
|
|
94
|
+
const invitationRows = await handle
|
|
95
|
+
.select({
|
|
96
|
+
id: invitations.id,
|
|
97
|
+
email: invitations.email,
|
|
98
|
+
roleId: invitations.roleId,
|
|
99
|
+
status: invitations.status,
|
|
100
|
+
mailState: invitations.mailState,
|
|
101
|
+
invitedBy: invitations.invitedBy,
|
|
102
|
+
expiresAt: invitations.expiresAt,
|
|
103
|
+
acceptedBy: invitations.acceptedBy,
|
|
104
|
+
acceptedAt: invitations.acceptedAt,
|
|
105
|
+
revokedBy: invitations.revokedBy,
|
|
106
|
+
revokedAt: invitations.revokedAt,
|
|
107
|
+
attachParentId: invitations.attachParentId,
|
|
108
|
+
createdAt: invitations.createdAt,
|
|
109
|
+
})
|
|
110
|
+
.from(invitations)
|
|
111
|
+
.where(eq(invitations.tenantId, tenantId))
|
|
112
|
+
.orderBy(asc(invitations.id));
|
|
113
|
+
const attachProposalRows = await handle
|
|
114
|
+
.select({
|
|
115
|
+
id: attachProposals.id,
|
|
116
|
+
parentId: attachProposals.parentId,
|
|
117
|
+
childId: attachProposals.childId,
|
|
118
|
+
status: attachProposals.status,
|
|
119
|
+
reason: attachProposals.reason,
|
|
120
|
+
reference: attachProposals.reference,
|
|
121
|
+
proposedBy: attachProposals.proposedBy,
|
|
122
|
+
parentAcceptedBy: attachProposals.parentAcceptedBy,
|
|
123
|
+
parentAcceptedAt: attachProposals.parentAcceptedAt,
|
|
124
|
+
decidedBy: attachProposals.decidedBy,
|
|
125
|
+
decidedAt: attachProposals.decidedAt,
|
|
126
|
+
createdAt: attachProposals.createdAt,
|
|
127
|
+
})
|
|
128
|
+
.from(attachProposals)
|
|
129
|
+
.where(or(eq(attachProposals.parentId, tenantId), eq(attachProposals.childId, tenantId)))
|
|
130
|
+
.orderBy(asc(attachProposals.id));
|
|
131
|
+
const auditRowsRaw = await handle
|
|
132
|
+
.select({
|
|
133
|
+
id: authzEvents.id,
|
|
134
|
+
teamId: authzEvents.teamId,
|
|
135
|
+
subjectId: authzEvents.subjectId,
|
|
136
|
+
subjectClass: authzEvents.subjectClass,
|
|
137
|
+
occurredAt: authzEvents.occurredAt,
|
|
138
|
+
tenantId: authzEvents.tenantId,
|
|
139
|
+
actorClass: authzEvents.actorClass,
|
|
140
|
+
actorId: authzEvents.actorId,
|
|
141
|
+
actorDisplay: authzEvents.actorDisplay,
|
|
142
|
+
action: authzEvents.action,
|
|
143
|
+
targetType: authzEvents.targetType,
|
|
144
|
+
targetId: authzEvents.targetId,
|
|
145
|
+
outcome: authzEvents.outcome,
|
|
146
|
+
context: authzEvents.context,
|
|
147
|
+
sessionId: authzEvents.sessionId,
|
|
148
|
+
reason: authzEvents.reason,
|
|
149
|
+
reference: authzEvents.reference,
|
|
150
|
+
requestId: authzEvents.requestId,
|
|
151
|
+
ip: authzEvents.ip,
|
|
152
|
+
userAgent: authzEvents.userAgent,
|
|
153
|
+
tenantVisible: authzEvents.tenantVisible,
|
|
154
|
+
before: authzEvents.before,
|
|
155
|
+
after: authzEvents.after,
|
|
156
|
+
erasedAt: authzEvents.erasedAt,
|
|
157
|
+
schemaVersion: authzEvents.schemaVersion,
|
|
158
|
+
})
|
|
159
|
+
.from(authzEvents)
|
|
160
|
+
.where(eq(authzEvents.tenantId, tenantId))
|
|
161
|
+
// The table's own ordering (drizzle/0003_authz.sql's comment on `id`):
|
|
162
|
+
// occurred_at first, the identity column as the tiebreaker.
|
|
163
|
+
.orderBy(asc(authzEvents.occurredAt), asc(authzEvents.id));
|
|
164
|
+
const [teamRows, participantRows, roleRows, assignmentRows] = await Promise.all([
|
|
165
|
+
handle
|
|
166
|
+
.select()
|
|
167
|
+
.from(accessTeams)
|
|
168
|
+
.where(eq(accessTeams.tenantId, tenantId))
|
|
169
|
+
.orderBy(asc(accessTeams.id)),
|
|
170
|
+
handle
|
|
171
|
+
.select()
|
|
172
|
+
.from(accessTeamMembers)
|
|
173
|
+
.where(eq(accessTeamMembers.tenantId, tenantId))
|
|
174
|
+
.orderBy(asc(accessTeamMembers.teamId), asc(accessTeamMembers.principalId)),
|
|
175
|
+
handle
|
|
176
|
+
.select()
|
|
177
|
+
.from(policyRoles)
|
|
178
|
+
.where(eq(policyRoles.tenantId, tenantId))
|
|
179
|
+
.orderBy(asc(policyRoles.id)),
|
|
180
|
+
handle
|
|
181
|
+
.select()
|
|
182
|
+
.from(policyAssignments)
|
|
183
|
+
.where(eq(policyAssignments.tenantId, tenantId))
|
|
184
|
+
.orderBy(asc(policyAssignments.id)),
|
|
185
|
+
]);
|
|
186
|
+
// Written after every read above, so this row about the export never
|
|
187
|
+
// appears inside the export it is about. Hand-built rather than through
|
|
188
|
+
// `record`/`recordAs`, for the reason `ExportActor`'s own comment gives.
|
|
189
|
+
{
|
|
190
|
+
const occurredAt = new Date();
|
|
191
|
+
const row = {
|
|
192
|
+
occurred_at: occurredAt.toISOString(),
|
|
193
|
+
tenant_id: tenantId,
|
|
194
|
+
actor_class: actor.class,
|
|
195
|
+
actor_id: actor.id,
|
|
196
|
+
actor_display: actor.display,
|
|
197
|
+
action: 'tenant.exported',
|
|
198
|
+
target_type: 'tenant',
|
|
199
|
+
target_id: tenantId,
|
|
200
|
+
outcome: 'success',
|
|
201
|
+
context: tenantRow.kind === 'operator' ? 'operator' : 'standard',
|
|
202
|
+
session_id: null,
|
|
203
|
+
reason: null,
|
|
204
|
+
reference: null,
|
|
205
|
+
request_id: null,
|
|
206
|
+
ip: null,
|
|
207
|
+
user_agent: null,
|
|
208
|
+
tenant_visible: tenantVisibleByDefault('tenant.exported'),
|
|
209
|
+
before: null,
|
|
210
|
+
after: null,
|
|
211
|
+
erased_at: null,
|
|
212
|
+
schema_version: core.version,
|
|
213
|
+
};
|
|
214
|
+
authzEventSchema.parse(row);
|
|
215
|
+
await handle.insert(authzEvents).values({
|
|
216
|
+
occurredAt,
|
|
217
|
+
tenantId: row.tenant_id,
|
|
218
|
+
actorClass: row.actor_class,
|
|
219
|
+
actorId: row.actor_id,
|
|
220
|
+
actorDisplay: row.actor_display,
|
|
221
|
+
action: row.action,
|
|
222
|
+
targetType: row.target_type,
|
|
223
|
+
targetId: row.target_id,
|
|
224
|
+
outcome: row.outcome,
|
|
225
|
+
context: row.context,
|
|
226
|
+
sessionId: row.session_id,
|
|
227
|
+
reason: row.reason,
|
|
228
|
+
reference: row.reference,
|
|
229
|
+
requestId: row.request_id,
|
|
230
|
+
ip: row.ip,
|
|
231
|
+
userAgent: row.user_agent,
|
|
232
|
+
tenantVisible: row.tenant_visible,
|
|
233
|
+
before: row.before,
|
|
234
|
+
after: row.after,
|
|
235
|
+
schemaVersion: row.schema_version,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
policyVersion: 2,
|
|
240
|
+
generatedAt: generatedAt.toISOString(),
|
|
241
|
+
teams: teamRows.map((row) => ({
|
|
242
|
+
...row,
|
|
243
|
+
createdAt: row.createdAt.toISOString(),
|
|
244
|
+
archivedAt: row.archivedAt?.toISOString() ?? null,
|
|
245
|
+
})),
|
|
246
|
+
teamParticipants: participantRows,
|
|
247
|
+
assignments: assignmentRows.map((row) => ({
|
|
248
|
+
...row,
|
|
249
|
+
createdAt: row.createdAt.toISOString(),
|
|
250
|
+
expiresAt: row.expiresAt?.toISOString() ?? null,
|
|
251
|
+
})),
|
|
252
|
+
tenant: {
|
|
253
|
+
...tenantRow,
|
|
254
|
+
createdAt: tenantRow.createdAt.toISOString(),
|
|
255
|
+
updatedAt: tenantRow.updatedAt.toISOString(),
|
|
256
|
+
},
|
|
257
|
+
memberships: membershipRows.map((row) => ({
|
|
258
|
+
...row,
|
|
259
|
+
createdAt: row.createdAt.toISOString(),
|
|
260
|
+
updatedAt: row.updatedAt.toISOString(),
|
|
261
|
+
})),
|
|
262
|
+
credentials: credentialRows.map((row) => ({
|
|
263
|
+
...row,
|
|
264
|
+
createdAt: row.createdAt.toISOString(),
|
|
265
|
+
expiresAt: row.expiresAt ? row.expiresAt.toISOString() : null,
|
|
266
|
+
revokedAt: row.revokedAt ? row.revokedAt.toISOString() : null,
|
|
267
|
+
lastUsedAt: row.lastUsedAt ? row.lastUsedAt.toISOString() : null,
|
|
268
|
+
})),
|
|
269
|
+
roles: roleRows.map((row) => ({
|
|
270
|
+
...row,
|
|
271
|
+
createdAt: row.createdAt.toISOString(),
|
|
272
|
+
updatedAt: row.updatedAt.toISOString(),
|
|
273
|
+
})),
|
|
274
|
+
invitations: invitationRows.map((row) => ({
|
|
275
|
+
...row,
|
|
276
|
+
expiresAt: row.expiresAt.toISOString(),
|
|
277
|
+
acceptedAt: row.acceptedAt ? row.acceptedAt.toISOString() : null,
|
|
278
|
+
revokedAt: row.revokedAt ? row.revokedAt.toISOString() : null,
|
|
279
|
+
createdAt: row.createdAt.toISOString(),
|
|
280
|
+
})),
|
|
281
|
+
attachProposals: attachProposalRows.map((row) => ({
|
|
282
|
+
...row,
|
|
283
|
+
parentAcceptedAt: row.parentAcceptedAt ? row.parentAcceptedAt.toISOString() : null,
|
|
284
|
+
decidedAt: row.decidedAt ? row.decidedAt.toISOString() : null,
|
|
285
|
+
createdAt: row.createdAt.toISOString(),
|
|
286
|
+
})),
|
|
287
|
+
auditRows: auditRowsRaw.map((row) => ({
|
|
288
|
+
...row,
|
|
289
|
+
occurredAt: row.occurredAt.toISOString(),
|
|
290
|
+
erasedAt: row.erasedAt ? row.erasedAt.toISOString() : null,
|
|
291
|
+
})),
|
|
292
|
+
};
|
|
293
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,40 @@
|
|
|
1
1
|
export { ReadOnlyAccessError, ScopedDb, UnscopedTableError, openScopedDb, scopeColumnOf, type DbOrTx, type Scope, } from './scoped.js';
|
|
2
2
|
export { CREDENTIAL_PREFIX, CREDENTIAL_PREFIX_LENGTH, CREDENTIAL_SECRET_BYTES, bearerFromHeader, generateCredentialSecret, hashCredentialSecret, looksLikeCredentialSecret, type GeneratedCredentialSecret, } from './credential-secret.js';
|
|
3
|
-
export { policyAssignments, policyRoles, policyVersion, } from './policy-schema.js';
|
|
3
|
+
export { policyActivationApprovals, policyActivations, policyAssignments, policyRoles, policyVersion, } from './policy-schema.js';
|
|
4
4
|
export { accessTeamMembers, accessTeams, type AppRolePermission, } from './resource-schema.js';
|
|
5
5
|
export { attachProposals, authzEvents, breakGlassSessions, credentials, invitations, memberships, tenants, type AttachProposal, type AuthzEvent, type BreakGlassSession, type Credential, type Invitation, type Membership, type NewAttachProposal, type NewAuthzEvent, type NewBreakGlassSession, type NewCredential, type NewInvitation, type NewMembership, type NewTenant, type Tenant, } from './schema.js';
|
|
6
6
|
export { ownerCoverage, ownerSeatCovered, principalIsReachable, type OwnerCoverage, type OwnerCoverageOptions, type PolicyBinding, } from './owners.js';
|
|
7
7
|
export { migrateStore } from './migrate.js';
|
|
8
8
|
export { guestGrants, policyGrants, type GrantStatus, type GuestGrant } from './grants.js';
|
|
9
|
+
export { permissionOf, type StoreBinding } from './binding.js';
|
|
10
|
+
export { isCustomRoleKey } from './role-keys.js';
|
|
11
|
+
export { type Access, type BreakGlassGrant, type Context, type Principal, type Result, type TenantRow, done, isUuid, refused, tenantTag, } from './types.js';
|
|
12
|
+
export { policyResource } from './policy-resources.js';
|
|
13
|
+
export { auditedReadGate, loadAccess, loadPolicyState, permits, permitsPlatform, permitsPlatformRead, permitsRead, policyAssignment, policyRole, policyTarget, refreshAccess, requirePolicySchema, restriction, type PolicyState, } from './policy-access.js';
|
|
14
|
+
export { explain } from './denial.js';
|
|
15
|
+
export { identityWhere, primaryAssignment, primaryRoleKey, roleAssignmentRefusal, roleById, writeParticipationPolicy, writePrimaryAssignment, } from './assignments.js';
|
|
16
|
+
export { record, recordAs, securityLogFor, writeServiceEvent, type EventInput, type SecurityLogEntry, type SecurityLogPage, } from './audit.js';
|
|
17
|
+
export { definePolicy, type PolicyData, type PolicyRoleTemplate, type StorePolicy, } from './policy.js';
|
|
18
|
+
export { assignableRoles, canEditDefinition, createRole, deleteRole, roleDef, rolesForEditor, updateRole, type CreateRoleOptions, type DeleteRoleOptions, type EditableRole, type UpdateRoleOptions, } from './roles.js';
|
|
19
|
+
export { checkSystemRoles, content, ensureBuiltInRoles, orphanedRoleKeys, seedStarterRoles, type OrphanedRoleKey, type SystemRoleCheckResult, type SystemRoleCheckStatus, } from './boot.js';
|
|
20
|
+
export { TenantCycleError, ancestorsOf, attachCustomRoleBlockers, attachProblem, childrenOf, customRoleBlockers, depthAbove, derivedEventAction, descendantsOf, detachReport, expectedDerivedFor, heightBelow, lockAttachScope, lockSubtreeForUpdate, lockTenantsForUpdate, nodeById, principalsAtOrAbove, type AttachProblem, type CustomRoleBlocker, type DerivedChange, type DetachReportRow, type ExpectedDerived, type TenantNode, } from './tree.js';
|
|
21
|
+
export { DerivedCustomRoleError, attachInTx, detachInTx, recomputeDerived, } from './tree-writes.js';
|
|
22
|
+
export { type PropagationWriter, propagateMembershipChange, writerFromAccess, writerFromPrincipal, } from './propagate.js';
|
|
23
|
+
export { type ReconcileMissing, type ReconcileOrphan, type ReconcileReport, type ReconcileWrongRole, reconcileDerived, } from './reconcile.js';
|
|
24
|
+
export { type InstallOwnerOptions, type OwnerActivityLookup, installOwner, } from './install-owner.js';
|
|
25
|
+
export { type ErasePersonResult, type ErasureHost, erasePerson, pseudonymFor, } from './erase.js';
|
|
26
|
+
export { type AlertCheck, type AlertFinding, type AuthzReporting, checkBreakGlassBurst, checkOrphanMemberships, checkOrphanedRoleKeys, checkStuckInvitations, reportAlert, runAlerts, } from './alerts.js';
|
|
27
|
+
export { type HousekeepingRegistry, registerHousekeeping, runStartupChecks, } from './startup.js';
|
|
28
|
+
export { type SessionsForOptions, type StartBreakGlassOptions, activeSession, endBreakGlass, endSessionsFor, sessionsFor, startBreakGlass, } from './break-glass.js';
|
|
29
|
+
export { type CredentialIssuerPort, type CredentialSummary, type IssuerNames, type MintCredentialOptions, type MintedCredential, type StoreKeyGrant, credentialsFor, mintCredential, revokeCredential, } from './credentials.js';
|
|
30
|
+
export { bootstrapOperator } from './bootstrap.js';
|
|
31
|
+
export { ATTACH_REASONS, acceptAttach, declineAttach, detach, pendingProposalsForChild, pendingProposalsForParent, proposalsFromParent, proposeAttach, type AttachProposalForChild, type AttachProposalForParent, type AttachProposalFromParent, type AttachReason, type DetachOptions, type DetachResult, type ProposeAttachOptions, } from './nesting.js';
|
|
32
|
+
export { acceptInvitation, generateInvitationToken, invite, invitationPreview, invitationTenantId, markInvitationMail, pendingInvitations, resendInvitation, revokeInvitation, type AcceptInvitationResult, type AttachRefusal, type GeneratedInvitationToken, type InvitationHandle, type InvitationPreview, type InviteOptions, type PendingInvitationSummary, } from './invitations.js';
|
|
33
|
+
export { personRecordFor, teamRecordFor, type GrantPermission, type PeopleDirectory, type PersonGrant, type PersonRecord, type PersonRecordResult, type PersonTeam, type TeamGrant, type TeamParent, type TeamRecord, type TeamRecordResult, } from './person-records.js';
|
|
34
|
+
export { changeRole, grantMembership, guardStaysHeld, membersOf, removeMembership, selfStanding, type ChangeRoleOptions, type GrantMembershipOptions, type Member, type RemoveMembershipOptions, type SelfStanding, } from './memberships.js';
|
|
35
|
+
export { approveActivation, endActivation, needsCoApproval, requestActivation, selfApproveActivation, type ApproveActivationOptions, type RequestActivationOptions, } from './activations.js';
|
|
36
|
+
export { allTenants, archiveTenant, createTenant, createTenantAsOperator, deleteJustCreatedTenant, renameTenant, setCeiling, setSelfDenied, setTenantState, tenantById, tenantBySlug, type AllTenantsPage, type CreateTenantAsOperatorOptions, type CreateTenantAsOperatorResult, type CreateTenantConfig, type CreateTenantOptions, type RenameTenantOptions, type SetTenantStateOptions, type TenantCreationMode, type TenantSummary, } from './tenants.js';
|
|
37
|
+
export { archiveTeam, assignAppRole, changeTeamParticipant, deleteAppRole, removeAppAssignment, resourceAccessFor, resourceOverview, saveAppRole, saveTeam, scopedAuditLog, } from './resource-access.js';
|
|
38
|
+
export { exportTenant, type ExportActor, type ExportedAttachProposal, type ExportedAuditRow, type ExportedCredential, type ExportedInvitation, type ExportedMembership, type ExportedRole, type ExportedTenant, } from './export.js';
|
|
39
|
+
export { eventsFor, type EventPage, type EventsForOptions, type EventSummary, } from './events.js';
|
|
40
|
+
export { platformFrozen, setPlatformFrozen } from './platform.js';
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,40 @@
|
|
|
1
1
|
export { ReadOnlyAccessError, ScopedDb, UnscopedTableError, openScopedDb, scopeColumnOf, } from './scoped.js';
|
|
2
2
|
export { CREDENTIAL_PREFIX, CREDENTIAL_PREFIX_LENGTH, CREDENTIAL_SECRET_BYTES, bearerFromHeader, generateCredentialSecret, hashCredentialSecret, looksLikeCredentialSecret, } from './credential-secret.js';
|
|
3
|
-
export { policyAssignments, policyRoles, policyVersion, } from './policy-schema.js';
|
|
3
|
+
export { policyActivationApprovals, policyActivations, policyAssignments, policyRoles, policyVersion, } from './policy-schema.js';
|
|
4
4
|
export { accessTeamMembers, accessTeams, } from './resource-schema.js';
|
|
5
5
|
export { attachProposals, authzEvents, breakGlassSessions, credentials, invitations, memberships, tenants, } from './schema.js';
|
|
6
6
|
export { ownerCoverage, ownerSeatCovered, principalIsReachable, } from './owners.js';
|
|
7
7
|
export { migrateStore } from './migrate.js';
|
|
8
8
|
export { guestGrants, policyGrants } from './grants.js';
|
|
9
|
+
export { permissionOf } from './binding.js';
|
|
10
|
+
export { isCustomRoleKey } from './role-keys.js';
|
|
11
|
+
export { done, isUuid, refused, tenantTag, } from './types.js';
|
|
12
|
+
export { policyResource } from './policy-resources.js';
|
|
13
|
+
export { auditedReadGate, loadAccess, loadPolicyState, permits, permitsPlatform, permitsPlatformRead, permitsRead, policyAssignment, policyRole, policyTarget, refreshAccess, requirePolicySchema, restriction, } from './policy-access.js';
|
|
14
|
+
export { explain } from './denial.js';
|
|
15
|
+
export { identityWhere, primaryAssignment, primaryRoleKey, roleAssignmentRefusal, roleById, writeParticipationPolicy, writePrimaryAssignment, } from './assignments.js';
|
|
16
|
+
export { record, recordAs, securityLogFor, writeServiceEvent, } from './audit.js';
|
|
17
|
+
export { definePolicy, } from './policy.js';
|
|
18
|
+
export { assignableRoles, canEditDefinition, createRole, deleteRole, roleDef, rolesForEditor, updateRole, } from './roles.js';
|
|
19
|
+
export { checkSystemRoles, content, ensureBuiltInRoles, orphanedRoleKeys, seedStarterRoles, } from './boot.js';
|
|
20
|
+
export { TenantCycleError, ancestorsOf, attachCustomRoleBlockers, attachProblem, childrenOf, customRoleBlockers, depthAbove, derivedEventAction, descendantsOf, detachReport, expectedDerivedFor, heightBelow, lockAttachScope, lockSubtreeForUpdate, lockTenantsForUpdate, nodeById, principalsAtOrAbove, } from './tree.js';
|
|
21
|
+
export { DerivedCustomRoleError, attachInTx, detachInTx, recomputeDerived, } from './tree-writes.js';
|
|
22
|
+
export { propagateMembershipChange, writerFromAccess, writerFromPrincipal, } from './propagate.js';
|
|
23
|
+
export { reconcileDerived, } from './reconcile.js';
|
|
24
|
+
export { installOwner, } from './install-owner.js';
|
|
25
|
+
export { erasePerson, pseudonymFor, } from './erase.js';
|
|
26
|
+
export { checkBreakGlassBurst, checkOrphanMemberships, checkOrphanedRoleKeys, checkStuckInvitations, reportAlert, runAlerts, } from './alerts.js';
|
|
27
|
+
export { registerHousekeeping, runStartupChecks, } from './startup.js';
|
|
28
|
+
export { activeSession, endBreakGlass, endSessionsFor, sessionsFor, startBreakGlass, } from './break-glass.js';
|
|
29
|
+
export { credentialsFor, mintCredential, revokeCredential, } from './credentials.js';
|
|
30
|
+
export { bootstrapOperator } from './bootstrap.js';
|
|
31
|
+
export { ATTACH_REASONS, acceptAttach, declineAttach, detach, pendingProposalsForChild, pendingProposalsForParent, proposalsFromParent, proposeAttach, } from './nesting.js';
|
|
32
|
+
export { acceptInvitation, generateInvitationToken, invite, invitationPreview, invitationTenantId, markInvitationMail, pendingInvitations, resendInvitation, revokeInvitation, } from './invitations.js';
|
|
33
|
+
export { personRecordFor, teamRecordFor, } from './person-records.js';
|
|
34
|
+
export { changeRole, grantMembership, guardStaysHeld, membersOf, removeMembership, selfStanding, } from './memberships.js';
|
|
35
|
+
export { approveActivation, endActivation, needsCoApproval, requestActivation, selfApproveActivation, } from './activations.js';
|
|
36
|
+
export { allTenants, archiveTenant, createTenant, createTenantAsOperator, deleteJustCreatedTenant, renameTenant, setCeiling, setSelfDenied, setTenantState, tenantById, tenantBySlug, } from './tenants.js';
|
|
37
|
+
export { archiveTeam, assignAppRole, changeTeamParticipant, deleteAppRole, removeAppAssignment, resourceAccessFor, resourceOverview, saveAppRole, saveTeam, scopedAuditLog, } from './resource-access.js';
|
|
38
|
+
export { exportTenant, } from './export.js';
|
|
39
|
+
export { eventsFor, } from './events.js';
|
|
40
|
+
export { platformFrozen, setPlatformFrozen } from './platform.js';
|