How do I access another field's value in Joi validation? - validation

How do I access another field value while I am validating a field?
export const validation = {
body: Joi.object({
name: Joi.string()
.required()
.min(2)
.max(20)
email: Joi
.string()
.required()
.email()
.pattern(new Regex()) // access value name here
.when('name', {
is: Joi.required(),
then: // access value of name here
}),
}),
}
In this case, I want to access the name field value in email field validation.

You can use the custom function to access the values of an object in the schema
export const validation = {
body: Joi.object({
name: Joi.string()
.required()
.min(2)
.max(20)
email: Joi
.string()
.required()
.email()
}).custom((user, helpers) => {
const { email, name} = user;
if (your regex validation on email) {
// if email validation failed
return helpers.message({
custom: `invalid email`
});
}
if (email !== `${name}#example.com`) {
// if name validation failed
return helpers.message({
custom: `email must be ${name}#example.com`
});
}
return user;
}),
}),
}

Related

Prisma and ApolloClient: Prevent overwriting the include conditions at the backend by the frontend for relations

I have a problem, thx for any help.
With prisma we can use include with where conditions for models with a relation. If I make include conditions I get the right result. If I return it to the frontend it gets overwritten. I want to return exact my result from the backend.
I have at the frontend a query (ApolloClient, gql) like. It will return an array of comments for each post, I just want to have the first Comment for each post.
const POSTS = gql`
query posts {
posts(postId: $postId) {
id
comments{ // at the backend I have conditions for the comments
id
}
}
}
`;
Backend: Primsa and graphql nexus
Prisma Schema
model Post {
id String #id #default(cuid())
comments Comment[]
}
model Comment {
id String #id #default(cuid())
post Post #relation(fields: [postId], references: [id])
postId String
}
Nexus Model
const Post = objectType({
name: 'Post',
definition(t) {
t.model.id()
t.model.comments()
})
const Comment = objectType({
name: 'Comment',
definition(t) {
t.model.id()
t.model.post()
t.model.postId()
})
Resolver
export const posts = queryField('posts', {
type: 'Post',
list: true,
args: {
...
},
resolve: async (_parent, args: any, { prisma, request }, info) => {
const posts = await prisma.post.findMany({
include: {
comments: {
take: 1
}
}
})
console.log(posts)
//Perfect result I want to return the include condition. But at the frontend I have all
//comments
return posts
},
})
The console.log(posts) is exact what I want to return!. Every post has an Array of ONE Comment.
I return the posts and at the frontend every post has an Array of ALL Comments, what I don't want. How can I prevent that the frontend query overwrite the backend return? The fields are the same.
I can't add a comment, so I am adding this to another answer.
Like I said with my PrismaSelect plugin, you can't use nexus-plugin-prisma t.model, t.crud. You will need to use Pal.Js CLI to autoGenerate all CRUD and ObjectTypes for all models.
const Post = objectType({
name: 'Post',
definition(t) {
t.model.id()
t.model.comments() // this field will overwritten by next one so this not needed
t.list.field('comments', {
type: 'Comment',
list: true,
resolve: (parent, args, { prisma }) => {
// here parent type include all other fields but not this field
return prisma.comment.findMany({ // this query is very wrong will case N+1 issue
where: {
postId: parent.id,
},
take: 1,
})
},
})
})
Example
model User {
id Int #default(autoincrement()) #id
createdAt DateTime #default(now())
email String #unique
name String?
password String
posts Post[]
comments Comment[]
}
model Post {
id Int #default(autoincrement()) #id
published Boolean #default(false)
title String
author User? #relation(fields: [authorId], references: [id])
authorId Int?
comments Comment[]
}
model Comment {
id Int #default(autoincrement()) #id
contain String
post Post #relation(fields: [postId], references: [id])
postId Int
author User? #relation(fields: [authorId], references: [id])
authorId Int?
}
Here is my Pal.Js CLI generated type for Post model
import { objectType } from '#nexus/schema'
export const Post = objectType({
name: 'Post',
definition(t) {
t.int('id', { nullable: false })
t.boolean('published', { nullable: false })
t.string('title', { nullable: false })
t.field('author', {
nullable: true,
type: 'User',
resolve(parent: any) {
return parent['author']
},
})
t.int('authorId', { nullable: true })
t.field('comments', {
nullable: false,
list: [true],
type: 'Comment',
args: {
where: 'CommentWhereInput',
orderBy: 'CommentOrderByInput',
cursor: 'CommentWhereUniqueInput',
take: 'Int',
skip: 'Int',
distinct: 'CommentDistinctFieldEnum',
},
resolve(parent: any) {
return parent['comments']
},
})
},
})
when you use my Pal.js CLI, your frontend request will be like this
query {
findOnePost(where: {id: 1}) {
comments(where: {}, take: 1){
id
}
}
}
``
The best way to handle this issue and just query what your frontend request to use my PrismaSelect plugin.
Prisma Select takes the info: GraphQLResolveInfo object in general graphql arguments (parent, args, context, info) to select object accepted by prisma client. The approach allows a better performance since you will only be using one resolver to retrieve all your request. By doing so, it also eliminates the N + 1 issue.
Also, you can use my CLI to autogenerate all CRUD from your schema.prisma file https://paljs.com/generator/nexus
I mean I can add to my Post-ObjectType a field condition like:
const Post = objectType({
name: 'Post',
definition(t) {
t.model.id()
t.model.comments()
t.list.field('comments', {
type: 'Comment',
list: true,
resolve: (parent, args, { prisma }) => {
return prisma.comment.findMany({
where: {
postId: parent.id,
},
take: 1,
})
},
})
})
This is working. But if I understood it correct I have for every post one extra request. But I have already at the mutation resolver the right result. And I don't have the comments field at the parent (t.list.field- resolver)

How to change password after logging in?

I used the following code to change the password, but I get "Request failed with status code 400". Can someone give me an indication of where the problem is?
axios.post ('http: // localhost: 1337 / auth / reset-password', {
       code: '',
       password: '1234567',
       passwordConfirmation: '1234567',
     }
     , {
       headers: {
           Authorization: `Bearer $ {this.currentUser.jwt}`
       }
     }
     ) .then (response => {
       // Handle success.
       console.log ('Your user \' s password has been changed. ');
     })
     .catch (error => {
       // Handle error.
       console.log ('An error occurred:', error);
     });
   }
Thanks in advance
Another alternative way is by using a password reset controller. The scenario is by POST a password object to http://localhost:1337/password, the controller will validate the current password then update the password with given newPassword, and return a new jwt token.
We will post a password object as follows:
{
"identifier": "yohanes",
"password": "123456789",
"newPassword": "123456",
"confirmPassword": "123456"
}
The steps are:
Create password reset route /api/password/config/routes.json:
{
"routes": [
{
"method": "POST",
"path": "/password",
"handler": "password.index"
}
]
}
Create password reset controller at /api/password/controllers/password.js
module.exports = {
index: async ctx => {
return 'Hello World!';
}
}
Note: Don't forget to enable password index at Roles -> Permission -> Application.
Point Postman to http://localhost:1337/password. The response will display the text Hello World!.
Update the password controller:
module.exports = {
index: async ctx => {
// Get posted params
// const params = JSON.parse(ctx.request.body); //if post raw object using Postman
const params = ctx.request.body;
// The identifier is required.
if (!params.identifier) {
return ctx.badRequest(
null,
formatError({
id: 'Auth.form.error.email.provide',
message: 'Please provide your username or your e-mail.',
})
);
}
// Other params validation
.
.
.
// Get User based on identifier
const user = await strapi.query('user', 'users-permissions').findOne({username: params.identifier});
// Validate given password against user query result password
const validPassword = await strapi.plugins['users-permissions'].services.user.validatePassword(params.password, user.password);
if (!validPassword) {
return ctx.badRequest(
null,
formatError({
id: 'Auth.form.error.invalid',
message: 'Identifier or password invalid.',
})
);
} else {
// Generate new hash password
const password = await strapi.plugins['users-permissions'].services.user.hashPassword({password: params.newPassword});
// Update user password
await strapi
.query('user', 'users-permissions')
.update({ id: user.id }, { resetPasswordToken: null, password });
// Return new jwt token
ctx.send({
jwt: strapi.plugins['users-permissions'].services.jwt.issue({ id: user.id }),
user: sanitizeEntity(user.toJSON ? user.toJSON() : user, { model: strapi.query('user', 'users-permissions').model }),
});
}
}
}
Once the password object posted, the controller will update the user password and return a newly created jwt token.
The complete code can be found here. Tested on Strapi v.3.3.2
You will have to use the PUT /users/:id route (from the User API)
If you want this route used by a user, you will have to create a isOwner policy and apply it to this route.
To let only the current user udpate it's own password and not all users password.
Here some documentation:
Create a policy
Get the current user in the request
Customize the User plugin
Here is yohanes's solution adapted to Strapi v4
For some reason the Strapi team has removed the hashPassword method of the users-permission.user service, so we need to generate the hash ourselves now. For this we use the same having method as v3 did. We need to import bcrypt like this: const bcrypt = require("bcryptjs");
Out new changePassword needs to look something like this:
async changePassword(ctx) {
const userId = ctx.request.body.userId;
const currentPassword = ctx.request.body.currentPassword;
const newPassword = ctx.request.body.newPassword;
if (!userId || !currentPassword || !newPassword) {
return ctx.throw(400, "provide-userId-currentPassword-newPassword");
}
let user = await strapi
.query("plugin::users-permissions.user")
.findOne({ id: userId });
const validPassword = await strapi
.service("plugin::users-permissions.user")
.validatePassword(currentPassword, user.password);
if (!validPassword) {
return ctx.throw(401, "wrong-current-password");
} else {
// Generate new hashed password
const password = bcrypt.hashSync(newPassword, 10);
user = await strapi.query("plugin::users-permissions.user").update({
where: { id: user.id },
data: { resetPasswordToken: null, password },
});
// Return new jwt token
ctx.send({
jwt: strapi.service("plugin::users-permissions.jwt").issue({
id: user.id,
}),
user: sanitizeOutput(user),
});
}
},

Post validation failed: title: Path `title` is required.", in graphql

I trying to add mutation in graphql-yoga but every time I try to mutate the I get error saying
Post validation failed: title: Path title is required.",
I have no idea why.
Here is my code
resolver
Mutation: {
createPost: async(root, args, ctx) => {
console.log(args)
try {
const post = new Post({
title: args.title,
description: args.description,
content: args.content
});
const result = await post.save();
console.log(result);
return result
}catch(err) {
throw err
}
}
}
schema
input postInput{
title: String!
description: String!
content: String!
}
type Mutation {
createPost(input: postInput): Post!
}
This works fine if I remove the input type and directly do like this
type Mutation {
createPost(title: String!,description: String!,content: String!): Post!
}
log result
{ input:
[Object: null prototype] {
title: 'with input',
description: 'this is de',
content: 'this is conte' } }
Here Why am I getting [Object: null prototype]?
You have to send your data in your resolver like this if you give input type like this on schema:
const post = new Post({
title: args.input.title,
description: args.input.description,
content: args.input.content
});
It means, in args, we need a parameter called input which is of type Post.
And while giving the datas on graphql gui send data like this:
mutation {
createPost(input:{
title: 'with input',
description: 'this is de',
content: 'this is conte'}) {
//return your id or others
}
}

sequelize custom validator

I would like to create custom field validator with reference to existing field. What I did is to create a custom validator:
const User = sequelize.define('User', {
postalCode: {
type: DataTypes.STRING
},
country: DataTypes.STRING,
}, {
validate: {
wrongPostalCode() {
if (this.postalCode && this.country) {
if (!validator.isPostalCode(String(this.postalCode), this.country)) {
throw new Error('Wrong postal code')
}
}
}
}
});
User.associate = (models) => {
// TO DO
};
return User;
};
As you can see below in error message, we are getting this validator but in the row "path" there is validator name. I would like to change it for example to "postalCode" or somehow connect it with one field from the model. It is very important for me as this is related to Front-End and to parse it to correct form control.
enter image description here
Is there any way to do it?
Thank you in advanced :)
Have you tried to use a custom validator for the field instead? I haven't tried the following piece of code but should work and link the validator to the postalCode field.
const User = sequelize.define('User', {
postalCode: {
type: DataTypes.STRING,
validate: {
wrongPostalCode(value) {
if (this.country) {
if (!validator.isPostalCode(String(this.postalCode), this.country)) {
throw new Error('Wrong postal code');
}
}
}
}
},
country: DataTypes.STRING,
});

Ember-Validations - Don't validate automatically

I am using ember-validations to validate a model in a form.
If I create the record with createRecord the instance of the model is already validated and therefore the form already shows validations errors before the user inputs values.
I just want to validate the model before submitting the form. Is there a way?
You need to add a conditional validator ('if' or 'unless') and activate it only when submitting the form.
Here is a quick example: http://jsbin.com/letujimu/1/edit?html,js,output
There is another alternative to ember-validations. ember-model-validator, with this addon you decide when to validate. By including Ember-model-validator's mixin into your model, this will add a validate function to your model, it is a synchronous function which returns either true or false.
There is support for all these validations:
Presence
Acceptance
Absence
Format
Length
Email
Color
ZipCode
Subdomain
URL
Inclusion
Exclusion
Numericality
Match
Password
CustomValidation
Relations
Example:
// Your model
import Validator from '../mixins/model-validator';
export default DS.Model.extend(Validator, {
email: DS.attr('string'),
password: DS.attr('string'),
passwordConfirmation: DS.attr('string'),
validations: {
email: {
presence: true,
email: { message: 'is not a valid email' }
},
password: {
presence: true,
length: {
minimum: 6
}
},
passwordConfirmation: {
presence: true,
match: 'password'
}
}
});
import Ember from 'ember';
export default Ember.Route.extend(
{
actions: {
saveFakeModel: function() {
var _this = this,
fakeModel = this.get('model');
if(fakeModel.validate()){
fakeModel.save().then(
// Success
function() {
// Alert success
console.log('ooooh yeah we just saved the FakeModel...');
},
// Error handling
function(error) {
// Alert failure
console.log('There was a problem saving the FakeModel...');
console.log(error);
}
);
}else{
fakeModel.get('errors');
}
},
}
}
);

Resources