diff --git a/packages/api/src/directives.ts b/packages/api/src/directives.ts index fd54cbb66..435166539 100644 --- a/packages/api/src/directives.ts +++ b/packages/api/src/directives.ts @@ -1,32 +1,41 @@ -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' 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 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 && 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..22404534f 100644 --- a/packages/api/src/scalars.ts +++ b/packages/api/src/scalars.ts @@ -8,10 +8,15 @@ export class SanitizedString extends GraphQLScalarType { constructor( type: GraphQLScalarType, allowedTags?: string[], - maxLength?: number + maxLength?: number, + pattern?: string ) { super({ - name: `SanitizedString_${allowedTags}_${maxLength}`, + // 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) { @@ -25,6 +30,9 @@ export class SanitizedString extends GraphQLScalarType { `Specified value cannot be longer than ${maxLength} characters` ) } + if (pattern && !new RegExp(pattern).test(value)) { + throw new Error(`Specified value does not match pattern`) + } return sanitize(value, { allowedTags: allowedTags || [] }) }, @@ -36,6 +44,9 @@ export class SanitizedString extends GraphQLScalarType { `Specified value cannot be longer than ${maxLength} characters` ) } + if (pattern && !new RegExp(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) }