Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(schema): Initial version of schema definitions and resolvers #2441

Merged
merged 11 commits into from
Sep 15, 2021
Merged

Conversation

daffl
Copy link
Member

@daffl daffl commented Aug 30, 2021

This pull request implements the first version of @feathersjs/schema as discussed in #2312. It features schema definitions based on JSON schema with static TypeScript type inference as well as a dynamic resolver system. Specifically:

  • Schemas are used for data type definitions and simple (synchronous) validations
  • Resolvers are used for modifying data based on the context like
    • Populating associations
    • Securing queries and e.g. limiting requests to a user
    • Removing protected properties for external requests or users without the right permissions
    • Hashing passwords and validating dynamic password policies etc.

There may be further iterations necessary but I'm planning on including it in the next pre-release because it is required for further CLI work (done in #2425).

Here is an example for a user schema definition and resolvers with protected passwords and query limitations (non-admins can only see and change their own data):

import {
  schema, resolve, Infer, resolveResult,
  propertyQuery, validateQuery, resolveQuery
} from '@feathersjs/schema';

export const userSchema = schema({
  type: 'object',
  additionalProperties: false,
  required: ['email', 'password'],
  properties: {
    id: { type: 'number' },
    email: { type: 'string' },
    password: { type: 'string' }
  }
} as const);

export type User = Infer<typeof userSchema>;

export const userDataResolver = resolve<User, HookContext<Application>>({
  properties: {
    password: async (value) => {
      // Return a hashed version of the password before storing it in the database
      return bcrypt(value);
    }
  }
});

export const userResultResolver = resolve<User, HookContext<Application>>({
  properties: {
    password: async (value, _user, context) => {
      // Do not return the password for external requests
      return context.params.provider ? undefined : value;
    }
  }
});

export const userQuerySchema = schema({
  type: 'object',
  additionalProperties: false,
  properties: {
    $limit: {
      type: 'number',
      minimum: 0,
      maximum: 100
    },
    $skip: {
      type: 'number'
    },
    // This will allow all of the Feathers query syntax
    id: propertyQuery({
      type: 'number'
    })
  }
} as const);

export type UserQuery = Infer<typeof userQuerySchema>;

export const userQueryResolver = resolve<User, HookContext<Application>>({
  properties: {
    id: async (value, _user, context) => {
      // Non admins can only query and modify their own data
      if(context.params?.user && context.params.user.role !== 'admin') {
        return context.params.user.id;
      }

      return value;
    }
  }
});

app.service('users').hooks([
  validateQuery(userQuerySchema),
  resolveQuery(userQueryResolver),
  resolveResult(userResultResolver
]);

@daffl daffl marked this pull request as ready for review September 5, 2021 01:07
@daffl daffl merged commit c57a5cd into dove Sep 15, 2021
@daffl daffl deleted the schema branch September 15, 2021 04:23
@deskoh
Copy link
Contributor

deskoh commented Sep 21, 2021

@daffl Is it possible to allow a singleton ajv instance to be configured? Custom AJV instance would be useful for options like removeAdditional and can be used for data sanitization (e.g. const sanitizedData = myschema.validate(data)).

@daffl
Copy link
Member Author

daffl commented Jan 12, 2022

@deskoh Passing a custom AJV instance is not possible and documented in https://dove.docs.feathersjs.com/api/schema/schema.html#options

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants