fix: Decrypt encrypted front component variables (#23494)

## Summary

Fixes #23492

Fixes front-component application variables returning their encrypted
at-rest value instead of their configured plaintext value.

Non-secret application variables (`isSecret: false`) are now decrypted
server-side before being injected into the front-component environment.
Secret variables remain excluded and are never decrypted or exposed to
the browser.

## Root cause

The front-component resolver filtered secret application variables
correctly, but forwarded the cached `encryptedValue` directly. As a
result, `getApplicationVariable()` returned an `enc:v2:...` envelope
rather than the configured value.

## Changes

- Decrypt recognized versioned envelopes for non-secret application
variables.
- Preserve empty and legacy/plain values unchanged for backwards
compatibility.
- Add `SecretEncryptionModule` to the front-component module.
- Add coverage for:
  - decrypting public variables;
  - retaining plaintext compatibility;
  - excluding secret variables without attempting decryption.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23494?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: prastoin <paul@twenty.com>
This commit is contained in:
Remi Huigen
2026-07-30 18:49:52 +02:00
committed by GitHub
parent a747e62970
commit 830404b215
13 changed files with 500 additions and 432 deletions
@@ -0,0 +1,21 @@
import gql from 'graphql-tag';
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
const DEFAULT_FRONT_COMPONENTS_GQL_FIELDS = `
id
name
universalIdentifier
applicationId
`;
export const findFrontComponentsQueryFactory = ({
gqlFields = DEFAULT_FRONT_COMPONENTS_GQL_FIELDS,
}: Partial<PerformMetadataQueryParams<undefined>>) => ({
query: gql`
query FrontComponents {
frontComponents {
${gqlFields}
}
}
`,
});
@@ -0,0 +1,36 @@
import { findFrontComponentsQueryFactory } from 'test/integration/metadata/suites/front-component/utils/find-front-components-query-factory.util';
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
import { type FrontComponentDTO } from 'src/engine/metadata-modules/front-component/dtos/front-component.dto';
export const findFrontComponents = async ({
gqlFields,
expectToFail = false,
token,
}: Partial<PerformMetadataQueryParams<undefined>>): CommonResponseBody<{
frontComponents: FrontComponentDTO[];
}> => {
const graphqlOperation = findFrontComponentsQueryFactory({ gqlFields });
const response = await makeMetadataAPIRequest(graphqlOperation, token);
if (expectToFail === true) {
warnIfNoErrorButExpectedToFail({
response,
errorMessage: 'Finding front components should have failed but did not',
});
}
if (expectToFail === false) {
warnIfErrorButNotExpectedToFail({
response,
errorMessage: 'Finding front components has failed but should not',
});
}
return { data: response.body.data, errors: response.body.errors };
};