dea1f89904
## Summary - Inject non-secret application variables (`isSecret: false`) into front component `process.env` via the existing Web Worker `setWorkerEnv` mechanism - Filter secret variables server-side in the resolver so they never reach the browser - Set application variables before system variables (`TWENTY_API_URL`, `TWENTY_APP_ACCESS_TOKEN`) to prevent override - Wire up environment variable keys in the logic function code editor for TypeScript autocomplete ## Test plan - [x] Unit tests for `buildNonSecretEnvVar` (6 passing) - [x] Typecheck passes for `twenty-front` and `twenty-server` - [x] Install an app with both `isSecret: false` and `isSecret: true` variables, open a front component, verify only non-secret vars appear in `process.env` - [x] Open a logic function editor, verify autocomplete suggests declared variable keys
145 lines
6.1 KiB
TypeScript
145 lines
6.1 KiB
TypeScript
import { Inject, UseGuards, UseInterceptors } from '@nestjs/common';
|
|
import { Args, Mutation, Query } from '@nestjs/graphql';
|
|
|
|
import { PermissionFlagType } from 'twenty-shared/constants';
|
|
import { isDefined } from 'twenty-shared/utils';
|
|
|
|
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
|
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
|
import { ApplicationTokenService } from 'src/engine/core-modules/auth/token/services/application-token.service';
|
|
import { type AuthContextUser } from 'src/engine/core-modules/auth/types/auth-context.type';
|
|
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
|
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
|
|
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
|
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
|
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
|
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
|
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
|
import { fromFlatFrontComponentToFrontComponentDto } from 'src/engine/metadata-modules/flat-front-component/utils/from-flat-front-component-to-front-component-dto.util';
|
|
import { CreateFrontComponentInput } from 'src/engine/metadata-modules/front-component/dtos/create-front-component.input';
|
|
import { FrontComponentDTO } from 'src/engine/metadata-modules/front-component/dtos/front-component.dto';
|
|
import { UpdateFrontComponentInput } from 'src/engine/metadata-modules/front-component/dtos/update-front-component.input';
|
|
import { FrontComponentService } from 'src/engine/metadata-modules/front-component/front-component.service';
|
|
import { FrontComponentGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/front-component/interceptors/front-component-graphql-api-exception.interceptor';
|
|
import { stripSecretFromApplicationVariables } from 'src/engine/metadata-modules/front-component/utils/strip-secret-from-application-variables';
|
|
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
|
import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor';
|
|
|
|
@UseGuards(WorkspaceAuthGuard)
|
|
@UseInterceptors(
|
|
WorkspaceMigrationGraphqlApiExceptionInterceptor,
|
|
FrontComponentGraphqlApiExceptionInterceptor,
|
|
)
|
|
@MetadataResolver(() => FrontComponentDTO)
|
|
export class FrontComponentResolver {
|
|
constructor(
|
|
@Inject(FrontComponentService)
|
|
private readonly frontComponentService: FrontComponentService,
|
|
@Inject(ApplicationTokenService)
|
|
private readonly applicationTokenService: ApplicationTokenService,
|
|
private readonly workspaceCacheService: WorkspaceCacheService,
|
|
) {}
|
|
|
|
@Query(() => [FrontComponentDTO])
|
|
@UseGuards(NoPermissionGuard)
|
|
async frontComponents(
|
|
@AuthWorkspace() workspace: WorkspaceEntity,
|
|
): Promise<FrontComponentDTO[]> {
|
|
return await this.frontComponentService.findAll(workspace.id);
|
|
}
|
|
|
|
@Query(() => FrontComponentDTO, { nullable: true })
|
|
@UseGuards(UserAuthGuard, NoPermissionGuard)
|
|
async frontComponent(
|
|
@Args('id', { type: () => UUIDScalarType }) id: string,
|
|
@AuthWorkspace() workspace: WorkspaceEntity,
|
|
@AuthUser() user: AuthContextUser,
|
|
@AuthUserWorkspaceId() userWorkspaceId: string,
|
|
): Promise<FrontComponentDTO | null> {
|
|
const dto = await this.frontComponentService.findById(id, workspace.id);
|
|
|
|
if (!dto) {
|
|
return null;
|
|
}
|
|
|
|
const tokenPair =
|
|
await this.applicationTokenService.generateApplicationTokenPair({
|
|
applicationId: dto.applicationId,
|
|
workspaceId: workspace.id,
|
|
userWorkspaceId,
|
|
userId: user.id,
|
|
});
|
|
|
|
const { applicationVariableMaps } =
|
|
await this.workspaceCacheService.getOrRecompute(workspace.id, [
|
|
'applicationVariableMaps',
|
|
]);
|
|
|
|
const variableUniversalIdentifiers =
|
|
applicationVariableMaps.universalIdentifiersByApplicationId[
|
|
dto.applicationId
|
|
] ?? [];
|
|
|
|
const flatApplicationVariables = variableUniversalIdentifiers
|
|
.map(
|
|
(universalIdentifier) =>
|
|
applicationVariableMaps.byUniversalIdentifier[universalIdentifier],
|
|
)
|
|
.filter(isDefined);
|
|
|
|
const applicationVariables = stripSecretFromApplicationVariables(
|
|
flatApplicationVariables,
|
|
);
|
|
|
|
return {
|
|
...dto,
|
|
applicationTokenPair: tokenPair,
|
|
applicationVariables,
|
|
};
|
|
}
|
|
|
|
@Mutation(() => FrontComponentDTO)
|
|
@UseGuards(SettingsPermissionGuard(PermissionFlagType.APPLICATIONS))
|
|
async createFrontComponent(
|
|
@Args('input') input: CreateFrontComponentInput,
|
|
@AuthWorkspace() workspace: WorkspaceEntity,
|
|
): Promise<FrontComponentDTO> {
|
|
const flatFrontComponent = await this.frontComponentService.createOne({
|
|
input,
|
|
workspaceId: workspace.id,
|
|
});
|
|
|
|
return fromFlatFrontComponentToFrontComponentDto(flatFrontComponent);
|
|
}
|
|
|
|
@Mutation(() => FrontComponentDTO)
|
|
@UseGuards(SettingsPermissionGuard(PermissionFlagType.APPLICATIONS))
|
|
async updateFrontComponent(
|
|
@Args('input') input: UpdateFrontComponentInput,
|
|
@AuthWorkspace() workspace: WorkspaceEntity,
|
|
): Promise<FrontComponentDTO> {
|
|
const flatFrontComponent = await this.frontComponentService.updateOne({
|
|
id: input.id,
|
|
update: input.update,
|
|
workspaceId: workspace.id,
|
|
});
|
|
|
|
return fromFlatFrontComponentToFrontComponentDto(flatFrontComponent);
|
|
}
|
|
|
|
@Mutation(() => FrontComponentDTO)
|
|
@UseGuards(SettingsPermissionGuard(PermissionFlagType.APPLICATIONS))
|
|
async deleteFrontComponent(
|
|
@Args('id', { type: () => UUIDScalarType }) id: string,
|
|
@AuthWorkspace() workspace: WorkspaceEntity,
|
|
): Promise<FrontComponentDTO> {
|
|
const flatFrontComponent = await this.frontComponentService.destroyOne({
|
|
id,
|
|
workspaceId: workspace.id,
|
|
});
|
|
|
|
return fromFlatFrontComponentToFrontComponentDto(flatFrontComponent);
|
|
}
|
|
}
|