来自客户端的错误数据通过了 GraphQL 验证

Wrong data from client passes GraphQL validation

我已经使用 GraphQL 在 NestJS 服务器上使用 React 和 Apollo 客户端制作了简单的 CRUD 应用 API。

我有这个简单的突变:

schema.gql:

type Mutation {
  createUser(input: CreateUserInput!): User! // CreateUserInput type you can see in user.input.ts below
  updateUser(id: ID!, input: UpdateUserInput!): User!
  deleteUser(id: ID!): User!
}

user.input.ts:

import { InputType, Field } from "@nestjs/graphql";
import { EmailScalar } from "../email.scalar-type";

@InputType()
export class CreateUserInput {
    // EmailScalar is a custom Scalar GraphQL Type that i took from the internet and it worked well
    @Field(() => EmailScalar)
    readonly email: string;
    @Field()
    readonly name: string;
}

"EmailScalar" 类型检查 "email" 输入是否基本上具有 *@*.* 格式

当我像这样对 GraphQL API 进行 createUser 查询时:

It cannot pass validation (因为电子邮件类型工作正常)


但是当从客户端发送查询时 - 它通过了验证:

NestJS server log(来自下面的代码)

users.resolver.ts:

@Mutation(() => User)
async createUser(@Args('input') input: CreateUserInput) { // Type from user.input.ts
   Logger.log(input); // log from screenshot, so if it's here it passed validation
   return this.usersService.create(input); // usersService makes requests to MongoDB
}

And it gets into MongoDB


这是客户端部分:

App.tsx:

...

// CreateUserInput class is not imported to App.tsx (it is at server part) but it seems to be fine with it
const ADD_USER = gql`
  mutation AddMutation($input: CreateUserInput!) {
    createUser(input: $input) {
      id
      name
      email
    }
  }
`

function App(props: any) {
  const { loading, error, data } = useQuery(GET_USERS);

  const [addUser] = useMutation(
    ADD_USER,
    {
      update: (cache: any, { data: { createUser } }: any) => {
        const { users } = cache.readQuery({ query: GET_USERS });
        cache.writeQuery({
          query: GET_USERS,
          data: {
            users: [createUser, ...users],
          },
        })
      }
    }
  );

...

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error :(</p>;

  return <UserTable users={data.users} addUser={addUser} updateUser={updateUser} deleteUser={deleteUser} />;
}

谁能给我解释一下,客户端查询是如何通过验证的,我做错了什么?

即使是两个空字符串也能通过

之前从未使用过 NestJS、Apollo、React 或 GraphQL,所以我有点迷茫。

For full code: https://github.com/N238635/nest-react-crud-test

自定义标量的方法是这样定义的:

parseValue(value: string): string {
  return value;
}

serialize(value: string): string {
  return value;
}

parseLiteral(ast: ValueNode): string {
  if (ast.kind !== Kind.STRING) {
    throw new GraphQLError('Query error: Can only parse strings got a: ' + ast.kind, [ast]);
  }

  // Regex taken from: 
  var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
  if (!re.test(ast.value)) {
    throw new GraphQLError('Query error: Not a valid Email', [ast]);
  }

  return ast.value;
}

parseLiteral 在解析查询中的文字值时调用(即用双引号引起来的文字字符串)。 parseValue 在解析变量值时被调用。当您的客户端发送查询时,它会将值作为变量而不是文字值发送。所以使用 parseValue 而不是 parseLiteral。但是您的 parseValue 不进行任何类型的验证——您只是 return 原样的值。您需要在 both 方法中实现验证逻辑。

实施 serialize 方法也是一个好主意,这样您的标量就可以用于输入和响应验证。