@cosmicdrift/kumiko-bundled-features 0.371.1 → 0.372.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth-mfa/db/queries.d.ts +3 -2
- package/dist/auth-mfa/db/queries.js +2 -5
- package/dist/auth-mfa/schema/user-mfa.d.ts +10 -10
- package/dist/billing-foundation/entities.d.ts +10 -10
- package/dist/billing-foundation/projection.d.ts +10 -10
- package/dist/cap-counter/entity.d.ts +4 -4
- package/dist/cap-overview/web/cap-cards-panel.js +2 -1
- package/dist/compliance-profiles/schema/profile-selection.d.ts +4 -4
- package/dist/config/table.d.ts +6 -6
- package/dist/config/web/index.d.ts +1 -0
- package/dist/config/web/index.js +1 -0
- package/dist/crypto-shredding/handlers/forget-subject.write.js +55 -30
- package/dist/crypto-shredding/resolve-file-bytes-deleter.d.ts +3 -0
- package/dist/crypto-shredding/resolve-file-bytes-deleter.js +24 -0
- package/dist/custom-fields/entity.d.ts +9 -7
- package/dist/custom-fields/executor.d.ts +19 -1
- package/dist/data-retention/index.d.ts +1 -0
- package/dist/data-retention/index.js +1 -0
- package/dist/data-retention/run-retention-cleanup.js +27 -159
- package/dist/data-retention/schema/tenant-retention-override.d.ts +6 -6
- package/dist/delivery/delivery-service.d.ts +2 -1
- package/dist/delivery/delivery-service.js +3 -2
- package/dist/delivery/handlers/unsubscribe-page-chrome.query.js +2 -2
- package/dist/delivery/index.d.ts +1 -1
- package/dist/delivery/index.js +1 -1
- package/dist/delivery/jobs.js +21 -7
- package/dist/delivery/recipient-address-seal.d.ts +13 -0
- package/dist/delivery/recipient-address-seal.js +53 -0
- package/dist/delivery/tables.d.ts +8 -7
- package/dist/delivery/testing.d.ts +2 -1
- package/dist/delivery/testing.js +1 -0
- package/dist/delivery/unsubscribe.d.ts +13 -2
- package/dist/delivery/unsubscribe.js +9 -1
- package/dist/document-ingest-foundation/entity.d.ts +8 -8
- package/dist/document-ingest-foundation/executor.d.ts +16 -1
- package/dist/file-foundation/read-file-ref-content.js +2 -2
- package/dist/folders/entity.d.ts +8 -8
- package/dist/folders/executor.d.ts +29 -2
- package/dist/form-draft/entity.d.ts +3 -3
- package/dist/form-draft/executor.d.ts +15 -1
- package/dist/inbound-mail-foundation/entities.d.ts +31 -31
- package/dist/inbound-mail-foundation/projection.d.ts +24 -24
- package/dist/jobs/job-run-table.d.ts +10 -10
- package/dist/ledger/entity.d.ts +21 -21
- package/dist/ledger/executor.d.ts +57 -3
- package/dist/managed-pages/table.d.ts +16 -14
- package/dist/notes-history/entity.d.ts +14 -14
- package/dist/notes-history/executor.d.ts +31 -2
- package/dist/personal-access-tokens/schema/api-token.d.ts +9 -9
- package/dist/personal-access-tokens/screens.js +14 -9
- package/dist/secrets/table.d.ts +2 -2
- package/dist/sessions/schema/user-session.d.ts +8 -8
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.js +2 -0
- package/dist/shared/purge-files-reaching-subject.d.ts +25 -0
- package/dist/shared/purge-files-reaching-subject.js +146 -0
- package/dist/shared/purge-row-file-refs.d.ts +33 -0
- package/dist/shared/purge-row-file-refs.js +152 -0
- package/dist/tags/entity.d.ts +9 -9
- package/dist/tags/executor.d.ts +30 -2
- package/dist/template-resolver/handlers/shared.d.ts +28 -1
- package/dist/template-resolver/table.d.ts +32 -32
- package/dist/template-resolver/template-preview.js +13 -4
- package/dist/template-resolver/user-content-table.d.ts +16 -16
- package/dist/template-resolver/web/client-plugin.js +2 -1
- package/dist/template-resolver/web/document-editor-extensions.d.ts +1 -1
- package/dist/template-resolver/web/document-editor-extensions.js +2 -5
- package/dist/template-resolver/web/placeholder-line-editor.js +2 -2
- package/dist/tenant/handlers/list.query.d.ts +21 -1
- package/dist/tenant/invitation-table.d.ts +14 -14
- package/dist/tenant/membership-table.d.ts +4 -4
- package/dist/tenant/schema/tenant.d.ts +20 -18
- package/dist/tier-engine/entity.d.ts +2 -2
- package/dist/tier-engine/tier-resolver.d.ts +2 -2
- package/dist/user/handlers/me.query.d.ts +21 -0
- package/dist/user/schema/user.d.ts +24 -22
- package/dist/user-data-rights/run-export-jobs.d.ts +20 -1
- package/dist/user-data-rights/run-forget-cleanup.js +35 -1
- package/dist/user-data-rights/schema/download-attempt.d.ts +16 -16
- package/dist/user-data-rights/schema/download-token.d.ts +16 -16
- package/dist/user-data-rights/schema/export-job.d.ts +20 -20
- package/dist/user-data-rights/web/privacy-center-screen.js +2 -1
- package/dist/waitlist/entity.d.ts +24 -24
- package/dist/waitlist/lib.d.ts +24 -1
- package/package.json +11 -11
- package/src/crypto-shredding/changes.json +6 -0
- package/src/data-retention/changes.json +6 -0
- package/src/delivery/changes.json +13 -0
- package/src/files/changes.json +7 -0
- package/src/personal-access-tokens/changes.json +12 -0
- package/src/template-resolver/changes.json +6 -0
- package/src/user-data-rights/changes.json +6 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type EntityDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
export declare function createNoteEntryEntity(access?: EntityDefinition["access"], parents?: readonly string[]): EntityDefinition<{
|
|
3
|
-
entityType: {
|
|
3
|
+
readonly entityType: {
|
|
4
4
|
readonly type: "text";
|
|
5
5
|
readonly description?: string;
|
|
6
6
|
readonly maxLength?: number;
|
|
@@ -23,7 +23,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
|
|
|
23
23
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
24
24
|
required: true;
|
|
25
25
|
};
|
|
26
|
-
entityId: {
|
|
26
|
+
readonly entityId: {
|
|
27
27
|
readonly type: "text";
|
|
28
28
|
readonly description?: string;
|
|
29
29
|
readonly maxLength?: number;
|
|
@@ -46,7 +46,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
|
|
|
46
46
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
47
47
|
required: true;
|
|
48
48
|
};
|
|
49
|
-
authorId: {
|
|
49
|
+
readonly authorId: {
|
|
50
50
|
readonly type: "text";
|
|
51
51
|
readonly description?: string;
|
|
52
52
|
readonly maxLength?: number;
|
|
@@ -69,7 +69,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
|
|
|
69
69
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
70
70
|
required: false;
|
|
71
71
|
};
|
|
72
|
-
authorName: {
|
|
72
|
+
readonly authorName: {
|
|
73
73
|
readonly type: "text";
|
|
74
74
|
readonly description?: string;
|
|
75
75
|
readonly maxLength?: number;
|
|
@@ -92,7 +92,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
|
|
|
92
92
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
93
93
|
required: false;
|
|
94
94
|
};
|
|
95
|
-
body: {
|
|
95
|
+
readonly body: {
|
|
96
96
|
readonly type: "longText";
|
|
97
97
|
readonly description?: string;
|
|
98
98
|
readonly maxLength?: number;
|
|
@@ -110,7 +110,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
|
|
|
110
110
|
};
|
|
111
111
|
}, "tenant">;
|
|
112
112
|
export declare const noteEntryEntity: EntityDefinition<{
|
|
113
|
-
entityType: {
|
|
113
|
+
readonly entityType: {
|
|
114
114
|
readonly type: "text";
|
|
115
115
|
readonly description?: string;
|
|
116
116
|
readonly maxLength?: number;
|
|
@@ -133,7 +133,7 @@ export declare const noteEntryEntity: EntityDefinition<{
|
|
|
133
133
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
134
134
|
required: true;
|
|
135
135
|
};
|
|
136
|
-
entityId: {
|
|
136
|
+
readonly entityId: {
|
|
137
137
|
readonly type: "text";
|
|
138
138
|
readonly description?: string;
|
|
139
139
|
readonly maxLength?: number;
|
|
@@ -156,7 +156,7 @@ export declare const noteEntryEntity: EntityDefinition<{
|
|
|
156
156
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
157
157
|
required: true;
|
|
158
158
|
};
|
|
159
|
-
authorId: {
|
|
159
|
+
readonly authorId: {
|
|
160
160
|
readonly type: "text";
|
|
161
161
|
readonly description?: string;
|
|
162
162
|
readonly maxLength?: number;
|
|
@@ -179,7 +179,7 @@ export declare const noteEntryEntity: EntityDefinition<{
|
|
|
179
179
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
180
180
|
required: false;
|
|
181
181
|
};
|
|
182
|
-
authorName: {
|
|
182
|
+
readonly authorName: {
|
|
183
183
|
readonly type: "text";
|
|
184
184
|
readonly description?: string;
|
|
185
185
|
readonly maxLength?: number;
|
|
@@ -202,7 +202,7 @@ export declare const noteEntryEntity: EntityDefinition<{
|
|
|
202
202
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
203
203
|
required: false;
|
|
204
204
|
};
|
|
205
|
-
body: {
|
|
205
|
+
readonly body: {
|
|
206
206
|
readonly type: "longText";
|
|
207
207
|
readonly description?: string;
|
|
208
208
|
readonly maxLength?: number;
|
|
@@ -220,7 +220,7 @@ export declare const noteEntryEntity: EntityDefinition<{
|
|
|
220
220
|
};
|
|
221
221
|
}, "tenant">;
|
|
222
222
|
export declare function createNoteMentionEntity(): EntityDefinition<{
|
|
223
|
-
noteId: {
|
|
223
|
+
readonly noteId: {
|
|
224
224
|
readonly type: "text";
|
|
225
225
|
readonly description?: string;
|
|
226
226
|
readonly maxLength?: number;
|
|
@@ -243,7 +243,7 @@ export declare function createNoteMentionEntity(): EntityDefinition<{
|
|
|
243
243
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
244
244
|
required: true;
|
|
245
245
|
};
|
|
246
|
-
subjectId: {
|
|
246
|
+
readonly subjectId: {
|
|
247
247
|
readonly type: "text";
|
|
248
248
|
readonly description?: string;
|
|
249
249
|
readonly maxLength?: number;
|
|
@@ -268,7 +268,7 @@ export declare function createNoteMentionEntity(): EntityDefinition<{
|
|
|
268
268
|
};
|
|
269
269
|
}, "tenant">;
|
|
270
270
|
export declare const noteMentionEntity: EntityDefinition<{
|
|
271
|
-
noteId: {
|
|
271
|
+
readonly noteId: {
|
|
272
272
|
readonly type: "text";
|
|
273
273
|
readonly description?: string;
|
|
274
274
|
readonly maxLength?: number;
|
|
@@ -291,7 +291,7 @@ export declare const noteMentionEntity: EntityDefinition<{
|
|
|
291
291
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
292
292
|
required: true;
|
|
293
293
|
};
|
|
294
|
-
subjectId: {
|
|
294
|
+
readonly subjectId: {
|
|
295
295
|
readonly type: "text";
|
|
296
296
|
readonly description?: string;
|
|
297
297
|
readonly maxLength?: number;
|
|
@@ -1,4 +1,33 @@
|
|
|
1
1
|
import { type EntityDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
-
export declare const noteEntryExecutor: import("@cosmicdrift/kumiko-types/event-store-executor-types").EventStoreExecutor
|
|
2
|
+
export declare const noteEntryExecutor: import("@cosmicdrift/kumiko-types/event-store-executor-types").EventStoreExecutor<{
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly tenantId: string;
|
|
5
|
+
readonly version: number;
|
|
6
|
+
readonly insertedAt: Temporal.Instant;
|
|
7
|
+
readonly modifiedAt: Temporal.Instant | null;
|
|
8
|
+
readonly insertedById: string | null;
|
|
9
|
+
readonly modifiedById: string | null;
|
|
10
|
+
readonly isDeleted?: boolean | undefined;
|
|
11
|
+
readonly deletedAt?: Temporal.Instant | null | undefined;
|
|
12
|
+
readonly deletedById?: string | null | undefined;
|
|
13
|
+
readonly authorId: string | null;
|
|
14
|
+
readonly authorName: string | null;
|
|
15
|
+
readonly body: string;
|
|
16
|
+
readonly entityId: string;
|
|
17
|
+
readonly entityType: string;
|
|
18
|
+
}>, noteEntryTable: import("@cosmicdrift/kumiko-framework/db").EntityTable;
|
|
3
19
|
export declare function createNoteEntryExecutor(ownership: EntityDefinition["access"] | undefined): typeof noteEntryExecutor;
|
|
4
|
-
export declare const noteMentionExecutor: import("@cosmicdrift/kumiko-types/event-store-executor-types").EventStoreExecutor
|
|
20
|
+
export declare const noteMentionExecutor: import("@cosmicdrift/kumiko-types/event-store-executor-types").EventStoreExecutor<{
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly tenantId: string;
|
|
23
|
+
readonly version: number;
|
|
24
|
+
readonly insertedAt: Temporal.Instant;
|
|
25
|
+
readonly modifiedAt: Temporal.Instant | null;
|
|
26
|
+
readonly insertedById: string | null;
|
|
27
|
+
readonly modifiedById: string | null;
|
|
28
|
+
readonly isDeleted?: boolean | undefined;
|
|
29
|
+
readonly deletedAt?: Temporal.Instant | null | undefined;
|
|
30
|
+
readonly deletedById?: string | null | undefined;
|
|
31
|
+
readonly noteId: string;
|
|
32
|
+
readonly subjectId: string;
|
|
33
|
+
}>, noteMentionTable: import("@cosmicdrift/kumiko-framework/db").EntityTable;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
|
|
2
|
-
userId: {
|
|
2
|
+
readonly userId: {
|
|
3
3
|
readonly type: "text";
|
|
4
4
|
readonly description?: string;
|
|
5
5
|
readonly maxLength?: number;
|
|
@@ -22,7 +22,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
|
|
|
22
22
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
23
23
|
required: true;
|
|
24
24
|
};
|
|
25
|
-
tenantId: {
|
|
25
|
+
readonly tenantId: {
|
|
26
26
|
readonly type: "text";
|
|
27
27
|
readonly description?: string;
|
|
28
28
|
readonly maxLength?: number;
|
|
@@ -45,7 +45,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
|
|
|
45
45
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
46
46
|
required: true;
|
|
47
47
|
};
|
|
48
|
-
name: {
|
|
48
|
+
readonly name: {
|
|
49
49
|
readonly type: "text";
|
|
50
50
|
readonly description?: string;
|
|
51
51
|
readonly maxLength?: number;
|
|
@@ -68,7 +68,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
|
|
|
68
68
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
69
69
|
required: true;
|
|
70
70
|
};
|
|
71
|
-
tokenHash: {
|
|
71
|
+
readonly tokenHash: {
|
|
72
72
|
readonly type: "text";
|
|
73
73
|
readonly description?: string;
|
|
74
74
|
readonly maxLength?: number;
|
|
@@ -91,7 +91,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
|
|
|
91
91
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
92
92
|
required: true;
|
|
93
93
|
};
|
|
94
|
-
prefix: {
|
|
94
|
+
readonly prefix: {
|
|
95
95
|
readonly type: "text";
|
|
96
96
|
readonly description?: string;
|
|
97
97
|
readonly maxLength?: number;
|
|
@@ -114,7 +114,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
|
|
|
114
114
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
115
115
|
required: true;
|
|
116
116
|
};
|
|
117
|
-
scopes: {
|
|
117
|
+
readonly scopes: {
|
|
118
118
|
readonly type: "text";
|
|
119
119
|
readonly description?: string;
|
|
120
120
|
readonly maxLength?: number;
|
|
@@ -137,7 +137,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
|
|
|
137
137
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
138
138
|
required: true;
|
|
139
139
|
};
|
|
140
|
-
createdAt: {
|
|
140
|
+
readonly createdAt: {
|
|
141
141
|
readonly type: "timestamp";
|
|
142
142
|
readonly description?: string;
|
|
143
143
|
readonly required?: boolean;
|
|
@@ -152,7 +152,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
|
|
|
152
152
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
153
153
|
required: true;
|
|
154
154
|
};
|
|
155
|
-
expiresAt: {
|
|
155
|
+
readonly expiresAt: {
|
|
156
156
|
readonly type: "timestamp";
|
|
157
157
|
readonly description?: string;
|
|
158
158
|
readonly required?: boolean;
|
|
@@ -167,7 +167,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
|
|
|
167
167
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
168
168
|
required: false;
|
|
169
169
|
};
|
|
170
|
-
revokedAt: {
|
|
170
|
+
readonly revokedAt: {
|
|
171
171
|
readonly type: "timestamp";
|
|
172
172
|
readonly description?: string;
|
|
173
173
|
readonly required?: boolean;
|
|
@@ -13,6 +13,14 @@ const PAT_SCREEN_VISIBLE_WHEN = {
|
|
|
13
13
|
field: "enabled",
|
|
14
14
|
eq: true,
|
|
15
15
|
};
|
|
16
|
+
const PAT_CREATE_ACTION = {
|
|
17
|
+
kind: "navigate",
|
|
18
|
+
id: "create",
|
|
19
|
+
label: i18nKey("pat.create.title"),
|
|
20
|
+
screen: PAT_MINT_SCREEN_ID,
|
|
21
|
+
style: "primary",
|
|
22
|
+
icon: "plus",
|
|
23
|
+
};
|
|
16
24
|
export const patListScreen = {
|
|
17
25
|
id: PAT_SCREEN_ID,
|
|
18
26
|
type: "projectionList",
|
|
@@ -63,15 +71,12 @@ export const patListScreen = {
|
|
|
63
71
|
visible: { field: "status", ne: "revoked" },
|
|
64
72
|
},
|
|
65
73
|
],
|
|
66
|
-
toolbarActions: [
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
style: "primary",
|
|
73
|
-
},
|
|
74
|
-
],
|
|
74
|
+
toolbarActions: [PAT_CREATE_ACTION],
|
|
75
|
+
emptyState: {
|
|
76
|
+
title: i18nKey("kumiko.list.empty.title"),
|
|
77
|
+
description: i18nKey("kumiko.list.empty.hint"),
|
|
78
|
+
action: PAT_CREATE_ACTION,
|
|
79
|
+
},
|
|
75
80
|
access: {
|
|
76
81
|
openToAll: {
|
|
77
82
|
reason: "each signed-in user views only their own personal access tokens, mirroring the list handler",
|
package/dist/secrets/table.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type StoredMetadata = {
|
|
|
4
4
|
readonly hint?: string;
|
|
5
5
|
};
|
|
6
6
|
export declare const tenantSecretEntity: import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
|
|
7
|
-
key: {
|
|
7
|
+
readonly key: {
|
|
8
8
|
readonly type: "text";
|
|
9
9
|
readonly description?: string;
|
|
10
10
|
readonly maxLength?: number;
|
|
@@ -27,7 +27,7 @@ export declare const tenantSecretEntity: import("@cosmicdrift/kumiko-types/field
|
|
|
27
27
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
28
28
|
required: true;
|
|
29
29
|
};
|
|
30
|
-
kekVersion: {
|
|
30
|
+
readonly kekVersion: {
|
|
31
31
|
readonly type: "number";
|
|
32
32
|
readonly description?: string;
|
|
33
33
|
readonly required?: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
|
|
2
|
-
userId: {
|
|
2
|
+
readonly userId: {
|
|
3
3
|
readonly type: "text";
|
|
4
4
|
readonly description?: string;
|
|
5
5
|
readonly maxLength?: number;
|
|
@@ -22,7 +22,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
22
22
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
23
23
|
required: true;
|
|
24
24
|
};
|
|
25
|
-
tenantId: {
|
|
25
|
+
readonly tenantId: {
|
|
26
26
|
readonly type: "text";
|
|
27
27
|
readonly description?: string;
|
|
28
28
|
readonly maxLength?: number;
|
|
@@ -45,7 +45,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
45
45
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
46
46
|
required: true;
|
|
47
47
|
};
|
|
48
|
-
createdAt: {
|
|
48
|
+
readonly createdAt: {
|
|
49
49
|
readonly type: "timestamp";
|
|
50
50
|
readonly description?: string;
|
|
51
51
|
readonly required?: boolean;
|
|
@@ -60,7 +60,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
60
60
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
61
61
|
required: true;
|
|
62
62
|
};
|
|
63
|
-
expiresAt: {
|
|
63
|
+
readonly expiresAt: {
|
|
64
64
|
readonly type: "timestamp";
|
|
65
65
|
readonly description?: string;
|
|
66
66
|
readonly required?: boolean;
|
|
@@ -75,7 +75,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
75
75
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
76
76
|
required: true;
|
|
77
77
|
};
|
|
78
|
-
revokedAt: {
|
|
78
|
+
readonly revokedAt: {
|
|
79
79
|
readonly type: "timestamp";
|
|
80
80
|
readonly description?: string;
|
|
81
81
|
readonly required?: boolean;
|
|
@@ -90,7 +90,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
90
90
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
91
91
|
required: false;
|
|
92
92
|
};
|
|
93
|
-
lastSeenAt: {
|
|
93
|
+
readonly lastSeenAt: {
|
|
94
94
|
readonly type: "timestamp";
|
|
95
95
|
readonly description?: string;
|
|
96
96
|
readonly required?: boolean;
|
|
@@ -105,7 +105,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
105
105
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
106
106
|
required: false;
|
|
107
107
|
};
|
|
108
|
-
ip: {
|
|
108
|
+
readonly ip: {
|
|
109
109
|
readonly type: "text";
|
|
110
110
|
readonly description?: string;
|
|
111
111
|
readonly maxLength?: number;
|
|
@@ -128,7 +128,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
128
128
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
129
129
|
required: false;
|
|
130
130
|
};
|
|
131
|
-
userAgent: {
|
|
131
|
+
readonly userAgent: {
|
|
132
132
|
readonly type: "text";
|
|
133
133
|
readonly description?: string;
|
|
134
134
|
readonly maxLength?: number;
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export { mailHeading, mailParagraph, mailRepeatedRows } from "./mail-template-bo
|
|
|
14
14
|
export { mapWithConcurrency } from "./map-with-concurrency.ts";
|
|
15
15
|
export { denyUnlessJoinRowParentVisible, hostRowIsGone, joinRowParentIsVisible, parentRowIsVisible, } from "./parent-visibility.ts";
|
|
16
16
|
export { hashPassword, verifyDummyPassword, verifyPassword } from "./password-hashing.ts";
|
|
17
|
+
export { type DeleteFileBytes, FileSweepError, type MissingStoragePolicy, purgeFilesReachingSubject, } from "./purge-files-reaching-subject.ts";
|
|
18
|
+
export { collectSingleFileHolders, type FileFieldPlan, type FileRefPurgeOutcome, planFileFields, purgeRowFileRefs, type SingleFileHolder, } from "./purge-row-file-refs.ts";
|
|
17
19
|
export { rateLimitedTextResponse } from "./rate-limited-text-response.ts";
|
|
18
20
|
export { redactBotTokens, redactEmailAddresses, redactErrorText, redactUrls, } from "./redact.ts";
|
|
19
21
|
export { REDACTED_PII_PLACEHOLDER, redactPiiCiphertext } from "./redact-pii-ciphertext.ts";
|
package/dist/shared/index.js
CHANGED
|
@@ -14,6 +14,8 @@ export { mailHeading, mailParagraph, mailRepeatedRows } from "./mail-template-bo
|
|
|
14
14
|
export { mapWithConcurrency } from "./map-with-concurrency.js";
|
|
15
15
|
export { denyUnlessJoinRowParentVisible, hostRowIsGone, joinRowParentIsVisible, parentRowIsVisible, } from "./parent-visibility.js";
|
|
16
16
|
export { hashPassword, verifyDummyPassword, verifyPassword } from "./password-hashing.js";
|
|
17
|
+
export { FileSweepError, purgeFilesReachingSubject, } from "./purge-files-reaching-subject.js";
|
|
18
|
+
export { collectSingleFileHolders, planFileFields, purgeRowFileRefs, } from "./purge-row-file-refs.js";
|
|
17
19
|
export { rateLimitedTextResponse } from "./rate-limited-text-response.js";
|
|
18
20
|
export { redactBotTokens, redactEmailAddresses, redactErrorText, redactUrls, } from "./redact.js";
|
|
19
21
|
export { REDACTED_PII_PLACEHOLDER, redactPiiCiphertext } from "./redact-pii-ciphertext.js";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type SubjectId } from "@cosmicdrift/kumiko-framework/crypto";
|
|
2
|
+
import { type DbRunner } from "@cosmicdrift/kumiko-framework/db";
|
|
3
|
+
import { type Registry, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
|
+
import { type FileRefPurgeOutcome } from "./purge-row-file-refs.ts";
|
|
5
|
+
export declare class FileSweepError extends Error {
|
|
6
|
+
readonly code: Exclude<FileRefPurgeOutcome, "purged">;
|
|
7
|
+
readonly entityName: string;
|
|
8
|
+
constructor(code: Exclude<FileRefPurgeOutcome, "purged">, entityName: string);
|
|
9
|
+
}
|
|
10
|
+
export type DeleteFileBytes = (storageKey: string) => Promise<readonly string[]>;
|
|
11
|
+
export type MissingStoragePolicy = {
|
|
12
|
+
readonly mode: "fail";
|
|
13
|
+
} | {
|
|
14
|
+
readonly mode: "skip";
|
|
15
|
+
readonly warn: (tenantId: TenantId) => void;
|
|
16
|
+
};
|
|
17
|
+
export declare function purgeFilesReachingSubject(args: {
|
|
18
|
+
readonly registry: Registry;
|
|
19
|
+
readonly db: DbRunner;
|
|
20
|
+
readonly subject: SubjectId;
|
|
21
|
+
readonly subjectKey: string;
|
|
22
|
+
readonly recordTenantId?: TenantId;
|
|
23
|
+
readonly resolveDeleteBytes: (tenantId: TenantId) => Promise<DeleteFileBytes | undefined>;
|
|
24
|
+
readonly missingStorage: MissingStoragePolicy;
|
|
25
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { resolveSubjectForField, SubjectResolutionError, subjectIdToKey, } from "@cosmicdrift/kumiko-framework/crypto";
|
|
2
|
+
import { createEventStoreExecutor, createTenantDb, selectMany, } from "@cosmicdrift/kumiko-framework/db";
|
|
3
|
+
import { createSystemUser, parseTenantId, } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
|
+
import { fileRefEntity, fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
|
|
5
|
+
import { collectSingleFileHolders, planFileFields, purgeRowFileRefs, } from "./purge-row-file-refs.js";
|
|
6
|
+
// Callers' error labels read err.code, so audit events name the failure class.
|
|
7
|
+
export class FileSweepError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
entityName;
|
|
10
|
+
constructor(code, entityName) {
|
|
11
|
+
super(`[files-purge] ${code} while purging files of entity ${entityName}`);
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.entityName = entityName;
|
|
14
|
+
this.name = "FileSweepError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function collectFileEntities(registry) {
|
|
18
|
+
const found = [];
|
|
19
|
+
for (const proj of registry.getAllProjections().values()) {
|
|
20
|
+
if (proj.isImplicit !== true || typeof proj.source !== "string" || !proj.table)
|
|
21
|
+
continue;
|
|
22
|
+
const entity = registry.getEntity(proj.source);
|
|
23
|
+
if (!entity)
|
|
24
|
+
continue;
|
|
25
|
+
const { singleFields, multiFields } = planFileFields(entity.fields);
|
|
26
|
+
const fileFields = [...singleFields, ...multiFields];
|
|
27
|
+
if (fileFields.length === 0)
|
|
28
|
+
continue;
|
|
29
|
+
found.push({ entityName: proj.source, entity, table: proj.table, fileFields });
|
|
30
|
+
}
|
|
31
|
+
return found;
|
|
32
|
+
}
|
|
33
|
+
// Candidate rows only; the exact per-field subject is verified with resolveSubjectForField,
|
|
34
|
+
// the same resolver that encrypted the field.
|
|
35
|
+
function candidateWheres(subject, recordTenantId, { entityName, entity, fileFields }) {
|
|
36
|
+
if (subject.kind === "record") {
|
|
37
|
+
// Without the owning tenant the record cannot be told apart from a same-id row elsewhere.
|
|
38
|
+
return entityName === subject.entity && recordTenantId !== undefined
|
|
39
|
+
? [{ id: subject.id, tenantId: recordTenantId }]
|
|
40
|
+
: [];
|
|
41
|
+
}
|
|
42
|
+
if (subject.kind !== "user")
|
|
43
|
+
return [];
|
|
44
|
+
const wheres = [];
|
|
45
|
+
for (const fieldName of fileFields) {
|
|
46
|
+
const field = entity.fields[fieldName];
|
|
47
|
+
if (field && "userOwned" in field && field.userOwned !== undefined) {
|
|
48
|
+
wheres.push({ [field.userOwned.ownerField]: subject.userId });
|
|
49
|
+
}
|
|
50
|
+
else if (field && "pii" in field && field.pii === true && entity.idType !== "serial") {
|
|
51
|
+
wheres.push({ id: subject.userId });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return wheres;
|
|
55
|
+
}
|
|
56
|
+
function fieldsResolvingToSubject(fileEntity, row, rowTenantId, subjectKey) {
|
|
57
|
+
return fileEntity.fileFields.filter((fieldName) => {
|
|
58
|
+
try {
|
|
59
|
+
const resolved = resolveSubjectForField(fileEntity.entity, fieldName, row, {
|
|
60
|
+
tenantId: rowTenantId,
|
|
61
|
+
entityName: fileEntity.entityName,
|
|
62
|
+
});
|
|
63
|
+
return resolved !== null && subjectIdToKey(resolved) === subjectKey;
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
// skip: a field whose owner cannot be resolved on this row never encrypted under this subject
|
|
67
|
+
if (err instanceof SubjectResolutionError)
|
|
68
|
+
return false;
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const fileRefExecutor = createEventStoreExecutor(fileRefsTable, fileRefEntity, {
|
|
74
|
+
entityName: "fileRef",
|
|
75
|
+
});
|
|
76
|
+
async function deleteBytesFailingClosed(deleteBytes, storageKey) {
|
|
77
|
+
try {
|
|
78
|
+
return await deleteBytes(storageKey);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// skip: an unsafe key or missing list permission fails this file closed, not the whole sweep
|
|
82
|
+
return [storageKey];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Forgetting a subject erases keys; the files behind a personal file field are not ciphertext,
|
|
86
|
+
// so they need their own purge (bytes, derivatives, fileRef rows). The entity row stays:
|
|
87
|
+
// forgetting is crypto-shredding, not deletion. Tenant subjects are covered by
|
|
88
|
+
// files-tenant-data on tenant destroy. The storage is resolved per owning tenant because a
|
|
89
|
+
// subject's rows can live in any tenant.
|
|
90
|
+
export async function purgeFilesReachingSubject(args) {
|
|
91
|
+
const { registry, db, subject, subjectKey, recordTenantId, resolveDeleteBytes, missingStorage } = args;
|
|
92
|
+
// skip: tenant subjects are covered by files-tenant-data on tenant destroy
|
|
93
|
+
if (subject.kind === "tenant")
|
|
94
|
+
return;
|
|
95
|
+
const fileHolders = collectSingleFileHolders(registry);
|
|
96
|
+
const storageByTenant = new Map();
|
|
97
|
+
let firstFailure;
|
|
98
|
+
for (const fileEntity of collectFileEntities(registry)) {
|
|
99
|
+
const seenRowIds = new Set();
|
|
100
|
+
for (const where of candidateWheres(subject, recordTenantId, fileEntity)) {
|
|
101
|
+
const rows = await selectMany(db, fileEntity.table, where);
|
|
102
|
+
for (const row of rows) {
|
|
103
|
+
const rowId = String(row["id"]);
|
|
104
|
+
const tenantId = parseTenantId(row["tenantId"]);
|
|
105
|
+
// skip: a row without a tenant cannot own tenant-scoped file refs
|
|
106
|
+
if (seenRowIds.has(rowId) || tenantId === null)
|
|
107
|
+
continue;
|
|
108
|
+
seenRowIds.add(rowId);
|
|
109
|
+
const fieldNames = fieldsResolvingToSubject(fileEntity, row, tenantId, subjectKey);
|
|
110
|
+
// skip: none of the row's file fields belongs to this subject
|
|
111
|
+
if (fieldNames.length === 0)
|
|
112
|
+
continue;
|
|
113
|
+
let storagePromise = storageByTenant.get(tenantId);
|
|
114
|
+
if (storagePromise === undefined) {
|
|
115
|
+
storagePromise = resolveDeleteBytes(tenantId);
|
|
116
|
+
storageByTenant.set(tenantId, storagePromise);
|
|
117
|
+
}
|
|
118
|
+
const deleteBytes = await storagePromise;
|
|
119
|
+
const systemUser = createSystemUser(tenantId);
|
|
120
|
+
const tdb = createTenantDb({ db, tenantId, mode: "system" });
|
|
121
|
+
const outcome = await purgeRowFileRefs({
|
|
122
|
+
db,
|
|
123
|
+
tenantId,
|
|
124
|
+
entityName: fileEntity.entityName,
|
|
125
|
+
entity: fileEntity.entity,
|
|
126
|
+
table: fileEntity.table,
|
|
127
|
+
fileHolders,
|
|
128
|
+
row,
|
|
129
|
+
fieldNames,
|
|
130
|
+
deleteBytes: deleteBytes
|
|
131
|
+
? (key) => deleteBytesFailingClosed(deleteBytes, key)
|
|
132
|
+
: undefined,
|
|
133
|
+
fileRefForget: async (fileRefId) => (await fileRefExecutor.forget({ id: fileRefId }, systemUser, tdb)).isSuccess,
|
|
134
|
+
});
|
|
135
|
+
if (outcome === "missing_file_storage" && missingStorage.mode === "skip") {
|
|
136
|
+
missingStorage.warn(tenantId);
|
|
137
|
+
}
|
|
138
|
+
else if (outcome !== "purged") {
|
|
139
|
+
firstFailure ??= new FileSweepError(outcome, fileEntity.entityName);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (firstFailure)
|
|
145
|
+
throw firstFailure;
|
|
146
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type DbRunner, selectMany } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
+
import type { EntityDefinition, Registry, TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
type EntityFields = EntityDefinition["fields"];
|
|
4
|
+
type EntityTable = Parameters<typeof selectMany>[1];
|
|
5
|
+
export type FileFieldPlan = {
|
|
6
|
+
/** type file|image: the fileRef-id lives directly in the entity column. */
|
|
7
|
+
readonly singleFields: readonly string[];
|
|
8
|
+
/** type files|images: resolved via file_refs (bound by entityType/entityId/fieldName). */
|
|
9
|
+
readonly multiFields: readonly string[];
|
|
10
|
+
};
|
|
11
|
+
export type SingleFileHolder = {
|
|
12
|
+
readonly table: EntityTable;
|
|
13
|
+
readonly tableHasTenantId: boolean;
|
|
14
|
+
readonly singleFields: readonly string[];
|
|
15
|
+
};
|
|
16
|
+
export type FileRefPurgeOutcome = "purged" | "missing_file_storage" | "file_delete_failed";
|
|
17
|
+
export declare function planFileFields(fields: EntityFields, fieldNames?: readonly string[]): FileFieldPlan;
|
|
18
|
+
export declare function collectSingleFileHolders(registry: Registry): readonly SingleFileHolder[];
|
|
19
|
+
export declare function purgeRowFileRefs(args: {
|
|
20
|
+
readonly db: DbRunner;
|
|
21
|
+
readonly tenantId: TenantId;
|
|
22
|
+
readonly entityName: string;
|
|
23
|
+
readonly entity: EntityDefinition;
|
|
24
|
+
readonly table: EntityTable;
|
|
25
|
+
readonly fileHolders: readonly SingleFileHolder[];
|
|
26
|
+
readonly row: Record<string, unknown>;
|
|
27
|
+
readonly fieldNames?: readonly string[];
|
|
28
|
+
/** Returns the keys whose delete failed; undefined = no storage reachable. */
|
|
29
|
+
readonly deleteBytes: ((storageKey: string) => Promise<readonly string[]>) | undefined;
|
|
30
|
+
readonly fileRefForget: (fileRefId: string) => Promise<boolean>;
|
|
31
|
+
readonly onBytesDeleteFailed?: (failedKeys: readonly string[]) => void;
|
|
32
|
+
}): Promise<FileRefPurgeOutcome>;
|
|
33
|
+
export {};
|