@cosmicdrift/kumiko-bundled-features 0.116.1 → 0.119.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/package.json +7 -6
- package/src/auth-email-password/__tests__/invite-flow-kms.integration.test.ts +283 -0
- package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +5 -1
- package/src/auth-email-password/handlers/invite-accept.write.ts +5 -2
- package/src/auth-email-password/handlers/invite-signup-complete.write.ts +5 -1
- package/src/auth-email-password/handlers/token-request-handler.ts +4 -2
- package/src/channel-email/__tests__/pii-guard.test.ts +56 -0
- package/src/channel-email/email-channel.ts +2 -1
- package/src/channel-email/index.ts +1 -0
- package/src/channel-email/pii-guard.ts +36 -0
- package/src/config/table.ts +1 -1
- package/src/crypto-shredding/__tests__/forget-subject.integration.test.ts +148 -0
- package/src/crypto-shredding/constants.ts +5 -0
- package/src/crypto-shredding/feature.ts +18 -0
- package/src/crypto-shredding/handlers/forget-subject.write.ts +91 -0
- package/src/crypto-shredding/index.ts +11 -0
- package/src/delivery/tables.ts +1 -1
- package/src/mail-foundation/feature.ts +5 -2
- package/src/personal-access-tokens/__tests__/feature.test.ts +24 -0
- package/src/personal-access-tokens/__tests__/pat.integration.test.ts +39 -1
- package/src/personal-access-tokens/feature.ts +11 -1
- package/src/personal-access-tokens/handlers/create.write.ts +21 -15
- package/src/personal-access-tokens/handlers/list.query.ts +12 -9
- package/src/personal-access-tokens/schema/api-token.ts +1 -1
- package/src/sessions/__tests__/sessions.integration.test.ts +49 -0
- package/src/sessions/feature.ts +2 -0
- package/src/sessions/handlers/list.query.ts +12 -9
- package/src/sessions/handlers/mine.query.ts +11 -8
- package/src/sessions/schema/user-session.ts +4 -2
- package/src/sessions/session-callbacks.ts +19 -10
- package/src/shared/decrypt-stored-pii.ts +19 -0
- package/src/shared/encrypt-for-direct-write.ts +23 -0
- package/src/shared/index.ts +2 -0
- package/src/tenant/handlers/invitations.query.ts +10 -2
- package/src/tenant/invitation-table.ts +3 -2
- package/src/user/schema/user.ts +5 -2
- package/src/user-data-rights/__tests__/anonymous-deletion-kms.integration.test.ts +143 -0
- package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +79 -0
- package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +73 -0
- package/src/user-data-rights/handlers/deletion-grace-period.ts +4 -1
- package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +3 -1
- package/src/user-data-rights/run-export-jobs.ts +5 -1
- package/src/user-data-rights/run-forget-cleanup.ts +51 -3
- package/src/user-data-rights/run-user-export.ts +39 -12
- package/src/user-data-rights-defaults/hooks/tenant-invitation.userdata-hook.ts +6 -1
- package/src/user-profile/handlers/change-email.write.ts +3 -1
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
// expliziert KEIN passwordHash + KEINE roles (privileged columns).
|
|
22
22
|
|
|
23
23
|
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
24
|
+
import {
|
|
25
|
+
configuredPiiSubjectKms,
|
|
26
|
+
decryptPiiFieldValues,
|
|
27
|
+
isPiiCiphertext,
|
|
28
|
+
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
24
29
|
import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
|
|
25
30
|
import {
|
|
26
31
|
collectEncryptedFieldNames,
|
|
@@ -223,20 +228,42 @@ async function decryptSnippetFields(
|
|
|
223
228
|
hookEntityName: string,
|
|
224
229
|
snippet: UserDataExportSnippet,
|
|
225
230
|
): Promise<UserDataExportSnippet> {
|
|
231
|
+
let rows = snippet.rows;
|
|
232
|
+
|
|
226
233
|
const entity = registry.getEntity(snippet.entity) ?? registry.getEntity(hookEntityName);
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
234
|
+
if (entity) {
|
|
235
|
+
const encryptedFields = collectEncryptedFieldNames(entity);
|
|
236
|
+
if (encryptedFields.size > 0) {
|
|
237
|
+
const cipher = configuredEntityFieldEncryption();
|
|
238
|
+
rows = await Promise.all(
|
|
239
|
+
rows.map(async (row) => {
|
|
240
|
+
if (cipher) return decryptEntityFieldValues(row, encryptedFields, cipher);
|
|
241
|
+
const out = { ...row };
|
|
242
|
+
for (const name of encryptedFields) {
|
|
243
|
+
if (typeof out[name] === "string") out[name] = ENCRYPTED_UNAVAILABLE;
|
|
244
|
+
}
|
|
245
|
+
return out;
|
|
246
|
+
}),
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// PII-Subject-Ciphertexte (kumiko-pii:) sind self-describing — kein
|
|
252
|
+
// Entity-Kontext noetig, deckt jeden heutigen und kuenftigen Hook ab.
|
|
253
|
+
// Erased Subject → Sentinel (ehrlich: der Wert ist geshreddet).
|
|
254
|
+
const kms = configuredPiiSubjectKms();
|
|
255
|
+
rows = await Promise.all(
|
|
256
|
+
rows.map(async (row) => {
|
|
257
|
+
const piiKeys = Object.keys(row).filter((k) => isPiiCiphertext(row[k]));
|
|
258
|
+
if (piiKeys.length === 0) return row;
|
|
259
|
+
if (!kms) {
|
|
260
|
+
const out = { ...row };
|
|
261
|
+
for (const k of piiKeys) out[k] = ENCRYPTED_UNAVAILABLE;
|
|
262
|
+
return out;
|
|
238
263
|
}
|
|
239
|
-
return
|
|
264
|
+
return decryptPiiFieldValues(row, piiKeys, kms, {
|
|
265
|
+
requestId: "user-data-rights:export",
|
|
266
|
+
});
|
|
240
267
|
}),
|
|
241
268
|
);
|
|
242
269
|
return { ...snippet, rows };
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type UserDataExportHook,
|
|
7
7
|
type UserDataHookCtx,
|
|
8
8
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
9
|
+
import { decryptStoredPii } from "../../shared";
|
|
9
10
|
import { tenantInvitationEntity, tenantInvitationsTable } from "../../tenant";
|
|
10
11
|
import { userTable } from "../../user";
|
|
11
12
|
import { featureMounted } from "./feature-mounted";
|
|
@@ -38,7 +39,11 @@ async function resolveUserEmail(ctx: UserDataHookCtx): Promise<string | null> {
|
|
|
38
39
|
const row = (await fetchOne(ctx.db, userTable, { id: ctx.userId })) as {
|
|
39
40
|
email: string;
|
|
40
41
|
} | null; // @cast-boundary db-runner
|
|
41
|
-
|
|
42
|
+
if (!row) return null;
|
|
43
|
+
// Stored value is ciphertext with an active KMS; lowercasing a base64
|
|
44
|
+
// blob would break both lookup arms — decrypt first.
|
|
45
|
+
const email = await decryptStoredPii(row.email, "user-data-rights:invitation-hook");
|
|
46
|
+
return email.toLowerCase();
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
export const tenantInvitationExportHook: UserDataExportHook = async (ctx) => {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import { AuthErrors, verifyPassword } from "../../auth-email-password";
|
|
10
|
+
import { decryptStoredPii } from "../../shared";
|
|
10
11
|
import { UserErrors, UserHandlers, UserQueries } from "../../user";
|
|
11
12
|
import { UserProfileErrors } from "../constants";
|
|
12
13
|
|
|
@@ -53,7 +54,8 @@ export const changeEmailWrite = defineWriteHandler({
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
const newEmail = event.payload.newEmail.toLowerCase();
|
|
56
|
-
|
|
57
|
+
const currentEmail = await decryptStoredPii(me.email, "user-profile:change-email");
|
|
58
|
+
if (newEmail === currentEmail.toLowerCase()) {
|
|
57
59
|
return writeFailure(
|
|
58
60
|
new UnprocessableError(UserProfileErrors.emailUnchanged, {
|
|
59
61
|
i18nKey: "profile.errors.emailUnchanged",
|