fix: FieldMetadata default value and options better validation (#2785)

* fix: wip better field metadata validation

* fix: remove files

* fix: default value and options validation

* fix: small fix

* fix: try to limit patch

* fix: tests

* Update server/src/metadata/field-metadata/validators/is-field-metadata-options.validator.ts

Co-authored-by: Weiko <corentin@twenty.com>

* fix: lint

* fix: standard fields update security

---------

Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
Jérémy M
2023-12-06 15:19:23 +01:00
committed by GitHub
parent b09100e3f3
commit 93decaceab
25 changed files with 6369 additions and 492 deletions

View File

@ -1,17 +1,54 @@
import { Catch, UnauthorizedException } from '@nestjs/common';
import { GqlExceptionFilter } from '@nestjs/graphql';
import { ArgumentsHost, Catch, HttpException } from '@nestjs/common';
import { GqlContextType, GqlExceptionFilter } from '@nestjs/graphql';
import { GraphQLError } from 'graphql';
import { TypeORMError } from 'typeorm';
import {
AuthenticationError,
BaseGraphQLError,
ForbiddenError,
} from 'src/filters/utils/graphql-errors.util';
const graphQLPredefinedExceptions = {
401: AuthenticationError,
403: ForbiddenError,
};
@Catch()
export class ExceptionFilter implements GqlExceptionFilter {
catch(exception: Error) {
if (exception instanceof UnauthorizedException) {
throw new GraphQLError('Unauthorized', {
extensions: {
code: 'UNAUTHENTICATED',
},
});
catch(exception: HttpException | TypeORMError, host: ArgumentsHost) {
if (host.getType<GqlContextType>() !== 'graphql') {
return null;
}
if (exception instanceof TypeORMError) {
const error = new BaseGraphQLError(
exception.name,
'INTERNAL_SERVER_ERROR',
);
error.stack = exception.stack;
error.extensions['response'] = exception.message;
return error;
} else if (exception instanceof HttpException) {
let error: BaseGraphQLError;
if (exception.getStatus() in graphQLPredefinedExceptions) {
error = new graphQLPredefinedExceptions[exception.getStatus()](
exception.message,
);
} else {
error = new BaseGraphQLError(
exception.message,
exception.getStatus().toString(),
);
}
error.stack = exception.stack;
error.extensions['response'] = exception.getResponse();
return error;
}
return exception;

View File

@ -0,0 +1,135 @@
import {
ASTNode,
GraphQLError,
GraphQLFormattedError,
Source,
SourceLocation,
} from 'graphql';
declare module 'graphql' {
export interface GraphQLErrorExtensions {
exception?: {
code?: string;
stacktrace?: ReadonlyArray<string>;
};
}
}
export class BaseGraphQLError extends Error implements GraphQLError {
public extensions: Record<string, any>;
override readonly name!: string;
readonly locations: ReadonlyArray<SourceLocation> | undefined;
readonly path: ReadonlyArray<string | number> | undefined;
readonly source: Source | undefined;
readonly positions: ReadonlyArray<number> | undefined;
readonly nodes: ReadonlyArray<ASTNode> | undefined;
public originalError: Error | undefined;
[key: string]: any;
constructor(
message: string,
code?: string,
extensions?: Record<string, any>,
) {
super(message);
// if no name provided, use the default. defineProperty ensures that it stays non-enumerable
if (!this.name) {
Object.defineProperty(this, 'name', { value: 'ApolloError' });
}
if (extensions?.extensions) {
throw Error(
'Pass extensions directly as the third argument of the ApolloError constructor: `new ' +
'ApolloError(message, code, {myExt: value})`, not `new ApolloError(message, code, ' +
'{extensions: {myExt: value}})`',
);
}
this.extensions = { ...extensions, code };
}
toJSON(): GraphQLFormattedError {
return toGraphQLError(this).toJSON();
}
override toString(): string {
return toGraphQLError(this).toString();
}
get [Symbol.toStringTag](): string {
return this.name;
}
}
function toGraphQLError(error: BaseGraphQLError): GraphQLError {
return new GraphQLError(error.message, {
nodes: error.nodes,
source: error.source,
positions: error.positions,
path: error.path,
originalError: error.originalError,
extensions: error.extensions,
});
}
export class SyntaxError extends BaseGraphQLError {
constructor(message: string) {
super(message, 'GRAPHQL_PARSE_FAILED');
Object.defineProperty(this, 'name', { value: 'SyntaxError' });
}
}
export class ValidationError extends BaseGraphQLError {
constructor(message: string) {
super(message, 'GRAPHQL_VALIDATION_FAILED');
Object.defineProperty(this, 'name', { value: 'ValidationError' });
}
}
export class AuthenticationError extends BaseGraphQLError {
constructor(message: string, extensions?: Record<string, any>) {
super(message, 'UNAUTHENTICATED', extensions);
Object.defineProperty(this, 'name', { value: 'AuthenticationError' });
}
}
export class ForbiddenError extends BaseGraphQLError {
constructor(message: string, extensions?: Record<string, any>) {
super(message, 'FORBIDDEN', extensions);
Object.defineProperty(this, 'name', { value: 'ForbiddenError' });
}
}
export class PersistedQueryNotFoundError extends BaseGraphQLError {
constructor() {
super('PersistedQueryNotFound', 'PERSISTED_QUERY_NOT_FOUND');
Object.defineProperty(this, 'name', {
value: 'PersistedQueryNotFoundError',
});
}
}
export class PersistedQueryNotSupportedError extends BaseGraphQLError {
constructor() {
super('PersistedQueryNotSupported', 'PERSISTED_QUERY_NOT_SUPPORTED');
Object.defineProperty(this, 'name', {
value: 'PersistedQueryNotSupportedError',
});
}
}
export class UserInputError extends BaseGraphQLError {
constructor(message: string, extensions?: Record<string, any>) {
super(message, 'BAD_USER_INPUT', extensions);
Object.defineProperty(this, 'name', { value: 'UserInputError' });
}
}