EncryptedString PlaintextString branded string types (#21001)
## Summary closes https://github.com/twentyhq/core-team-issues/issues/2464 Introduces compile-time branded types to distinguish encrypted ciphertext from plaintext strings, preventing mix-ups like the one fixed in #20819 — but at the type level rather in addition to the one existing at runtime. ### Branded string primitives - Created `EncryptedString` and `PlaintextString` as hard nominal brands using `z.string().brand(...)`, making them non-assignable to each other or to raw `string` - Created `isEncryptedString` type predicate to narrow `string` to `EncryptedString` based on the `enc:v2:` envelope prefix - Retyped `SecretEncryptionService`: `encryptVersioned` accepts `PlaintextString`, `decryptVersioned` returns `PlaintextString` ### Entity typing - Typed encrypted columns across entities: `SigningKeyEntity.privateKey`, `TwoFactorAuthenticationMethodEntity.secret`, `ApplicationRegistrationVariableEntity.encryptedValue`, `ApplicationVariableEntity.value` - Parameterized JSONB types for connected account connection parameters (`ImapSmtpCaldavParams<Pwd>`) with reusable aliases `EncryptedImapSmtpCaldavParams` / `DecryptedImapSmtpCaldavParams` - Typed DTOs (`CreateApplicationRegistrationVariableInput`, `UpdateApplicationRegistrationVariablePayload`, `UpdateApplicationVariableEntityInput`) with `PlaintextString` ### ApplicationVariable always-encrypt uniformization - Retyped `ApplicationVariableEntity.value` to `EncryptedString | ''` — all values are now encrypted regardless of `isSecret` - Updated `ApplicationVariableEntityService` to always encrypt on write and always decrypt on read - Simplified `UpdateApplicationVariableActionHandlerService` by removing conditional encrypt/decrypt-on-isSecret-toggle logic - Added slow instance command (`2.9.0`) to backfill-encrypt existing `isSecret=false` plaintext rows and tighten the `CHECK` constraint ### ConfigStorageService refactor - Split `convertAndSecureValue` (which used `any`) into two well-typed methods: `convertAndDecrypt` and `convertAndEncrypt` - Introduced `isSensitiveStringValue` type predicate to narrow values before encryption/decryption ### What's next - Typeorm entity derivation to strictly type sitemap configuration as code + handler logic for encryption rotation - https://github.com/twentyhq/core-team-issues/issues/2465
This commit is contained in:
+6
-3
@@ -1,6 +1,7 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { DataSource, QueryRunner } from 'typeorm';
|
||||
|
||||
import { PlaintextString } from 'src/engine/core-modules/secret-encryption/branded-strings';
|
||||
import { SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX } from 'src/engine/core-modules/secret-encryption/constants/secret-encryption.constant';
|
||||
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
import { SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/slow-instance-command.interface';
|
||||
@@ -26,7 +27,9 @@ const isPlaintext = (value: string | null): value is string =>
|
||||
isDefined(value) && !value.startsWith(SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX);
|
||||
|
||||
@RegisteredInstanceCommand('2.5.0', 1798000004000, { type: 'slow' })
|
||||
export class EncryptConnectedAccountTokensSlowInstanceCommand implements SlowInstanceCommand {
|
||||
export class EncryptConnectedAccountTokensSlowInstanceCommand
|
||||
implements SlowInstanceCommand
|
||||
{
|
||||
constructor(
|
||||
private readonly connectedAccountTokenEncryptionService: ConnectedAccountTokenEncryptionService,
|
||||
) {}
|
||||
@@ -59,7 +62,7 @@ export class EncryptConnectedAccountTokensSlowInstanceCommand implements SlowIns
|
||||
if (isPlaintext(row.accessToken)) {
|
||||
params.push(
|
||||
this.connectedAccountTokenEncryptionService.encrypt({
|
||||
plaintext: row.accessToken,
|
||||
plaintext: row.accessToken as PlaintextString,
|
||||
workspaceId: row.workspaceId,
|
||||
}),
|
||||
);
|
||||
@@ -69,7 +72,7 @@ export class EncryptConnectedAccountTokensSlowInstanceCommand implements SlowIns
|
||||
if (isPlaintext(row.refreshToken)) {
|
||||
params.push(
|
||||
this.connectedAccountTokenEncryptionService.encrypt({
|
||||
plaintext: row.refreshToken,
|
||||
plaintext: row.refreshToken as PlaintextString,
|
||||
workspaceId: row.workspaceId,
|
||||
}),
|
||||
);
|
||||
|
||||
+14
-5
@@ -3,6 +3,9 @@ import { Logger } from '@nestjs/common';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { DataSource, QueryRunner } from 'typeorm';
|
||||
|
||||
import { type EncryptedString } from 'src/engine/core-modules/secret-encryption/branded-strings/encrypted-string.type';
|
||||
import { isEncryptedString } from 'src/engine/core-modules/secret-encryption/branded-strings/is-encrypted-string.util';
|
||||
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 { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
@@ -34,7 +37,9 @@ const looksLikeLegacyCtrCiphertext = (value: string): boolean =>
|
||||
LEGACY_CTR_LOOKS_LIKE_BASE64_RE.test(value);
|
||||
|
||||
@RegisteredInstanceCommand('2.5.0', 1798000005000, { type: 'slow' })
|
||||
export class EncryptApplicationVariableSlowInstanceCommand implements SlowInstanceCommand {
|
||||
export class EncryptApplicationVariableSlowInstanceCommand
|
||||
implements SlowInstanceCommand
|
||||
{
|
||||
private readonly logger = new Logger(
|
||||
EncryptApplicationVariableSlowInstanceCommand.name,
|
||||
);
|
||||
@@ -72,12 +77,16 @@ export class EncryptApplicationVariableSlowInstanceCommand implements SlowInstan
|
||||
continue;
|
||||
}
|
||||
|
||||
let plaintext: string;
|
||||
if (isEncryptedString(row.value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let plaintext: PlaintextString;
|
||||
|
||||
if (looksLikeLegacyCtrCiphertext(row.value)) {
|
||||
try {
|
||||
plaintext = this.secretEncryptionService.decryptVersioned(
|
||||
row.value,
|
||||
row.value as EncryptedString,
|
||||
{ workspaceId: row.workspaceId },
|
||||
);
|
||||
} catch (error) {
|
||||
@@ -86,13 +95,13 @@ export class EncryptApplicationVariableSlowInstanceCommand implements SlowInstan
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
);
|
||||
plaintext = row.value;
|
||||
plaintext = row.value as PlaintextString;
|
||||
}
|
||||
} else {
|
||||
this.logger.warn(
|
||||
`applicationVariable row ${row.id} value is not base64; treating as plaintext.`,
|
||||
);
|
||||
plaintext = row.value;
|
||||
plaintext = row.value as PlaintextString;
|
||||
}
|
||||
|
||||
if (!isDefined(plaintext)) {
|
||||
|
||||
+10
-2
@@ -1,6 +1,8 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { DataSource, QueryRunner } from 'typeorm';
|
||||
|
||||
import { type EncryptedString } from 'src/engine/core-modules/secret-encryption/branded-strings/encrypted-string.type';
|
||||
import { isEncryptedString } from 'src/engine/core-modules/secret-encryption/branded-strings/is-encrypted-string.util';
|
||||
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 { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
@@ -19,7 +21,9 @@ type ApplicationRegistrationVariableRow = {
|
||||
};
|
||||
|
||||
@RegisteredInstanceCommand('2.5.0', 1798000006000, { type: 'slow' })
|
||||
export class EncryptApplicationRegistrationVariableSlowInstanceCommand implements SlowInstanceCommand {
|
||||
export class EncryptApplicationRegistrationVariableSlowInstanceCommand
|
||||
implements SlowInstanceCommand
|
||||
{
|
||||
constructor(
|
||||
private readonly secretEncryptionService: SecretEncryptionService,
|
||||
) {}
|
||||
@@ -49,8 +53,12 @@ export class EncryptApplicationRegistrationVariableSlowInstanceCommand implement
|
||||
}
|
||||
|
||||
for (const row of rows) {
|
||||
if (isEncryptedString(row.encryptedValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const plaintext = this.secretEncryptionService.decryptVersioned(
|
||||
row.encryptedValue,
|
||||
row.encryptedValue as EncryptedString,
|
||||
);
|
||||
|
||||
if (!isDefined(plaintext)) {
|
||||
|
||||
+11
-2
@@ -1,6 +1,9 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { DataSource, QueryRunner } from 'typeorm';
|
||||
|
||||
import { type EncryptedString } from 'src/engine/core-modules/secret-encryption/branded-strings/encrypted-string.type';
|
||||
import { isEncryptedString } from 'src/engine/core-modules/secret-encryption/branded-strings/is-encrypted-string.util';
|
||||
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 { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
@@ -47,8 +50,12 @@ export class EncryptSigningKeyPrivateKeysSlowInstanceCommand implements SlowInst
|
||||
}
|
||||
|
||||
for (const row of rows) {
|
||||
if (isEncryptedString(row.privateKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const plaintext = this.secretEncryptionService.decryptVersioned(
|
||||
row.privateKey,
|
||||
row.privateKey as EncryptedString,
|
||||
);
|
||||
|
||||
if (!isDefined(plaintext)) {
|
||||
@@ -56,7 +63,9 @@ export class EncryptSigningKeyPrivateKeysSlowInstanceCommand implements SlowInst
|
||||
}
|
||||
|
||||
const encryptedPrivateKey =
|
||||
this.secretEncryptionService.encryptVersioned(plaintext);
|
||||
this.secretEncryptionService.encryptVersioned(
|
||||
plaintext as PlaintextString,
|
||||
);
|
||||
|
||||
await dataSource.query(
|
||||
`UPDATE "core"."signingKey"
|
||||
|
||||
+10
-8
@@ -2,7 +2,9 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { DataSource, QueryRunner } from 'typeorm';
|
||||
|
||||
import { KeyValuePairType } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity';
|
||||
import { SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX } from 'src/engine/core-modules/secret-encryption/constants/secret-encryption.constant';
|
||||
import { type EncryptedString } from 'src/engine/core-modules/secret-encryption/branded-strings/encrypted-string.type';
|
||||
import { isEncryptedString } from 'src/engine/core-modules/secret-encryption/branded-strings/is-encrypted-string.util';
|
||||
import { type PlaintextString } from 'src/engine/core-modules/secret-encryption/branded-strings/plaintext-string.type';
|
||||
import { SecretEncryptionService } from 'src/engine/core-modules/secret-encryption/secret-encryption.service';
|
||||
import { ConfigVariables } from 'src/engine/core-modules/twenty-config/config-variables';
|
||||
import { type ConfigVariablesMetadataMap } from 'src/engine/core-modules/twenty-config/decorators/config-variables-metadata.decorator';
|
||||
@@ -51,22 +53,22 @@ export class EncryptSensitiveConfigStorageSlowInstanceCommand implements SlowIns
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
rawValue === '' ||
|
||||
rawValue.startsWith(SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX)
|
||||
) {
|
||||
if (rawValue === '' || isEncryptedString(rawValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const plaintext =
|
||||
this.secretEncryptionService.decryptVersioned(rawValue);
|
||||
const plaintext = this.secretEncryptionService.decryptVersioned(
|
||||
rawValue as EncryptedString,
|
||||
);
|
||||
|
||||
if (!isDefined(plaintext)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const encrypted =
|
||||
this.secretEncryptionService.encryptVersioned(plaintext);
|
||||
this.secretEncryptionService.encryptVersioned(
|
||||
plaintext as PlaintextString,
|
||||
);
|
||||
|
||||
await dataSource.query(
|
||||
`UPDATE "core"."keyValuePair"
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { DataSource, QueryRunner } from 'typeorm';
|
||||
|
||||
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 { SimpleSecretEncryptionUtil } from 'src/engine/core-modules/two-factor-authentication/utils/simple-secret-encryption.util';
|
||||
@@ -59,7 +60,7 @@ export class EncryptTotpSecretsSlowInstanceCommand implements SlowInstanceComman
|
||||
}
|
||||
|
||||
const encryptedValue = this.secretEncryptionService.encryptVersioned(
|
||||
plaintext,
|
||||
plaintext as PlaintextString,
|
||||
{ workspaceId: row.workspaceId },
|
||||
);
|
||||
|
||||
|
||||
+23
-5
@@ -1,7 +1,12 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { DataSource, QueryRunner } from 'typeorm';
|
||||
|
||||
import { type ImapSmtpCaldavParams } from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type';
|
||||
import {
|
||||
type EncryptedImapSmtpCaldavParams,
|
||||
type ImapSmtpCaldavParams,
|
||||
type PlaintextImapSmtpCaldavParams,
|
||||
} from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.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 { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
import { SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/slow-instance-command.interface';
|
||||
@@ -16,6 +21,9 @@ const CHECK_CONSTRAINT_NAME =
|
||||
type ConnectionParametersRow = {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
// Pre-backfill rows can hold either plaintext or already-v2 passwords.
|
||||
// Either form is structurally a `string` at the JSONB layer; the brand
|
||||
// type is a phantom marker only.
|
||||
connectionParameters: ImapSmtpCaldavParams | null;
|
||||
};
|
||||
|
||||
@@ -67,7 +75,7 @@ export class EncryptConnectionParametersSlowInstanceCommand implements SlowInsta
|
||||
continue;
|
||||
}
|
||||
|
||||
const plaintextOnly: ImapSmtpCaldavParams = {};
|
||||
const plaintextOnly: PlaintextImapSmtpCaldavParams = {};
|
||||
|
||||
for (const protocol of ACCOUNT_TYPES) {
|
||||
const protocolParams = row.connectionParameters[protocol];
|
||||
@@ -78,7 +86,13 @@ export class EncryptConnectionParametersSlowInstanceCommand implements SlowInsta
|
||||
SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX,
|
||||
)
|
||||
) {
|
||||
plaintextOnly[protocol] = protocolParams;
|
||||
// Upstream filter guarantees this protocol's password is
|
||||
// plaintext (no `enc:v2:` prefix); brand the leaf in-place so
|
||||
// the encryption service can consume it.
|
||||
plaintextOnly[protocol] = {
|
||||
...protocolParams,
|
||||
password: protocolParams.password as PlaintextString,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,8 +104,12 @@ export class EncryptConnectionParametersSlowInstanceCommand implements SlowInsta
|
||||
},
|
||||
);
|
||||
|
||||
const merged: ImapSmtpCaldavParams = {
|
||||
...row.connectionParameters,
|
||||
// Pre-backfill row may already contain a mix of plaintext and
|
||||
// already-encrypted protocols; we trust the entity-level brand on
|
||||
// the post-merge result since each protocol is either freshly
|
||||
// encrypted above or was already an `enc:v2:` envelope.
|
||||
const merged: EncryptedImapSmtpCaldavParams = {
|
||||
...(row.connectionParameters as EncryptedImapSmtpCaldavParams),
|
||||
...encrypted,
|
||||
};
|
||||
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
import { DataSource, QueryRunner } from 'typeorm';
|
||||
|
||||
import { isEncryptedString } from 'src/engine/core-modules/secret-encryption/branded-strings/is-encrypted-string.util';
|
||||
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 { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
import { SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/slow-instance-command.interface';
|
||||
|
||||
const BACKFILL_BATCH_SIZE = 500;
|
||||
|
||||
const VALUE_CHECK_CONSTRAINT_NAME = 'CHK_applicationVariable_value_encrypted';
|
||||
|
||||
const V2_ENCRYPTED_LIKE_PATTERN = `${SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX}%`;
|
||||
|
||||
type ApplicationVariableRow = {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
// Encrypts all remaining plaintext non-secret application variable rows
|
||||
// into the enc:v2 envelope, then tightens the CHECK constraint to require
|
||||
// encryption for ALL rows (not just isSecret=true ones).
|
||||
@RegisteredInstanceCommand('2.9.0', 1798400000000, { type: 'slow' })
|
||||
export class EncryptNonSecretApplicationVariableSlowInstanceCommand
|
||||
implements SlowInstanceCommand
|
||||
{
|
||||
private readonly logger = new Logger(
|
||||
EncryptNonSecretApplicationVariableSlowInstanceCommand.name,
|
||||
);
|
||||
|
||||
constructor(
|
||||
private readonly secretEncryptionService: SecretEncryptionService,
|
||||
) {}
|
||||
|
||||
async runDataMigration(dataSource: DataSource): Promise<void> {
|
||||
let cursor = '00000000-0000-0000-0000-000000000000';
|
||||
let totalEncrypted = 0;
|
||||
|
||||
while (true) {
|
||||
const rows: ApplicationVariableRow[] = await dataSource.query(
|
||||
`SELECT id, "workspaceId", "value"
|
||||
FROM "core"."applicationVariable"
|
||||
WHERE id > $1
|
||||
AND "isSecret" = false
|
||||
AND "value" <> ''
|
||||
AND "value" NOT LIKE $2
|
||||
ORDER BY id
|
||||
LIMIT $3`,
|
||||
[cursor, V2_ENCRYPTED_LIKE_PATTERN, BACKFILL_BATCH_SIZE],
|
||||
);
|
||||
|
||||
if (rows.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
let batchEncrypted = 0;
|
||||
|
||||
for (const row of rows) {
|
||||
if (isEncryptedString(row.value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const encryptedValue = this.secretEncryptionService.encryptVersioned(
|
||||
row.value as PlaintextString,
|
||||
{ workspaceId: row.workspaceId },
|
||||
);
|
||||
|
||||
await dataSource.query(
|
||||
`UPDATE "core"."applicationVariable"
|
||||
SET "value" = $2
|
||||
WHERE id = $1`,
|
||||
[row.id, encryptedValue],
|
||||
);
|
||||
|
||||
batchEncrypted++;
|
||||
}
|
||||
|
||||
totalEncrypted += batchEncrypted;
|
||||
cursor = rows[rows.length - 1].id;
|
||||
|
||||
this.logger.log(
|
||||
`Encrypted ${batchEncrypted} non-secret application variables in this batch (${totalEncrypted} total so far)`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Finished encrypting non-secret application variables: ${totalEncrypted} rows encrypted`,
|
||||
);
|
||||
}
|
||||
|
||||
// Tightens the CHECK constraint: all values must now be either empty
|
||||
// or in the enc:v2 envelope, regardless of isSecret.
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."applicationVariable"
|
||||
DROP CONSTRAINT IF EXISTS "${VALUE_CHECK_CONSTRAINT_NAME}"`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."applicationVariable"
|
||||
ADD CONSTRAINT "${VALUE_CHECK_CONSTRAINT_NAME}"
|
||||
CHECK ("value" = '' OR "value" LIKE '${V2_ENCRYPTED_LIKE_PATTERN}')`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."applicationVariable"
|
||||
DROP CONSTRAINT IF EXISTS "${VALUE_CHECK_CONSTRAINT_NAME}"`,
|
||||
);
|
||||
|
||||
// Restore the original constraint that allowed plaintext for non-secret rows
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."applicationVariable"
|
||||
ADD CONSTRAINT "${VALUE_CHECK_CONSTRAINT_NAME}"
|
||||
CHECK ("isSecret" = false OR "value" = '' OR "value" LIKE '${V2_ENCRYPTED_LIKE_PATTERN}')`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+13
-11
@@ -22,8 +22,6 @@ import { AddCacheTokensToAgentChatThreadFastInstanceCommand } from 'src/database
|
||||
import { AddLogoToApplicationFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-2/2-2-instance-command-fast-1777539664664-add-logo-to-application';
|
||||
import { AddSubFieldNameToViewSortEarlyFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1747234200000-add-sub-field-name-to-view-sort';
|
||||
import { AddRelationTargetFieldMetadataIdToViewFilterEarlyFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1747234300000-add-relation-target-field-metadata-id-to-view-filter';
|
||||
import { AddRelationTargetFieldMetadataIdToViewFilterEarly2_4FastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1747234400000-add-relation-target-field-metadata-id-to-view-filter';
|
||||
import { AddRelationTargetFieldMetadataIdToViewFilterEarly2_5FastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1747234500000-add-relation-target-field-metadata-id-to-view-filter';
|
||||
import { AddUpgradeMigrationWorkspaceIdIndexFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1777308014234-add-upgrade-migration-workspace-id-index';
|
||||
import { AddDeletedAtToAgentChatThreadFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1777682000000-add-deleted-at-to-agent-chat-thread';
|
||||
import { ConnectionProviderSyncableEntityFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1777896012579-connection-provider-syncable-entity';
|
||||
@@ -32,30 +30,33 @@ import { TransformApplicationVariableToSyncableEntityFastInstanceCommand } from
|
||||
import { AddToolAndWorkflowActionTriggerSettingsFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1797000001000-add-tool-and-workflow-action-trigger-settings';
|
||||
import { BackfillApplicationVariableUniversalIdentifierSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-slow-1777966965588-backfill-application-variable-universal-identifier';
|
||||
import { MigrateToolTriggerSettingsSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-slow-1797000002000-migrate-tool-trigger-settings';
|
||||
import { AddRelationTargetFieldMetadataIdToViewFilterEarly2_4FastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1747234400000-add-relation-target-field-metadata-id-to-view-filter';
|
||||
import { AddMetadataToBillingPriceFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1777100000000-add-metadata-to-billing-price';
|
||||
import { RenamePermissionFlagToRolePermissionFlagFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340020-rename-permission-flag-to-role-permission-flag';
|
||||
import { PermissionFlagSyncableEntityFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340021-permission-flag-syncable-entity';
|
||||
import { LinkRolePermissionFlagToPermissionFlagFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340022-link-role-permission-flag-to-permission-flag';
|
||||
import { BackfillRolePermissionFlagPermissionFlagIdSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-slow-1778235340023-backfill-role-permission-flag-permission-flag-id';
|
||||
import { AddEmailGroupChannelTypeFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1778256809018-add-email-group-channel-type';
|
||||
import { AddApplicationIdToPublicDomainFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1798000003000-add-application-id-to-public-domain';
|
||||
import { AddRelationTargetFieldMetadataIdToViewFilterEarly2_5FastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1747234500000-add-relation-target-field-metadata-id-to-view-filter';
|
||||
import { AddSubFieldNameToViewSortFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1778502963794-add-sub-field-name-to-view-sort';
|
||||
import { AddIsInternalMessagesImportEnabledFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1778525104406-add-is-internal-messages-import-enabled';
|
||||
import { CreateSigningKeyTableFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1778550000000-create-signing-key-table';
|
||||
import { DropPostgresCredentialsTableFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1798500000000-drop-postgres-credentials-table';
|
||||
import { EncryptConnectedAccountTokensSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000004000-encrypt-connected-account-tokens';
|
||||
import { AddRelationTargetFieldMetadataIdToViewFilterFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1798000005000-add-relation-target-field-metadata-id-to-view-filter';
|
||||
import { EmailingDomainTenantStatusAndGlobalUniquenessFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-9/2-9-instance-command-fast-1799000020000-emailing-domain-tenant-status-and-global-uniqueness';
|
||||
import { EncryptApplicationVariableSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000005000-encrypt-application-variable';
|
||||
import { EncryptApplicationRegistrationVariableSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000006000-encrypt-application-registration-variable';
|
||||
import { EncryptSigningKeyPrivateKeysSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000007000-encrypt-signing-key-private-keys';
|
||||
import { EncryptSensitiveConfigStorageSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000008000-encrypt-sensitive-config-storage';
|
||||
import { EncryptTotpSecretsSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-slow-1798000009000-encrypt-totp-secrets';
|
||||
import { EncryptConnectionParametersSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-7/2-7-instance-command-slow-1798000010000-encrypt-connection-parameters';
|
||||
import { AddSubFieldNameToViewSortFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1778502963794-add-sub-field-name-to-view-sort';
|
||||
import { DropPostgresCredentialsTableFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-5/2-5-instance-command-fast-1798500000000-drop-postgres-credentials-table';
|
||||
import { RenamePermissionFlagToRolePermissionFlagFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340020-rename-permission-flag-to-role-permission-flag';
|
||||
import { PermissionFlagSyncableEntityFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340021-permission-flag-syncable-entity';
|
||||
import { LinkRolePermissionFlagToPermissionFlagFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1778235340022-link-role-permission-flag-to-permission-flag';
|
||||
import { AddRelationTargetFieldMetadataIdToViewFilterFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1798000005000-add-relation-target-field-metadata-id-to-view-filter';
|
||||
import { AddChannelSyncStageIndexesFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1798000010000-add-channel-sync-stage-indexes';
|
||||
import { BackfillRolePermissionFlagPermissionFlagIdSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-slow-1778235340023-backfill-role-permission-flag-permission-flag-id';
|
||||
import { FinalizeRolePermissionFlagCutoverFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-7/2-7-instance-command-fast-1779600000000-finalize-role-permission-flag-cutover';
|
||||
import { EncryptConnectionParametersSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-7/2-7-instance-command-slow-1798000010000-encrypt-connection-parameters';
|
||||
import { AddSubFieldNameToIndexFieldMetadataFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-8/2-8-instance-command-fast-1798200000000-add-sub-field-name-to-index-field-metadata';
|
||||
import { DropFieldMetadataIsUniqueColumnFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-8/2-8-instance-command-fast-1798300000000-drop-field-metadata-is-unique-column';
|
||||
import { EmailingDomainTenantStatusAndGlobalUniquenessFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-9/2-9-instance-command-fast-1799000020000-emailing-domain-tenant-status-and-global-uniqueness';
|
||||
import { EncryptNonSecretApplicationVariableSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-9/2-9-instance-command-slow-1798400000000-encrypt-non-secret-application-variable';
|
||||
import { MigrateAiModelPreferencesSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-9/2-9-instance-command-slow-1799000010000-migrate-ai-model-preferences';
|
||||
|
||||
export const INSTANCE_COMMANDS = [
|
||||
@@ -116,4 +117,5 @@ export const INSTANCE_COMMANDS = [
|
||||
AddSubFieldNameToIndexFieldMetadataFastInstanceCommand,
|
||||
DropFieldMetadataIsUniqueColumnFastInstanceCommand,
|
||||
MigrateAiModelPreferencesSlowInstanceCommand,
|
||||
EncryptNonSecretApplicationVariableSlowInstanceCommand,
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user