From 928ddd69a94089ec6983783799f5695185161e18 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 13 Apr 2022 12:17:12 +0800 Subject: [PATCH 1/3] validate color input in label as rgb hex value --- packages/api/src/directives.ts | 13 ++++++++++--- packages/api/src/generated/graphql.ts | 1 + packages/api/src/generated/schema.graphql | 2 +- packages/api/src/scalars.ts | 9 +++++++-- packages/api/src/schema.ts | 3 ++- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/packages/api/src/directives.ts b/packages/api/src/directives.ts index fd54cbb66..b4a32bf5e 100644 --- a/packages/api/src/directives.ts +++ b/packages/api/src/directives.ts @@ -1,4 +1,4 @@ -import { mapSchema, getDirective, MapperKind } from '@graphql-tools/utils' +import { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils' import { GraphQLNonNull, GraphQLScalarType, GraphQLSchema } from 'graphql' import { SanitizedString } from './scalars' @@ -14,19 +14,26 @@ export const sanitizeDirectiveTransformer = (schema: GraphQLSchema) => { const allowedTags = sanitizeDirective[0].allowedTags as | string[] | undefined + const pattern = sanitizeDirective[0].pattern as RegExp | undefined if ( fieldConfig.type instanceof GraphQLNonNull && fieldConfig.type.ofType instanceof GraphQLScalarType ) { fieldConfig.type = new GraphQLNonNull( - new SanitizedString(fieldConfig.type.ofType, allowedTags, maxLength) + new SanitizedString( + fieldConfig.type.ofType, + allowedTags, + maxLength, + pattern + ) ) } else if (fieldConfig.type instanceof GraphQLScalarType) { fieldConfig.type = new SanitizedString( fieldConfig.type, allowedTags, - maxLength + maxLength, + pattern ) } else { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index d14428b39..d1e733acc 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -2501,6 +2501,7 @@ export type ResolversParentTypes = { export type SanitizeDirectiveArgs = { allowedTags?: Maybe>>; maxLength?: Maybe; + pattern?: Maybe; }; export type SanitizeDirectiveResolver = DirectiveResolverFn; diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index e3c5630a8..2f77183e1 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -1,4 +1,4 @@ -directive @sanitize(allowedTags: [String], maxLength: Int) on INPUT_FIELD_DEFINITION +directive @sanitize(allowedTags: [String], maxLength: Int, pattern: String) on INPUT_FIELD_DEFINITION type ArchiveLinkError { errorCodes: [ArchiveLinkErrorCode!]! diff --git a/packages/api/src/scalars.ts b/packages/api/src/scalars.ts index 2be928e6e..454b7c38e 100644 --- a/packages/api/src/scalars.ts +++ b/packages/api/src/scalars.ts @@ -8,10 +8,11 @@ export class SanitizedString extends GraphQLScalarType { constructor( type: GraphQLScalarType, allowedTags?: string[], - maxLength?: number + maxLength?: number, + pattern?: RegExp ) { super({ - name: `SanitizedString_${allowedTags}_${maxLength}`, + name: `SanitizedString_${allowedTags}_${maxLength}_${pattern}`, description: 'Source string that was sanitized', serialize(value: string) { @@ -24,6 +25,8 @@ export class SanitizedString extends GraphQLScalarType { throw new Error( `Specified value cannot be longer than ${maxLength} characters` ) + } else if (pattern && !pattern.test(value)) { + throw new Error(`Specified value does not match pattern`) } return sanitize(value, { allowedTags: allowedTags || [] }) }, @@ -35,6 +38,8 @@ export class SanitizedString extends GraphQLScalarType { throw new Error( `Specified value cannot be longer than ${maxLength} characters` ) + } else if (pattern && !pattern.test(value)) { + throw new Error(`Specified value does not match pattern`) } return sanitize(value, { allowedTags: allowedTags || [] }) }, diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index 43fa75719..08def9c86 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -8,6 +8,7 @@ const schema = gql` directive @sanitize( allowedTags: [String] maxLength: Int + pattern: String ) on INPUT_FIELD_DEFINITION enum SortOrder { @@ -1273,7 +1274,7 @@ const schema = gql` input CreateLabelInput { name: String! @sanitize(maxLength: 64) - color: String! + color: String! @sanitize(pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$") description: String @sanitize(maxLength: 100) } From 3b831fecb80d6c9bb2fee6012ea7299a192064c2 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 13 Apr 2022 12:20:41 +0800 Subject: [PATCH 2/3] test both max length and pattern if declared --- packages/api/src/scalars.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/api/src/scalars.ts b/packages/api/src/scalars.ts index 454b7c38e..2f0de333a 100644 --- a/packages/api/src/scalars.ts +++ b/packages/api/src/scalars.ts @@ -25,7 +25,8 @@ export class SanitizedString extends GraphQLScalarType { throw new Error( `Specified value cannot be longer than ${maxLength} characters` ) - } else if (pattern && !pattern.test(value)) { + } + if (pattern && !pattern.test(value)) { throw new Error(`Specified value does not match pattern`) } return sanitize(value, { allowedTags: allowedTags || [] }) @@ -38,7 +39,8 @@ export class SanitizedString extends GraphQLScalarType { throw new Error( `Specified value cannot be longer than ${maxLength} characters` ) - } else if (pattern && !pattern.test(value)) { + } + if (pattern && !pattern.test(value)) { throw new Error(`Specified value does not match pattern`) } return sanitize(value, { allowedTags: allowedTags || [] }) From 8ba1be456c7aa7cd130f2f3c9bd2382c435f4c85 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 13 Apr 2022 13:23:21 +0800 Subject: [PATCH 3/3] fix sanitizer invalid name --- packages/api/src/directives.ts | 16 +++++++++------- packages/api/src/scalars.ts | 12 ++++++++---- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/api/src/directives.ts b/packages/api/src/directives.ts index b4a32bf5e..435166539 100644 --- a/packages/api/src/directives.ts +++ b/packages/api/src/directives.ts @@ -5,16 +5,18 @@ import { SanitizedString } from './scalars' export const sanitizeDirectiveTransformer = (schema: GraphQLSchema) => { return mapSchema(schema, { [MapperKind.FIELD]: (fieldConfig) => { - const sanitizeDirective = getDirective(schema, fieldConfig, 'sanitize') - if (!sanitizeDirective || sanitizeDirective.length < 1) { + const sanitizeDirective = getDirective( + schema, + fieldConfig, + 'sanitize' + )?.[0] + if (!sanitizeDirective) { return fieldConfig } - const maxLength = sanitizeDirective[0].maxLength as number | undefined - const allowedTags = sanitizeDirective[0].allowedTags as - | string[] - | undefined - const pattern = sanitizeDirective[0].pattern as RegExp | undefined + const maxLength = sanitizeDirective.maxLength as number | undefined + const allowedTags = sanitizeDirective.allowedTags as string[] | undefined + const pattern = sanitizeDirective.pattern as string | undefined if ( fieldConfig.type instanceof GraphQLNonNull && diff --git a/packages/api/src/scalars.ts b/packages/api/src/scalars.ts index 2f0de333a..22404534f 100644 --- a/packages/api/src/scalars.ts +++ b/packages/api/src/scalars.ts @@ -9,10 +9,14 @@ export class SanitizedString extends GraphQLScalarType { type: GraphQLScalarType, allowedTags?: string[], maxLength?: number, - pattern?: RegExp + pattern?: string ) { super({ - name: `SanitizedString_${allowedTags}_${maxLength}_${pattern}`, + // Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ as per graphql-js + name: `SanitizedString_${allowedTags}_${maxLength}_${pattern}`.replace( + /\W/g, + '' + ), description: 'Source string that was sanitized', serialize(value: string) { @@ -26,7 +30,7 @@ export class SanitizedString extends GraphQLScalarType { `Specified value cannot be longer than ${maxLength} characters` ) } - if (pattern && !pattern.test(value)) { + if (pattern && !new RegExp(pattern).test(value)) { throw new Error(`Specified value does not match pattern`) } return sanitize(value, { allowedTags: allowedTags || [] }) @@ -40,7 +44,7 @@ export class SanitizedString extends GraphQLScalarType { `Specified value cannot be longer than ${maxLength} characters` ) } - if (pattern && !pattern.test(value)) { + if (pattern && !new RegExp(pattern).test(value)) { throw new Error(`Specified value does not match pattern`) } return sanitize(value, { allowedTags: allowedTags || [] })