I am trying to implement a rate limit on GraphQL Mutation when creating new users. I successfully implemented it in Queries but could not do the same on Mutations, any advice or resource on how I can solve this would be much appreciated.
Thanks in advance.
imports in resolvers/users
const bcrypt = require('bcrypt');
const { UserInputError } = require('apollo-server');
const { getGraphQLRateLimiter } = require('graphql-rate-limit')
const { validateCreateInput } = require('../../util/validators');
const User = require('../../models/User');
rate limiter declaration
const rateLimiter = getGraphQLRateLimiter({ identifyContext: (ctx) => ctx.id });
Query with rate limitation which works fine
Query: {
getUsers: async (parent, args, context, info) => {
try {
const errorMessage = await rateLimiter(
{ parent, args, context, info },
{ max: 1, window: '3s' }
);
if (errorMessage) throw new Error(errorMessage);
const users = await User.find().sort({ createdAt: -1 });
return users;
} catch (err) {
throw new Error(err);
}
},
},
My problem occurs here on Mutation when trying to create user with rate limit
Mutation: {
async createUser(_, {
createInput: {
name,
email,
role,
password,
confirmPassword
}
}) {
/****** problem here ******/
const errorMessage = await rateLimiter(
{ parent, args, context, info },
{ max: 1, window: '3s' }
);
if (errorMessage) throw new Error(errorMessage);
/****** problem here ******/
const { valid, errors } = validateCreateInput(name, email, role, password, confirmPassword);
if (!valid) {
throw new UserInputError('Errors', { errors })
}
const duplicateEmail = await User.findOne({ email });
if (duplicateEmail) {
throw new UserInputError('Email is already used', {
errors: {
email: 'Email is already used'
}
})
}
password = await bcrypt.hash(password, 12);
const newUser = new User({
name,
email,
role,
password,
createdAt: new Date().toISOString()
})
const res = await newUser.save();
return {
...res._doc,
id: res._id
}
}
}
The error that I am getting while creating new user
The error is pretty clear, check the difference between your query resolver and your mutation resolver. You need something like:
Mutation: {
async createUser(parent, args, context, info) { ... }
}
Then parent and other required fields will be defined.
Related
i'm trying to show a user specific data using req.session.user and pass the ID to the criteria i'm building. (every entry also has a user field so i can match) yet it does not work.
The Service :
async function query(filterBy) {
try {
const criteria = _buildCriteria(filterBy);
const collection = await dbService.getCollection('tab');
const tabs = await collection.find(criteria).toArray();
// const userTabs = await collection.find({ user: '62be030cb4de461a8462b863' }).toArray();
return tabs;
} catch (err) {
logger.error('Can not find tabs', err);
throw err;
}
}
The console.log('userId', userId) returns the Id I get from my controller
function _buildCriteria(filterBy) {
const criteria = {};
const { text, genre, userId } = filterBy;
console.log('userId', userId);
if (text) {
const txtCriteria = { $regex: text, $options: 'i' };
criteria.name = txtCriteria;
}
if (genre) {
criteria.genre = { $eq: genre };
}
if (userId) {
criteria.user = { $eq: userId };
}
return criteria;
}
The controller :
async function getTabs(req, res) {
try {
const userId = req?.session?.user?._id;
const filterBy = req.query;
const fitlerUpdated = { ...filterBy, id: userId };
const tabs = await tabService.query(fitlerUpdated);
res.json(tabs);
} catch (err) {
logger.error('Failed to get tabs', err);
res.status(500).send({ err: 'Failer ti get tabs' });
}
}
I tried using
const userTabs = await collection.find({ user: '62be030cb4de461a8462b863' }).toArray()
and it works yet it doens't work along with the criteria.
thanks for any help!
I have realize I accidentally passed the wrong key.
should have been id and not userId
This is the error that I get:
"You have created a new client application that use…i/web/guides/gis-migration) for more information."
here are my codes on server, the statement inside console.log doesnt even show:
static async googleLogin(req, res, next) {
try {
console.log("masuk google login server")
const { id_token } = req.body
const client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID)
const ticket = await client.verifyIdToken({
idToken: id_token,
audience: process.env.GOOGLE_CLIENT_ID
});
const payload = ticket.getPayload()
const email = payload.email
let password = email.toString().split('#')
password = password[0]
let user = await User.findOne({ where: { email } })
if (!user) {
let newUser = { email, password }
let createUser = await User.create(newUser)
const payload = {
id: createUser.id,
email: createUser.email
}
const access_token = generateToken(payload)
return res.status(201).json({ access_token })
} else {
const payload = {
id: user.id,
email: user.email
}
const access_token = generateToken(payload)
return res.status(200).json({ access_token })
}
} catch (err) {
console.log(err)
return next(err)
}
}
the console.log in my client also doesnt show
function onSignIn(googleUser) {
console.log("masuk client oauth")
$.ajax({
method: "POST",
url: `${baseUrl}/users/google-login`,
data: {
id_token: googleUser.getAuthResponse().id_token
}
})
.done((response) => {
console.log(response, "client response")
localStorage.setItem("access_token", response.access_token)
checkLocalStorage();
})
.fail((err) => {
console.log(err, "error client");
})
.always(() => {
authentication()
})
}
i tried deleting cache and run my app again, recreate a new project on google api (which genereated new ID). they didnt work
i am encountering a problem concerning firebase authentication
so whenever I submit the form the state will get the signup data but it will not send it to my firebase database
utils.js
export const handleUserProfile = async({ userAuth, additionalData }) => {
if (!userAuth) return;
const { uid } = userAuth;
const userRef = firestore.doc(`users/${uid}`);
const snapshot = await userRef.get();
if (!snapshot.exists) {
const { displayName, email } = userAuth;
const timestamp = new Date();
const userRoles = ['user'];
try {
await userRef.set({
displayName,
email,
createdDate: timestamp,
userRoles,
...additionalData
});
} catch (err) {
console.log(err);
}
}
return userRef;
};
in Signup.js :
handleSubmit = async event => {
event.preventDefault();
const { displayName, email, password, confirmPassword } = this.state;
if (password !== confirmPassword) {
const err = ['Password Don\'t match'];
this.setState({ errors: err })
return
}
try {
const { user } = await auth.createUserWithEmailAndPassword(email, password);
await handleUserProfile(user, { displayName })
this.setState({
...initialState
})
}
catch (err) {
}
}
where i am missing ?
I'm developing a server in Node JS where there are two routes - Login and Signup.
Whenever I do signup, I am getting response as success and the data is being stored in MongoDB database successfully and then I'm getting [nodemon] app crashed - waiting for file changes before starting... in my console.
Note:- "The problem is in signup only not in login".
postSignup() will be called when a user requests for signup which is validated according to schema and inserted in database.
I'm providing the code related to signup.
signup.js
const { User } = require("../../models");
const createError = require("http-errors");
const postSignup = (req, res, next) => {
//validation
const validation = User.validate(req.body);
if (validation.error) {
const error = new Error(validation.error.message);
error.statusCode = 400;
return next(error);
}
//check Existence
const user = new User(req.body);
user
.checkExistence()
.then((result) => {
if (result.check) {
const error = new Error(result.message);
error.statusCode = 409;
return next(error);
}
user.save((err) => {
if (err) {
console.log(err);
return next(createError(500));
}
res.status(201).json({
message: "User has been Successfully Created",
});
});
})
.catch((err) => {
next(createError(500));
});
};
module.exports = {
postSignup,
};
User.js
const { dbCon } = require("../configuration");
const { userValidator, logSchema } = require("../validator");
const { hashSync, compareSync } = require("bcryptjs");
class User {
constructor(userData) {
this.userData = { ...userData };
}
save(cb) {
dbCon("users", (db) => {
try {
const hashPass = hashSync(this.userData["password"], 12);
this.userData["password"] = hashPass;
db.insertOne(this.userData);
cb();
} catch (err) {
cb(err);
}
});
}
checkExistence() {
return new Promise((resolve, reject) => {
dbCon("users", async (db) => {
try {
const user = await db.findOne({
$or: [
{ username: this.userData["username"] },
{ email: this.userData["email"] },
],
});
if (!user) {
resolve({
check: false,
});
} else if (this.userData["username"] === user.username) {
resolve({
check: true,
message: "username already exists",
});
} else if (this.userData["email"] === user.email) {
resolve({
check: true,
message: "email already exists",
});
}
} catch (err) {
reject(err);
}
});
});
}
static validate(userData) {
//console.log(userData);
return userValidator.validate(userData);
}
module.exports = User;
userValidator.js
const Joi = require("#hapi/joi");
const schema = Joi.object({
username: Joi.string().alphanum().required().min(3).max(15),
email: Joi.string().email().required(),
password: Joi.string()
.pattern(
new RegExp(
"^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!#$%^&*-]).{8,}$"
)
)
.message(
"Password must contain at least eight characters, at least one number and both lower and uppercase letters and special characters"
)
.required(),
first_name: Joi.string().required(),
last_name: Joi.string().required(),
});
module.exports = {
schema
};
I faced the same issue. I don't know what was the issue but I tried to change node version in mongo db connect and then used the new connect URL.
If it still doesn't work, then try to create new cluster and connect it again with new cluster.
I want to to check if email already exists in 'users' collection:
I have this model:
const isEmailExists = async (value) => {
const res = await User.countDocuments({ email: value });
return res > 0;
}
const User = mongoose.model('User', {
email: {
type: String,
required: true,
validate(value) {
isEmailExists(value).then(res => {
if (res) {
throw new Error('Email already exists');
}
})
}
}
});
And I use post method with express router:
router
.route('/register')
.get((req, res) => {
res.sendFile(publicDirPath + '/auth/register.html');
})
.post(async (req, res) => {
const user = new User(req.body);
try {
const saveUser = await user.save();
res.send(saveUser);
} catch (error) {
res.send(error);
}
});
For some reason, it does not work and the user is been added anyway..
What am i doing wrong ?
If you want to check if one document with a certain entry/value exists you can do this :
function emailExists(value) {
User.findOne({email: value}).then((err, user) => !!user)
}