Files
twenty/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/create-logic-function.input.ts
T
Félix Malfait 67074a7581 Harden server-side input validation and auth defaults (#18018)
## Summary

- **File storage (LocalDriver):** Add realpath resolution and symlink
rejection to `writeFile`, `downloadFile`, and `downloadFolder` — brings
them in line with the existing `readFile` protections. Includes unit
tests.
- **JWT:** Pin signing/verification to HS256 explicitly.
- **Auth:** Revoke active refresh tokens when a user changes their
password.
- **Logic functions:** Validate `handlerName` as a safe JS identifier at
both DTO and runtime level, preventing injection into the generated
runner script.
- **User entity:** Remove `passwordHash` from the GraphQL schema
(`@Field` decorator removed, column stays).
- **Query params:** Use `crypto.randomBytes` instead of `Math.random`
for SQL parameter name generation.
- **Exception filter:** Mirror the request `Origin` header instead of
sending `Access-Control-Allow-Origin: *`.

## Test plan

- [x] `local.driver.spec.ts` — writeFile rejects symlinks, downloadFile
rejects paths outside storage
- [ ] Verify JWT auth flow still works (login, token refresh)
- [ ] Verify password change invalidates existing sessions
- [ ] Verify logic function creation with valid/invalid handler names
- [ ] Verify file upload/download in dev environment


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-02-18 21:41:01 +01:00

104 lines
2.3 KiB
TypeScript

import { Field, InputType } from '@nestjs/graphql';
import {
IsBoolean,
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsString,
IsUUID,
Matches,
Max,
Min,
} from 'class-validator';
import graphqlTypeJson from 'graphql-type-json';
import {
CronTriggerSettings,
DatabaseEventTriggerSettings,
HttpRouteTriggerSettings,
} from 'twenty-shared/application';
import type { InputJsonSchema } from 'twenty-shared/logic-function';
import type { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@InputType()
export class CreateLogicFunction {
@IsUUID()
@IsOptional()
@Field(() => UUIDScalarType, { nullable: true })
id?: string;
@IsUUID()
@IsOptional()
@Field(() => UUIDScalarType, { nullable: true })
universalIdentifier?: string;
@IsString()
@IsNotEmpty()
@Field()
name: string;
@IsString()
@IsOptional()
@Field({ nullable: true })
description?: string;
@IsNumber()
@Field({ nullable: true })
@Min(1)
@Max(900)
@IsOptional()
timeoutSeconds?: number;
@Field(() => graphqlTypeJson, { nullable: false })
@IsObject()
toolInputSchema: InputJsonSchema;
@IsBoolean()
@Field({ nullable: true })
@IsOptional()
isTool?: boolean;
@IsBoolean()
@Field({ nullable: false })
isBuildUpToDate: boolean;
@IsString()
@Field({ nullable: true })
@IsOptional()
checksum?: string;
@IsString()
@Matches(/^[a-zA-Z_$][a-zA-Z0-9_$]*$/, {
message: 'handlerName must be a valid JavaScript identifier',
})
@Field({ nullable: false })
handlerName: string;
@IsString()
@Field({ nullable: false })
sourceHandlerPath: string;
@IsString()
@Field({ nullable: false })
builtHandlerPath: string;
@IsObject()
@Field(() => graphqlTypeJson, { nullable: true })
@IsOptional()
cronTriggerSettings?: JsonbProperty<CronTriggerSettings>;
@IsObject()
@Field(() => graphqlTypeJson, { nullable: true })
@IsOptional()
databaseEventTriggerSettings?: JsonbProperty<DatabaseEventTriggerSettings>;
@IsObject()
@Field(() => graphqlTypeJson, { nullable: true })
@IsOptional()
httpRouteTriggerSettings?: JsonbProperty<HttpRouteTriggerSettings>;
}