From 26db3f5735ae879344cf3c0cb9255ccb5b25475e Mon Sep 17 00:00:00 2001
From: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
Date: Fri, 19 Jun 2026 13:32:39 +0200
Subject: [PATCH] Deprecate legacy encryption (#21831)
# Introduction
Still preserving the cross-upgrade flow
close https://github.com/twentyhq/core-team-issues/issues/2465
---
.../handlers/column-rotation-site.handler.ts | 2 +-
.../connection-parameters-rotation.handler.ts | 16 +--
...nsitive-config-storage-rotation.handler.ts | 3 +-
...8000005000-encrypt-application-variable.ts | 9 +-
...crypt-application-registration-variable.ts | 7 +-
...007000-encrypt-signing-key-private-keys.ts | 7 +-
...008000-encrypt-sensitive-config-storage.ts | 7 +-
...plication-registration-variable.service.ts | 2 +-
.../application-variable.service.spec.ts | 2 +-
.../application-variable.service.ts | 2 +-
.../connection-provider.service.ts | 4 +-
.../services/unsubscribe-token.service.ts | 2 +-
.../jwt/services/jwt-key-manager.service.ts | 4 +-
.../logic-function-executor.service.ts | 7 +-
.../utils/__tests__/build-env-var.spec.ts | 26 ++--
.../utils/build-env-var.ts | 2 +-
.../secret-encryption.service.spec.ts | 45 +++++++
.../secret-encryption.service.ts | 46 ++++++-
.../__tests__/config-storage.service.spec.ts | 14 +--
.../storage/config-storage.service.ts | 2 +-
.../two-factor-authentication.module.ts | 6 -
.../two-factor-authentication.service.spec.ts | 112 ++++--------------
.../two-factor-authentication.service.ts | 29 +----
...nected-account-token-encryption.service.ts | 32 +----
...on-variable-encryption.integration-spec.ts | 16 +--
...on-variable-encryption.integration-spec.ts | 16 +--
...on-parameters-rotation.integration-spec.ts | 9 +-
...t-application-variable.integration-spec.ts | 8 +-
...-registration-variable.integration-spec.ts | 6 +-
...gning-key-private-keys.integration-spec.ts | 6 +-
...nsitive-config-storage.integration-spec.ts | 2 +-
...0-encrypt-totp-secrets.integration-spec.ts | 4 +-
32 files changed, 206 insertions(+), 249 deletions(-)
diff --git a/packages/twenty-server/src/database/commands/secret-encryption-rotation/handlers/column-rotation-site.handler.ts b/packages/twenty-server/src/database/commands/secret-encryption-rotation/handlers/column-rotation-site.handler.ts
index 6cfdcc5ea1..565b382f66 100644
--- a/packages/twenty-server/src/database/commands/secret-encryption-rotation/handlers/column-rotation-site.handler.ts
+++ b/packages/twenty-server/src/database/commands/secret-encryption-rotation/handlers/column-rotation-site.handler.ts
@@ -133,7 +133,7 @@ export class ColumnRotationSiteHandler<
: undefined;
try {
- const plaintext = this.secretEncryptionService.decryptVersioned(
+ const plaintext = this.secretEncryptionService.decryptVersionedOrThrow(
currentValue,
cryptoOptions,
);
diff --git a/packages/twenty-server/src/database/commands/secret-encryption-rotation/handlers/connection-parameters-rotation.handler.ts b/packages/twenty-server/src/database/commands/secret-encryption-rotation/handlers/connection-parameters-rotation.handler.ts
index 26607d010f..5b14b7b614 100644
--- a/packages/twenty-server/src/database/commands/secret-encryption-rotation/handlers/connection-parameters-rotation.handler.ts
+++ b/packages/twenty-server/src/database/commands/secret-encryption-rotation/handlers/connection-parameters-rotation.handler.ts
@@ -16,11 +16,6 @@ import {
type EncryptedImapSmtpCaldavParams,
type ImapSmtpCaldavParams,
} from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type';
-import { SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX } from 'src/engine/core-modules/secret-encryption/constants/secret-encryption.constant';
-import {
- SecretEncryptionException,
- SecretEncryptionExceptionCode,
-} from 'src/engine/core-modules/secret-encryption/exceptions/secret-encryption.exception';
import { SecretEncryptionService } from 'src/engine/core-modules/secret-encryption/secret-encryption.service';
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
@@ -147,16 +142,7 @@ export class ConnectionParametersRotationHandler extends SecretEncryptionRotatio
continue;
}
- // Refuse non-enc:v2 values up front: decryptVersioned would otherwise
- // fall through to unauthenticated legacy CTR and corrupt the password.
- if (!params.password.startsWith(SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX)) {
- throw new SecretEncryptionException(
- `${protocol} password is not a versioned envelope (expected '${SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX}…'), refusing to rotate.`,
- SecretEncryptionExceptionCode.MALFORMED_ENVELOPE,
- );
- }
-
- const plaintext = this.secretEncryptionService.decryptVersioned(
+ const plaintext = this.secretEncryptionService.decryptVersionedOrThrow(
params.password,
{ workspaceId },
);
diff --git a/packages/twenty-server/src/database/commands/secret-encryption-rotation/handlers/sensitive-config-storage-rotation.handler.ts b/packages/twenty-server/src/database/commands/secret-encryption-rotation/handlers/sensitive-config-storage-rotation.handler.ts
index 2feb50ceab..177ea1fa87 100644
--- a/packages/twenty-server/src/database/commands/secret-encryption-rotation/handlers/sensitive-config-storage-rotation.handler.ts
+++ b/packages/twenty-server/src/database/commands/secret-encryption-rotation/handlers/sensitive-config-storage-rotation.handler.ts
@@ -118,7 +118,8 @@ export class SensitiveConfigStorageRotationHandler extends SecretEncryptionRotat
}
try {
- const plaintext = this.secretEncryptionService.decryptVersioned(rawValue);
+ const plaintext =
+ this.secretEncryptionService.decryptVersionedOrThrow(rawValue);
const reEncrypted =
this.secretEncryptionService.encryptVersioned(plaintext);
diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000005000-encrypt-application-variable.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000005000-encrypt-application-variable.ts
index 0fb13b6a8a..a703653a63 100644
--- a/packages/twenty-server/src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000005000-encrypt-application-variable.ts
+++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000005000-encrypt-application-variable.ts
@@ -85,10 +85,11 @@ export class EncryptApplicationVariableSlowInstanceCommand
if (looksLikeLegacyCtrCiphertext(row.value)) {
try {
- plaintext = this.secretEncryptionService.decryptVersioned(
- row.value as EncryptedString,
- { workspaceId: row.workspaceId },
- );
+ plaintext =
+ this.secretEncryptionService.legacyDecryptVersionedWithFallback(
+ row.value as EncryptedString,
+ { workspaceId: row.workspaceId },
+ );
} catch (error) {
this.logger.warn(
`applicationVariable row ${row.id} value not valid ciphertext; treating as plaintext. ${
diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000006000-encrypt-application-registration-variable.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000006000-encrypt-application-registration-variable.ts
index 9630798f56..cad38a8d2b 100644
--- a/packages/twenty-server/src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000006000-encrypt-application-registration-variable.ts
+++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000006000-encrypt-application-registration-variable.ts
@@ -57,9 +57,10 @@ export class EncryptApplicationRegistrationVariableSlowInstanceCommand
continue;
}
- const plaintext = this.secretEncryptionService.decryptVersioned(
- row.encryptedValue as EncryptedString,
- );
+ const plaintext =
+ this.secretEncryptionService.legacyDecryptVersionedWithFallback(
+ row.encryptedValue as EncryptedString,
+ );
if (!isDefined(plaintext)) {
continue;
diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000007000-encrypt-signing-key-private-keys.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000007000-encrypt-signing-key-private-keys.ts
index fdc1e2c26c..b2b016526f 100644
--- a/packages/twenty-server/src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000007000-encrypt-signing-key-private-keys.ts
+++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000007000-encrypt-signing-key-private-keys.ts
@@ -54,9 +54,10 @@ export class EncryptSigningKeyPrivateKeysSlowInstanceCommand implements SlowInst
continue;
}
- const plaintext = this.secretEncryptionService.decryptVersioned(
- row.privateKey as EncryptedString,
- );
+ const plaintext =
+ this.secretEncryptionService.legacyDecryptVersionedWithFallback(
+ row.privateKey as EncryptedString,
+ );
if (!isDefined(plaintext)) {
continue;
diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000008000-encrypt-sensitive-config-storage.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000008000-encrypt-sensitive-config-storage.ts
index 220e0fbb53..e8556797d1 100644
--- a/packages/twenty-server/src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000008000-encrypt-sensitive-config-storage.ts
+++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000008000-encrypt-sensitive-config-storage.ts
@@ -57,9 +57,10 @@ export class EncryptSensitiveConfigStorageSlowInstanceCommand implements SlowIns
continue;
}
- const plaintext = this.secretEncryptionService.decryptVersioned(
- rawValue as EncryptedString,
- );
+ const plaintext =
+ this.secretEncryptionService.legacyDecryptVersionedWithFallback(
+ rawValue as EncryptedString,
+ );
if (!isDefined(plaintext)) {
continue;
diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration-variable/application-registration-variable.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration-variable/application-registration-variable.service.ts
index 1747cc8df9..3703799095 100644
--- a/packages/twenty-server/src/engine/core-modules/application/application-registration-variable/application-registration-variable.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/application/application-registration-variable/application-registration-variable.service.ts
@@ -255,7 +255,7 @@ export class ApplicationRegistrationVariableService {
encryptedValue !== ''
? variable.isSecret
? '•••••••••••••'
- : this.encryptionService.decryptVersioned(encryptedValue)
+ : this.encryptionService.decryptVersionedOrThrow(encryptedValue)
: null,
};
}
diff --git a/packages/twenty-server/src/engine/core-modules/application/application-variable/__tests__/application-variable.service.spec.ts b/packages/twenty-server/src/engine/core-modules/application/application-variable/__tests__/application-variable.service.spec.ts
index 3c4191b7f4..d4db112ca6 100644
--- a/packages/twenty-server/src/engine/core-modules/application/application-variable/__tests__/application-variable.service.spec.ts
+++ b/packages/twenty-server/src/engine/core-modules/application/application-variable/__tests__/application-variable.service.spec.ts
@@ -44,7 +44,7 @@ describe('ApplicationVariableEntityService', () => {
(value: string, opts?: { workspaceId?: string }) =>
`enc:v2:deadbeef:${value}|${opts?.workspaceId ?? 'instance'}`,
),
- decryptVersioned: jest.fn(
+ decryptVersionedOrThrow: jest.fn(
(value: string, _opts?: { workspaceId?: string }) =>
value.replace(/^enc:v2:[0-9a-f]+:/, '').replace(/\|.*$/, ''),
),
diff --git a/packages/twenty-server/src/engine/core-modules/application/application-variable/application-variable.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-variable/application-variable.service.ts
index 8a36e22402..28c30d4b0d 100644
--- a/packages/twenty-server/src/engine/core-modules/application/application-variable/application-variable.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/application/application-variable/application-variable.service.ts
@@ -36,7 +36,7 @@ export class ApplicationVariableEntityService {
});
}
- return this.secretEncryptionService.decryptVersioned(
+ return this.secretEncryptionService.decryptVersionedOrThrow(
applicationVariable.value,
{ workspaceId: applicationVariable.workspaceId },
);
diff --git a/packages/twenty-server/src/engine/core-modules/application/connection-provider/connection-provider.service.ts b/packages/twenty-server/src/engine/core-modules/application/connection-provider/connection-provider.service.ts
index f847118bc2..0c076742bf 100644
--- a/packages/twenty-server/src/engine/core-modules/application/connection-provider/connection-provider.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/application/connection-provider/connection-provider.service.ts
@@ -53,7 +53,9 @@ export class ConnectionProviderService {
variables.map((v) => [
v.key,
v.encryptedValue !== ''
- ? this.secretEncryptionService.decryptVersioned(v.encryptedValue)
+ ? this.secretEncryptionService.decryptVersionedOrThrow(
+ v.encryptedValue,
+ )
: '',
]),
);
diff --git a/packages/twenty-server/src/engine/core-modules/emailing-domain/services/unsubscribe-token.service.ts b/packages/twenty-server/src/engine/core-modules/emailing-domain/services/unsubscribe-token.service.ts
index 7892fb12a7..6660c87cdf 100644
--- a/packages/twenty-server/src/engine/core-modules/emailing-domain/services/unsubscribe-token.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/emailing-domain/services/unsubscribe-token.service.ts
@@ -26,7 +26,7 @@ export class UnsubscribeTokenService {
verify(token: string): UnsubscribeTokenPayload | null {
try {
- const decrypted = this.secretEncryptionService.decryptVersioned(
+ const decrypted = this.secretEncryptionService.decryptVersionedOrThrow(
Buffer.from(token, 'base64url').toString('utf8') as EncryptedString,
);
diff --git a/packages/twenty-server/src/engine/core-modules/jwt/services/jwt-key-manager.service.ts b/packages/twenty-server/src/engine/core-modules/jwt/services/jwt-key-manager.service.ts
index f08ffe14a9..8bbe68f550 100644
--- a/packages/twenty-server/src/engine/core-modules/jwt/services/jwt-key-manager.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/jwt/services/jwt-key-manager.service.ts
@@ -191,7 +191,9 @@ export class JwtKeyManagerService {
);
}
- return this.secretEncryptionService.decryptVersioned(encryptedPrivateKey);
+ return this.secretEncryptionService.decryptVersionedOrThrow(
+ encryptedPrivateKey,
+ );
}
private async generateAndPersistCurrent(): Promise {
diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.service.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.service.ts
index 624f4f5bd5..523767b2e9 100644
--- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.service.ts
@@ -359,9 +359,10 @@ export class LogicFunctionExecutorService {
for (const variable of serverVariables) {
if (variable.encryptedValue !== '') {
- envMap[variable.key] = this.secretEncryptionService.decryptVersioned(
- variable.encryptedValue,
- );
+ envMap[variable.key] =
+ this.secretEncryptionService.decryptVersionedOrThrow(
+ variable.encryptedValue,
+ );
}
}
diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/utils/__tests__/build-env-var.spec.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/utils/__tests__/build-env-var.spec.ts
index c8cf288d27..bd10fe56b8 100644
--- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/utils/__tests__/build-env-var.spec.ts
+++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/utils/__tests__/build-env-var.spec.ts
@@ -12,7 +12,7 @@ describe('buildEnvVar', () => {
(value: string, opts?: { workspaceId?: string }) =>
`enc:v2:deadbeef:${value}|${opts?.workspaceId ?? 'instance'}`,
),
- decryptVersioned: jest.fn(
+ decryptVersionedOrThrow: jest.fn(
(value: string, _opts?: { workspaceId?: string }) =>
value.replace(/^enc:v2:[0-9a-f]+:/, '').replace(/\|.*$/, ''),
),
@@ -79,9 +79,9 @@ describe('buildEnvVar', () => {
API_SECRET: 'secret-123',
DEBUG: 'true',
});
- expect(mockSecretEncryptionService.decryptVersioned).toHaveBeenCalledTimes(
- 3,
- );
+ expect(
+ mockSecretEncryptionService.decryptVersionedOrThrow,
+ ).toHaveBeenCalledTimes(3);
});
it('routes each secret variable to its own workspace HKDF context', () => {
@@ -116,14 +116,16 @@ describe('buildEnvVar', () => {
buildEnvVar(flatVariables, mockSecretEncryptionService);
- expect(mockSecretEncryptionService.decryptVersioned).toHaveBeenCalledWith(
- `enc:v2:deadbeef:value-a|${workspaceA}`,
- { workspaceId: workspaceA },
- );
- expect(mockSecretEncryptionService.decryptVersioned).toHaveBeenCalledWith(
- `enc:v2:deadbeef:value-b|${workspaceB}`,
- { workspaceId: workspaceB },
- );
+ expect(
+ mockSecretEncryptionService.decryptVersionedOrThrow,
+ ).toHaveBeenCalledWith(`enc:v2:deadbeef:value-a|${workspaceA}`, {
+ workspaceId: workspaceA,
+ });
+ expect(
+ mockSecretEncryptionService.decryptVersionedOrThrow,
+ ).toHaveBeenCalledWith(`enc:v2:deadbeef:value-b|${workspaceB}`, {
+ workspaceId: workspaceB,
+ });
});
it('should handle null or undefined values', () => {
diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/utils/build-env-var.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/utils/build-env-var.ts
index bf82c85a89..58a862471c 100644
--- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/utils/build-env-var.ts
+++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/utils/build-env-var.ts
@@ -16,7 +16,7 @@ export const buildEnvVar = (
// the else branch into an invariant violation for non-empty values.
acc[flatApplicationVariable.key] =
isNonEmptyString(value) && isEncryptedString(value)
- ? secretEncryptionService.decryptVersioned(value, {
+ ? secretEncryptionService.decryptVersionedOrThrow(value, {
workspaceId: flatApplicationVariable.workspaceId,
})
: value;
diff --git a/packages/twenty-server/src/engine/core-modules/secret-encryption/secret-encryption.service.spec.ts b/packages/twenty-server/src/engine/core-modules/secret-encryption/secret-encryption.service.spec.ts
index b8031e351d..2f87a1931c 100644
--- a/packages/twenty-server/src/engine/core-modules/secret-encryption/secret-encryption.service.spec.ts
+++ b/packages/twenty-server/src/engine/core-modules/secret-encryption/secret-encryption.service.spec.ts
@@ -239,4 +239,49 @@ describe('SecretEncryptionService', () => {
).toBeUndefined();
});
});
+
+ describe('decryptVersionedOrThrow', () => {
+ it('round-trips a v2 envelope', () => {
+ const secret = 'sk-strict-secret-value';
+ const encrypted = service.encryptVersioned(secret as PlaintextString);
+
+ expect(service.decryptVersionedOrThrow(encrypted)).toBe(secret);
+ });
+
+ it('throws on a legacy non-v2 value instead of falling back to CTR', () => {
+ const legacyCiphertext = service.encrypt(testValue) as EncryptedString;
+
+ expect(() => service.decryptVersionedOrThrow(legacyCiphertext)).toThrow();
+ });
+
+ it('returns null/undefined values as-is', () => {
+ expect(
+ service.decryptVersionedOrThrow(null as unknown as EncryptedString),
+ ).toBeNull();
+ expect(
+ service.decryptVersionedOrThrow(
+ undefined as unknown as EncryptedString,
+ ),
+ ).toBeUndefined();
+ });
+ });
+
+ describe('legacyDecryptVersionedWithFallback', () => {
+ it('round-trips a v2 envelope', () => {
+ const secret = 'sk-legacy-secret-value';
+ const encrypted = service.encryptVersioned(secret as PlaintextString);
+
+ expect(service.legacyDecryptVersionedWithFallback(encrypted)).toBe(
+ secret,
+ );
+ });
+
+ it('falls back to legacy CTR decryption for non-v2 values', () => {
+ const legacyCiphertext = service.encrypt(testValue) as EncryptedString;
+
+ expect(service.legacyDecryptVersionedWithFallback(legacyCiphertext)).toBe(
+ testValue,
+ );
+ });
+ });
});
diff --git a/packages/twenty-server/src/engine/core-modules/secret-encryption/secret-encryption.service.ts b/packages/twenty-server/src/engine/core-modules/secret-encryption/secret-encryption.service.ts
index ff89ff7693..2d7f7cc0f1 100644
--- a/packages/twenty-server/src/engine/core-modules/secret-encryption/secret-encryption.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/secret-encryption/secret-encryption.service.ts
@@ -4,6 +4,10 @@ import { isDefined } from 'twenty-shared/utils';
import { type EncryptedString } from 'src/engine/core-modules/secret-encryption/branded-strings/encrypted-string.type';
import { type PlaintextString } from 'src/engine/core-modules/secret-encryption/branded-strings/plaintext-string.type';
+import {
+ SecretEncryptionException,
+ SecretEncryptionExceptionCode,
+} from 'src/engine/core-modules/secret-encryption/exceptions/secret-encryption.exception';
import { EnvironmentConfigDriver } from 'src/engine/core-modules/twenty-config/drivers/environment-config.driver';
import { computeEncryptionKeyId } from './utils/compute-encryption-key-id.util';
@@ -87,7 +91,7 @@ export class SecretEncryptionService {
}
return this.maskDecryptedValue(
- this.decryptVersioned(value, { workspaceId }),
+ this.decryptVersionedOrThrow(value, { workspaceId }),
mask,
);
}
@@ -127,7 +131,45 @@ export class SecretEncryptionService {
}) as EncryptedString;
}
- public decryptVersioned(
+ public decryptVersionedOrThrow(
+ value: EncryptedString,
+ opts: VersionedOptions = {},
+ ): PlaintextString {
+ if (!isDefined(value)) {
+ return value;
+ }
+
+ const parsed = parseSecretEncryptionEnvelopeOrThrow({ value });
+
+ if (parsed.version !== 2) {
+ throw new SecretEncryptionException(
+ 'Expected an enc:v2 envelope but received a non-versioned value. The 2.5 encryption backfill instance commands must have run before this value can be decrypted.',
+ SecretEncryptionExceptionCode.UNKNOWN_ENVELOPE_VERSION,
+ );
+ }
+
+ const keys = resolveEncryptionKeysOrThrow({
+ environmentConfigDriver: this.environmentConfigDriver,
+ });
+ const rawKey = pickEncryptionKeyByKeyIdOrThrow({
+ keyId: parsed.keyId,
+ keys,
+ });
+
+ return decryptAesGcmV2OrThrow({
+ payloadBase64: parsed.payload,
+ rawKey,
+ workspaceId: opts.workspaceId,
+ }) as PlaintextString;
+ }
+
+ /**
+ * @deprecated Legacy variant kept only for the 2.5 encryption backfill
+ * instance commands, which read pre-v2 rows (legacy AES-CTR ciphertext or
+ * plaintext) and re-encrypt them into the enc:v2 envelope. Runtime and
+ * rotation paths must use `decryptVersionedOrThrow` instead.
+ */
+ public legacyDecryptVersionedWithFallback(
value: EncryptedString,
opts: VersionedOptions = {},
): PlaintextString {
diff --git a/packages/twenty-server/src/engine/core-modules/twenty-config/storage/__tests__/config-storage.service.spec.ts b/packages/twenty-server/src/engine/core-modules/twenty-config/storage/__tests__/config-storage.service.spec.ts
index c03da3e54e..50accfb4e3 100644
--- a/packages/twenty-server/src/engine/core-modules/twenty-config/storage/__tests__/config-storage.service.spec.ts
+++ b/packages/twenty-server/src/engine/core-modules/twenty-config/storage/__tests__/config-storage.service.spec.ts
@@ -82,7 +82,7 @@ describe('ConfigStorageService', () => {
{
provide: SecretEncryptionService,
useValue: {
- decryptVersioned: jest.fn((value) => value),
+ decryptVersionedOrThrow: jest.fn((value) => value),
encryptVersioned: jest.fn((value) => value),
},
},
@@ -197,9 +197,9 @@ describe('ConfigStorageService', () => {
const result = await service.get(key);
expect(result).toBe(encryptedValue);
- expect(secretEncryptionService.decryptVersioned).toHaveBeenCalledWith(
- encryptedValue,
- );
+ expect(
+ secretEncryptionService.decryptVersionedOrThrow,
+ ).toHaveBeenCalledWith(encryptedValue);
});
it('should handle decryption errors gracefully', async () => {
@@ -572,9 +572,9 @@ describe('ConfigStorageService', () => {
expect(result.get('NORMAL_CONFIG' as keyof ConfigVariables)).toBe(
'normal-value',
);
- expect(secretEncryptionService.decryptVersioned).toHaveBeenCalledWith(
- 'enc:v2:deadbeef:sensitive-value',
- );
+ expect(
+ secretEncryptionService.decryptVersionedOrThrow,
+ ).toHaveBeenCalledWith('enc:v2:deadbeef:sensitive-value');
});
});
diff --git a/packages/twenty-server/src/engine/core-modules/twenty-config/storage/config-storage.service.ts b/packages/twenty-server/src/engine/core-modules/twenty-config/storage/config-storage.service.ts
index 7df6d4ca47..5ee8df107d 100644
--- a/packages/twenty-server/src/engine/core-modules/twenty-config/storage/config-storage.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/twenty-config/storage/config-storage.service.ts
@@ -76,7 +76,7 @@ export class ConfigStorageService implements ConfigStorageInterface {
this.isSensitiveStringValue(convertedValue, key) &&
isEncryptedString(convertedValue)
) {
- return this.secretEncryptionService.decryptVersioned(
+ return this.secretEncryptionService.decryptVersionedOrThrow(
convertedValue,
) as unknown as ConfigVariables[T];
}
diff --git a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.module.ts b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.module.ts
index eeefe4c46e..e10d915d68 100644
--- a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.module.ts
+++ b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.module.ts
@@ -3,7 +3,6 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
-import { JwtModule } from 'src/engine/core-modules/jwt/jwt.module';
import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
import { SecretEncryptionModule } from 'src/engine/core-modules/secret-encryption/secret-encryption.module';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
@@ -15,7 +14,6 @@ import { TwoFactorAuthenticationResolver } from './two-factor-authentication.res
import { TwoFactorAuthenticationService } from './two-factor-authentication.service';
import { TwoFactorAuthenticationMethodEntity } from './entities/two-factor-authentication-method.entity';
-import { SimpleSecretEncryptionUtil } from './utils/simple-secret-encryption.util';
@Module({
imports: [
@@ -23,9 +21,6 @@ import { SimpleSecretEncryptionUtil } from './utils/simple-secret-encryption.uti
WorkspaceDomainsModule,
MetricsModule,
TokenModule,
- // JwtModule is required by the deprecated SimpleSecretEncryptionUtil; drop
- // it together with the util once the 2.5 cross-upgrade window closes.
- JwtModule,
SecretEncryptionModule,
TypeOrmModule.forFeature([
UserEntity,
@@ -37,7 +32,6 @@ import { SimpleSecretEncryptionUtil } from './utils/simple-secret-encryption.uti
providers: [
TwoFactorAuthenticationService,
TwoFactorAuthenticationResolver,
- SimpleSecretEncryptionUtil,
provideWorkspaceScopedRepository(TwoFactorAuthenticationMethodEntity),
],
exports: [TwoFactorAuthenticationService],
diff --git a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.service.spec.ts b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.service.spec.ts
index df1e36c1b0..b4b5c6ceeb 100644
--- a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.service.spec.ts
+++ b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.service.spec.ts
@@ -18,7 +18,6 @@ import { TwoFactorAuthenticationService } from './two-factor-authentication.serv
import { TwoFactorAuthenticationMethodEntity } from './entities/two-factor-authentication-method.entity';
import { OTPStatus } from './strategies/otp/otp.constants';
-import { SimpleSecretEncryptionUtil } from './utils/simple-secret-encryption.util';
const V2_ENVELOPE_PREFIX = 'enc:v2:';
@@ -60,7 +59,6 @@ describe('TwoFactorAuthenticationService', () => {
let repository: any;
let userWorkspaceService: any;
let secretEncryptionService: any;
- let simpleSecretEncryptionUtil: any;
const mockUser = { id: 'user_123', email: 'test@example.com' };
const workspace = { id: 'ws_123', displayName: 'Test Workspace' };
@@ -71,7 +69,6 @@ describe('TwoFactorAuthenticationService', () => {
const rawSecret = 'RAW_OTP_SECRET';
const encryptedSecret = `${V2_ENVELOPE_PREFIX}abcdef12:payload`;
- const legacyCbcSecret = '0123456789abcdef0123456789abcdef:cafebabe';
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
@@ -96,13 +93,7 @@ describe('TwoFactorAuthenticationService', () => {
provide: SecretEncryptionService,
useValue: {
encryptVersioned: jest.fn(),
- decryptVersioned: jest.fn(),
- },
- },
- {
- provide: SimpleSecretEncryptionUtil,
- useValue: {
- decryptSecret: jest.fn(),
+ decryptVersionedOrThrow: jest.fn(),
},
},
],
@@ -119,9 +110,6 @@ describe('TwoFactorAuthenticationService', () => {
secretEncryptionService = module.get(
SecretEncryptionService,
);
- simpleSecretEncryptionUtil = module.get(
- SimpleSecretEncryptionUtil,
- );
jest.clearAllMocks();
});
@@ -295,7 +283,9 @@ describe('TwoFactorAuthenticationService', () => {
};
repository.findOne.mockResolvedValue(existingMethod);
- secretEncryptionService.decryptVersioned.mockReturnValue(rawSecret);
+ secretEncryptionService.decryptVersionedOrThrow.mockReturnValue(
+ rawSecret,
+ );
const expectedUri =
'otpauth://totp/test@example.com?secret=RAW_OTP_SECRET&issuer=Twenty%20-%20Test%20Workspace';
@@ -308,47 +298,14 @@ describe('TwoFactorAuthenticationService', () => {
);
expect(uri).toBe(expectedUri);
- expect(secretEncryptionService.decryptVersioned).toHaveBeenCalledWith(
- encryptedSecret,
- { workspaceId: workspace.id },
- );
- expect(simpleSecretEncryptionUtil.decryptSecret).not.toHaveBeenCalled();
+ expect(
+ secretEncryptionService.decryptVersionedOrThrow,
+ ).toHaveBeenCalledWith(encryptedSecret, { workspaceId: workspace.id });
// Should not create new method or call initiate
expect(totpStrategyMocks.initiate).not.toHaveBeenCalled();
expect(repository.save).not.toHaveBeenCalled();
});
- it('falls back to SimpleSecretEncryptionUtil when the stored secret is in the legacy AES-CBC format', async () => {
- const recentTime = new Date(Date.now() - 5 * 60 * 1000);
- const existingMethod = {
- id: 'existing_method_id',
- status: 'PENDING',
- secret: legacyCbcSecret,
- createdAt: recentTime,
- };
-
- repository.findOne.mockResolvedValue(existingMethod);
- simpleSecretEncryptionUtil.decryptSecret.mockResolvedValue(rawSecret);
-
- const uri = await service.initiateStrategyConfiguration(
- mockUser.id,
- mockUser.email,
- workspace.id,
- workspace.displayName,
- );
-
- expect(uri).toBe(
- 'otpauth://totp/test@example.com?secret=RAW_OTP_SECRET&issuer=Twenty%20-%20Test%20Workspace',
- );
- expect(simpleSecretEncryptionUtil.decryptSecret).toHaveBeenCalledWith(
- legacyCbcSecret,
- `${mockUser.id}${workspace.id}otp-secret`,
- );
- expect(secretEncryptionService.decryptVersioned).not.toHaveBeenCalled();
- expect(totpStrategyMocks.initiate).not.toHaveBeenCalled();
- expect(repository.save).not.toHaveBeenCalled();
- });
-
it('should create new method when existing pending method is too old', async () => {
const oldTime = new Date(Date.now() - 2 * 60 * 60 * 1000);
const existingMethod = {
@@ -398,7 +355,7 @@ describe('TwoFactorAuthenticationService', () => {
repository.findOne.mockResolvedValue(existingMethod);
const decryptionError = new Error('Decryption failed');
- secretEncryptionService.decryptVersioned.mockImplementation(() => {
+ secretEncryptionService.decryptVersionedOrThrow.mockImplementation(() => {
throw decryptionError;
});
@@ -463,7 +420,9 @@ describe('TwoFactorAuthenticationService', () => {
it('should successfully validate a valid token', async () => {
repository.findOne.mockResolvedValue(mock2FAMethod);
- secretEncryptionService.decryptVersioned.mockReturnValue(rawSecret);
+ secretEncryptionService.decryptVersionedOrThrow.mockReturnValue(
+ rawSecret,
+ );
totpStrategyMocks.validate.mockReturnValue({
isValid: true,
@@ -477,11 +436,9 @@ describe('TwoFactorAuthenticationService', () => {
TwoFactorAuthenticationStrategy.TOTP,
);
- expect(secretEncryptionService.decryptVersioned).toHaveBeenCalledWith(
- encryptedSecret,
- { workspaceId: workspace.id },
- );
- expect(simpleSecretEncryptionUtil.decryptSecret).not.toHaveBeenCalled();
+ expect(
+ secretEncryptionService.decryptVersionedOrThrow,
+ ).toHaveBeenCalledWith(encryptedSecret, { workspaceId: workspace.id });
expect(totpStrategyMocks.validate).toHaveBeenCalledWith(otpToken, {
status: mock2FAMethod.status,
secret: rawSecret,
@@ -495,36 +452,11 @@ describe('TwoFactorAuthenticationService', () => {
);
});
- it('dispatches to SimpleSecretEncryptionUtil for legacy AES-CBC secrets', async () => {
- const legacyMethod = {
- ...mock2FAMethod,
- secret: legacyCbcSecret,
- };
-
- repository.findOne.mockResolvedValue(legacyMethod);
- simpleSecretEncryptionUtil.decryptSecret.mockResolvedValue(rawSecret);
- totpStrategyMocks.validate.mockReturnValue({
- isValid: true,
- context: { status: legacyMethod.status, secret: rawSecret },
- });
-
- await service.validateStrategy(
- mockUser.id,
- otpToken,
- workspace.id,
- TwoFactorAuthenticationStrategy.TOTP,
- );
-
- expect(simpleSecretEncryptionUtil.decryptSecret).toHaveBeenCalledWith(
- legacyCbcSecret,
- `${mockUser.id}${workspace.id}otp-secret`,
- );
- expect(secretEncryptionService.decryptVersioned).not.toHaveBeenCalled();
- });
-
it('should throw if the token is invalid', async () => {
repository.findOne.mockResolvedValue(mock2FAMethod);
- secretEncryptionService.decryptVersioned.mockReturnValue(rawSecret);
+ secretEncryptionService.decryptVersionedOrThrow.mockReturnValue(
+ rawSecret,
+ );
totpStrategyMocks.validate.mockReturnValue({
isValid: false,
context: mock2FAMethod,
@@ -587,7 +519,7 @@ describe('TwoFactorAuthenticationService', () => {
it('should handle secret decryption errors', async () => {
repository.findOne.mockResolvedValue(mock2FAMethod);
- secretEncryptionService.decryptVersioned.mockImplementation(() => {
+ secretEncryptionService.decryptVersionedOrThrow.mockImplementation(() => {
throw new Error('Secret decryption failed');
});
@@ -614,7 +546,9 @@ describe('TwoFactorAuthenticationService', () => {
it('should successfully verify and return success', async () => {
repository.findOne.mockResolvedValue(mock2FAMethod);
- secretEncryptionService.decryptVersioned.mockReturnValue(rawSecret);
+ secretEncryptionService.decryptVersionedOrThrow.mockReturnValue(
+ rawSecret,
+ );
totpStrategyMocks.validate.mockReturnValue({
isValid: true,
@@ -644,7 +578,9 @@ describe('TwoFactorAuthenticationService', () => {
it('should throw if the token is invalid', async () => {
repository.findOne.mockResolvedValue(mock2FAMethod);
- secretEncryptionService.decryptVersioned.mockReturnValue(rawSecret);
+ secretEncryptionService.decryptVersionedOrThrow.mockReturnValue(
+ rawSecret,
+ );
totpStrategyMocks.validate.mockReturnValue({
isValid: false,
context: mock2FAMethod,
diff --git a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.service.ts b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.service.ts
index 80e56df3d8..0681cab32c 100644
--- a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.service.ts
@@ -10,7 +10,6 @@ import {
} from 'src/engine/core-modules/auth/auth.exception';
import { type EncryptedString } from 'src/engine/core-modules/secret-encryption/branded-strings/encrypted-string.type';
import { type PlaintextString } from 'src/engine/core-modules/secret-encryption/branded-strings/plaintext-string.type';
-import { SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX } from 'src/engine/core-modules/secret-encryption/constants/secret-encryption.constant';
import { SecretEncryptionService } from 'src/engine/core-modules/secret-encryption/secret-encryption.service';
import { UserEntity } from 'src/engine/core-modules/user/user.entity';
import { TwoFactorAuthenticationMethodEntity } from 'src/engine/core-modules/two-factor-authentication/entities/two-factor-authentication-method.entity';
@@ -28,19 +27,9 @@ import {
import { twoFactorAuthenticationMethodsValidator } from './two-factor-authentication.validation';
import { OTPStatus } from './strategies/otp/otp.constants';
-import { SimpleSecretEncryptionUtil } from './utils/simple-secret-encryption.util';
const PENDING_METHOD_REUSE_WINDOW_MS = 60 * 60 * 1000;
-// TODO: drop this helper, the `simpleSecretEncryptionUtil` dep, and the legacy
-// branch in `decryptStoredSecret` below once the 2.5 cross-upgrade window
-// closes and every TOTP secret row has been backfilled to enc:v2 by the
-// matching slow instance command.
-const buildLegacyTotpCbcPurpose = (
- userId: string,
- workspaceId: string,
-): string => `${userId}${workspaceId}otp-secret`;
-
@Injectable()
// oxlint-disable-next-line twenty/inject-workspace-repository
export class TwoFactorAuthenticationService {
@@ -49,28 +38,18 @@ export class TwoFactorAuthenticationService {
private readonly twoFactorAuthenticationMethodRepository: WorkspaceScopedRepository,
private readonly userWorkspaceService: UserWorkspaceService,
private readonly secretEncryptionService: SecretEncryptionService,
- private readonly simpleSecretEncryptionUtil: SimpleSecretEncryptionUtil,
) {}
private async decryptStoredSecret({
storedSecret,
- userId,
workspaceId,
}: {
storedSecret: EncryptedString;
- userId: string;
workspaceId: string;
}): Promise {
- if (storedSecret.startsWith(SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX)) {
- return this.secretEncryptionService.decryptVersioned(storedSecret, {
- workspaceId,
- });
- }
-
- return this.simpleSecretEncryptionUtil.decryptSecret(
- storedSecret,
- buildLegacyTotpCbcPurpose(userId, workspaceId),
- );
+ return this.secretEncryptionService.decryptVersionedOrThrow(storedSecret, {
+ workspaceId,
+ });
}
/**
@@ -141,7 +120,6 @@ export class TwoFactorAuthenticationService {
) {
const existingSecret = await this.decryptStoredSecret({
storedSecret: existing2FAMethod.secret,
- userId,
workspaceId,
});
@@ -207,7 +185,6 @@ export class TwoFactorAuthenticationService {
const originalSecret = await this.decryptStoredSecret({
storedSecret: userTwoFactorAuthenticationMethod.secret,
- userId,
workspaceId,
});
diff --git a/packages/twenty-server/src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.service.ts b/packages/twenty-server/src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.service.ts
index 4837f5fa77..c7d41aaf3b 100644
--- a/packages/twenty-server/src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.service.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.service.ts
@@ -1,4 +1,4 @@
-import { Injectable, Logger } from '@nestjs/common';
+import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
@@ -20,10 +20,6 @@ import { ACCOUNT_TYPES } from 'twenty-shared/constants';
@Injectable()
export class ConnectedAccountTokenEncryptionService {
- private readonly logger = new Logger(
- ConnectedAccountTokenEncryptionService.name,
- );
-
constructor(
private readonly secretEncryptionService: SecretEncryptionService,
) {}
@@ -75,7 +71,7 @@ export class ConnectedAccountTokenEncryptionService {
);
}
- return this.secretEncryptionService.decryptVersioned(ciphertext, {
+ return this.secretEncryptionService.decryptVersionedOrThrow(ciphertext, {
workspaceId,
});
}
@@ -179,30 +175,6 @@ export class ConnectedAccountTokenEncryptionService {
protocolParams: EncryptedConnectionParameters;
workspaceId: string;
}): PlaintextConnectionParameters {
- const isEncrypted = protocolParams.password.startsWith(
- SECRET_ENCRYPTION_ENVELOPE_PREFIX,
- );
-
- // TODO: Remove in follow-up PR once all legacy encryption fallbacks are dropped.
- // TODO: Remove after 2-5 slow instance command has been run everywhere.
- // During the rollout window protocolParams.password may be a legacy
- // unencrypted plaintext value living in the same column. We trust the
- // entity-level brand at the type layer (column is EncryptedString) but
- // still re-validate at runtime to handle the un-backfilled tail; the
- // assert above splits the two.
- if (!isEncrypted) {
- this.logger.warn(
- 'Protocol password is not encrypted. Expected during the rollout window until the slow instance command finishes backfilling.',
- );
-
- const rawPassword: string = protocolParams.password;
-
- return {
- ...protocolParams,
- password: rawPassword as PlaintextString,
- };
- }
-
return {
...protocolParams,
password: this.decrypt({
diff --git a/packages/twenty-server/test/integration/secret-encryption/application-registration-variable-encryption.integration-spec.ts b/packages/twenty-server/test/integration/secret-encryption/application-registration-variable-encryption.integration-spec.ts
index 0e48f3075d..7597f7efbd 100644
--- a/packages/twenty-server/test/integration/secret-encryption/application-registration-variable-encryption.integration-spec.ts
+++ b/packages/twenty-server/test/integration/secret-encryption/application-registration-variable-encryption.integration-spec.ts
@@ -135,7 +135,7 @@ describe('ApplicationRegistrationVariable encryption (integration)', () => {
expect(variable.value).toBe(plaintext);
});
- describe('legacy CTR fallback', () => {
+ describe('legacy CTR values are rejected at runtime', () => {
let legacyVariableId: string;
beforeAll(async () => {
@@ -156,7 +156,7 @@ describe('ApplicationRegistrationVariable encryption (integration)', () => {
);
});
- it('decrypts a legacy CTR-encrypted value through the live API', async () => {
+ it('rejects a legacy CTR-encrypted value through the live API', async () => {
legacyVariableId = crypto.randomUUID();
const plaintext = 'legacy-ctr-registration-variable-secret';
@@ -189,16 +189,8 @@ describe('ApplicationRegistrationVariable encryption (integration)', () => {
variables: { applicationRegistrationId },
});
- expect(findResponse.body.errors).toBeUndefined();
-
- const variable =
- findResponse.body.data.findApplicationRegistrationVariables.find(
- (v: { id: string }) => v.id === legacyVariableId,
- );
-
- expect(variable).toBeDefined();
- expect(variable.isSecret).toBe(false);
- expect(variable.value).toBe(plaintext);
+ expect(findResponse.body.errors).toBeDefined();
+ expect(findResponse.body.errors[0].message).toContain('enc:v2 envelope');
});
});
});
diff --git a/packages/twenty-server/test/integration/secret-encryption/application-variable-encryption.integration-spec.ts b/packages/twenty-server/test/integration/secret-encryption/application-variable-encryption.integration-spec.ts
index 2fd42d190c..ab7cda9d83 100644
--- a/packages/twenty-server/test/integration/secret-encryption/application-variable-encryption.integration-spec.ts
+++ b/packages/twenty-server/test/integration/secret-encryption/application-variable-encryption.integration-spec.ts
@@ -243,7 +243,7 @@ describe('ApplicationVariable encryption (integration)', () => {
expect(variable.value).toBe(plaintext);
});
- describe('legacy CTR fallback', () => {
+ describe('legacy CTR values are rejected at runtime', () => {
beforeAll(async () => {
await dataSource.query(
`ALTER TABLE core."applicationVariable"
@@ -264,7 +264,7 @@ describe('ApplicationVariable encryption (integration)', () => {
);
});
- it('decrypts a legacy CTR-encrypted value through the live API read path', async () => {
+ it('rejects a legacy CTR-encrypted value through the live API read path', async () => {
const plaintext = 'legacy-ctr-application-variable-secret-value-here';
await dataSource.query(
@@ -293,16 +293,8 @@ describe('ApplicationVariable encryption (integration)', () => {
variables: { id: applicationId },
});
- expect(findResponse.body.errors).toBeUndefined();
-
- const variable =
- findResponse.body.data.findOneApplication.applicationVariables.find(
- (v: { key: string }) => v.key === LEGACY_VARIABLE_KEY,
- );
-
- expect(variable).toBeDefined();
- expect(variable.isSecret).toBe(true);
- expect(variable.value).toBe(buildExpectedMask(plaintext));
+ expect(findResponse.body.errors).toBeDefined();
+ expect(findResponse.body.errors[0].message).toContain('enc:v2 envelope');
});
});
});
diff --git a/packages/twenty-server/test/integration/secret-encryption/connection-parameters-rotation.integration-spec.ts b/packages/twenty-server/test/integration/secret-encryption/connection-parameters-rotation.integration-spec.ts
index e5525574f5..b0cd44159a 100644
--- a/packages/twenty-server/test/integration/secret-encryption/connection-parameters-rotation.integration-spec.ts
+++ b/packages/twenty-server/test/integration/secret-encryption/connection-parameters-rotation.integration-spec.ts
@@ -54,9 +54,12 @@ const expectAllPasswordsDecryptTo = ({
expect(params).toBeDefined();
expect(params?.password).toMatch(V2_ENVELOPE_REGEX);
- const decrypted = secretEncryption.decryptVersioned(params!.password, {
- workspaceId: row.workspaceId,
- });
+ const decrypted = secretEncryption.decryptVersionedOrThrow(
+ params!.password,
+ {
+ workspaceId: row.workspaceId,
+ },
+ );
expect(decrypted).toBe(expectedPlaintextByProtocol[protocol]);
}
diff --git a/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000005000-encrypt-application-variable.integration-spec.ts b/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000005000-encrypt-application-variable.integration-spec.ts
index 275b50a0d5..82cbcf28ac 100644
--- a/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000005000-encrypt-application-variable.integration-spec.ts
+++ b/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000005000-encrypt-application-variable.integration-spec.ts
@@ -147,7 +147,9 @@ describe('2-5 slow instance command 1798000005000 - EncryptApplicationVariableSl
true,
);
expect(
- secretEncryptionService.decryptVersioned(row.value, { workspaceId }),
+ secretEncryptionService.decryptVersionedOrThrow(row.value, {
+ workspaceId,
+ }),
).toBe(plaintext);
});
@@ -181,7 +183,9 @@ describe('2-5 slow instance command 1798000005000 - EncryptApplicationVariableSl
true,
);
expect(
- secretEncryptionService.decryptVersioned(row.value, { workspaceId }),
+ secretEncryptionService.decryptVersionedOrThrow(row.value, {
+ workspaceId,
+ }),
).toBe(plaintext);
});
diff --git a/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000006000-encrypt-application-registration-variable.integration-spec.ts b/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000006000-encrypt-application-registration-variable.integration-spec.ts
index 88f65bc24b..d0794744d1 100644
--- a/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000006000-encrypt-application-registration-variable.integration-spec.ts
+++ b/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000006000-encrypt-application-registration-variable.integration-spec.ts
@@ -157,9 +157,9 @@ describe('2-5 slow instance command 1798000006000 - EncryptApplicationRegistrati
expect(
row.encryptedValue.startsWith(SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX),
).toBe(true);
- expect(secretEncryptionService.decryptVersioned(row.encryptedValue)).toBe(
- plaintext,
- );
+ expect(
+ secretEncryptionService.decryptVersionedOrThrow(row.encryptedValue),
+ ).toBe(plaintext);
});
it('leaves unfilled rows (encryptedValue = "") untouched', async () => {
diff --git a/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000007000-encrypt-signing-key-private-keys.integration-spec.ts b/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000007000-encrypt-signing-key-private-keys.integration-spec.ts
index d731fcf6bb..aed1145fd1 100644
--- a/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000007000-encrypt-signing-key-private-keys.integration-spec.ts
+++ b/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000007000-encrypt-signing-key-private-keys.integration-spec.ts
@@ -115,9 +115,9 @@ describe('2-5 slow instance command 1798000007000 - EncryptSigningKeyPrivateKeys
expect(
row.privateKey.startsWith(SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX),
).toBe(true);
- expect(secretEncryptionService.decryptVersioned(row.privateKey)).toBe(
- plaintextPem,
- );
+ expect(
+ secretEncryptionService.decryptVersionedOrThrow(row.privateKey),
+ ).toBe(plaintextPem);
});
it('leaves NULL private keys untouched (revoked / rotated keys)', async () => {
diff --git a/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000008000-encrypt-sensitive-config-storage.integration-spec.ts b/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000008000-encrypt-sensitive-config-storage.integration-spec.ts
index 2e6cdd03b2..5b38ea9c2e 100644
--- a/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000008000-encrypt-sensitive-config-storage.integration-spec.ts
+++ b/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000008000-encrypt-sensitive-config-storage.integration-spec.ts
@@ -103,7 +103,7 @@ describe('2-5 slow instance command 1798000008000 - EncryptSensitiveConfigStorag
expect(value.startsWith(SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX)).toBe(true);
expect(
- secretEncryptionService.decryptVersioned(
+ secretEncryptionService.decryptVersionedOrThrow(
value as EncryptedString,
),
).toBe(plaintext);
diff --git a/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000009000-encrypt-totp-secrets.integration-spec.ts b/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000009000-encrypt-totp-secrets.integration-spec.ts
index e603357f43..a413887ac5 100644
--- a/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000009000-encrypt-totp-secrets.integration-spec.ts
+++ b/packages/twenty-server/test/integration/upgrade/suites/2-5-instance-command-slow-1798000009000-encrypt-totp-secrets.integration-spec.ts
@@ -190,7 +190,9 @@ describe('2-5 slow instance command 1798000009000 - EncryptTotpSecretsSlowInstan
true,
);
expect(
- secretEncryptionService.decryptVersioned(row.secret, { workspaceId }),
+ secretEncryptionService.decryptVersionedOrThrow(row.secret, {
+ workspaceId,
+ }),
).toBe(plaintext);
});