Express + TSOA + Passport - javascript

I am new to Passport and to TSOA. I am trying to migrate an old Express route to TSOA controllers but I have not any idea how to achieve this. I did not find any documentation regarding this, in fact, I found this issue but I think I need a workaround.
router.post(
'/register',
(req: Request, res: Response, next) => {
const user: IUserData = req.body
if (validator.isEmpty(email))
return res.status(422)
.json({ error: 'Email is mandatory' })
if (validator.isEmpty(password.trim()))
return res.status(422)
.json({ error: 'Password is mandatory' })
next()
},
passport.authenticate('local'),
async (req: Request, res: Response) => {
if (req.user)
return res.json({ message: 'Already logged in' })
const existingUser: IUser = await UserModel.findOne({ email: req.body.email })
if (existingUser)
return res.status(400)
.json({ error: 'User exists' })
const data: IUserData = req.body
try {
const hashedPassword = await bcrypt.hashSync(data.password, parseInt(process.env.SALT_ROUNDS) || 10)
data.password = hashedPassword
} catch(error) {
return res.status(500)
.json({ error: error.message })
}
try {
const user: IUser = await new User(data)
await user.save()
res.json(user);
} catch(error) {
return res.status(500)
.json({ error: error.message });
}
return;
}
);
I have tried to "replicate" this code into a TSOA controller but I don't know how to apply the middleware stuff:
#Route('/auth')
export class AuthController extends Controller {
#Post()
public async register(#BodyProp() user: IUserData) {
const errors: Array<IAuthError> = []
if (validator.isEmpty(user.emailAddress)) {
this.setStatus(422)
return { error: 'Email is mandatory' }
}
if (validator.isEmpty(user.password.trim())) {
this.setStatus(422)
return { error: 'Password is mandatory' }
}
}
}
// to be continued ...
I could also separate this certain route in a different file (without any controller) but I don't know if that would work at all. I guess not.
How should I manage this?
Edit:
I just have read this article regarding TSOA authentication and it seems to handle authentication by using middlewares.

Related

Stop execution of code after returning response in Node JS?

I am new to Node JS. I am practising to build a MERN app. I have two function getUserById and isSignedIn. These two function are middlewares.
router.param("userId",getUserById)
const getUserById = (req,res,next,id)=>{
User.findById(id).exec((err,user)=>{
if(err){
return res.json({
error:"Unable to process request",
status: false,
})
}
console.log(1234);
if(!user){
return res.json({
error:"User doesn't exist",
status: false
})
}
req.user=user;
next()
})
}
const isSignedIn = (req,res,next)=>{
const token = req.headers.authorization.split(" ")[1]
jwt.verify(token, process.env.SECRET_KEY, (err, decoded)=>{
if(err){
return res.json({
status: false,
error: "Invalid Token"
})
}
console.log(123);
req.auth=decoded
next()
})
};
router.post("/api/create/:userId",isSignedIn,(req,res)=>{ res.send("Success")})
This is my understanding. If in url userId is found getUserById will be executed and then isSigned in. In getUserById if there was an error or if the user doesn't exist if will send a response and the execution of code stop there. But in isSignedin if the token is not valid I am sending a response as Invalid Token and the code execution should stop there. But the code after if is also getting executed why it is so?
Try following code
router.param("userId",getUserById)
const getUserById = async (req,res,next,id)=>{
try {
const user = await User.findById(id)
if(!user){
return res.json({
error:"User doesn't exist",
status: false
})
} else {
req.user=user;
next()
}
} catch (err) {
return res.json({
error:"Unable to process request",
status: false,
error: JSON.stringify(err),
})
}
}
const isSignedIn = async (req,res,next)=>{
const token = req.headers.authorization.split(" ")[1]
try {
const decoded = await jwt.verify(token, process.env.SECRET_KEY)
if (decoded) {
req.auth=decoded
next()
}
} catch (err) {
if(err){
return res.json({
status: false,
error: "Invalid Token"
})
}
}
};
router.post("/api/create/:userId",isSignedIn,(req,res)=>{ res.send("Success")})

How to use chained Promises calls?

I'm trying to implement a simple login function.
module.exports.login = (req, res, next) => {
let loggedin_user;
User.findOne({email: req.body.email.toLowerCase()})
.then(user => {
if(!user){
throw ('Invalid e-mail or password');
}
loggedin_user = user;
return bcryptjs.compare(req.body.password, user.password)
})
.then(res => {
if(!res){
return res.status(401).json('Invalid e-mail or password')
}
const token = jwt.sign({
id: loggedin_user._id,
role: loggedin_user.role
}, process.env.JWT_KEY, { expiresIn: '24h' });
return res.status(200).json({
token: token,
role: loggedin_user.role,
expires_in: 24*60*60})
})
.catch(err => {
return res.status(401).json(err);
})
}
My code works great until it reaches the last return part, this part:
return res.status(200).json({
token: token,
role: loggedin_user.role,
expires_in: 24*60*60,
})
It doesn't return anything instead it jumps to the catch block, although it console logs that javascript object that I need to return, it logs it right before the return statement.
What's the problem?
You should log the error message in the catch to see what the error is.
I'd suspect req.body or user may be undefined, and checking the properties email and password could result in an error.
At first glance, I don't catch any error to your code. Maybe issue is with your password, but I am not sure.
Any way to simplify things and check the same, I modified your code as below. Try this and let me know the output. To deal with Promises, Async/Await is better.
module.exports.login = async (req, res, next) => {
try {
const user = await User.findOne({ email: req.body.email.toLowerCase() });
if (!user) {
res.status(401).json('Invalid e-mail');
}
const checkPass = await bcryptjs.compare(req.body.password, user.password);
if (!checkPass) {
res.status(401).json('Invalid password');
}
const token = jwt.sign(
{
id: user._id,
role: user.role,
},
process.env.JWT_KEY,
{ expiresIn: '24h' }
);
res.status(200).json({
token: token,
role: user.role,
expires_in: 24 * 60 * 60,
});
} catch (err) {
console.error(err.message);
}
};

Handling errors in Express.js in service / controller layers

I am writing an application in Express.js with a separate controller layer and a service layer. Here is my current code:
user.service.js
exports.registerUser = async function (email, password) {
const hash = await bcrypt.hash(password, 10);
const countUser = await User.countDocuments({email: email});
if(countUser > 0) {
throw ({ status: 409, code: 'USER_ALREADY_EXISTS', message: 'This e-mail address is already taken.' });
}
const user = new User({
email: email,
password: hash
});
return await user.save();
};
exports.loginUser = async function (email, password) {
const user = await User.findOne({ email: email });
const countUser = await User.countDocuments({email: email});
if(countUser === 0) {
throw ({ status: 404, code: 'USER_NOT_EXISTS', message: 'E-mail address does not exist.' });
}
const validPassword = await bcrypt.compare(password, user.password);
if (validPassword) {
const token = jwt.sign({ email: user.email, userId: user._id }, process.env.JWT_KEY, { expiresIn: "10s" });
return {
token: token,
expiresIn: 3600,
userId: user._id
}
} else {
throw ({ status: 401, code: 'LOGIN_INVALID', message: 'Invalid authentication credentials.' });
}
};
user.controller.js
exports.userRegister = async function (req, res, next) {
try {
const user = await UserService.registerUser(req.body.email, req.body.password);
res.status(201).json({ data: user });
} catch (e) {
if(!e.status) {
res.status(500).json( { error: { code: 'UNKNOWN_ERROR', message: 'An unknown error occurred.' } });
} else {
res.status(e.status).json( { error: { code: e.code, message: e.message } });
}
}
}
exports.userLogin = async function (req, res, next) {
try {
const user = await UserService.loginUser(req.body.email, req.body.password);
res.status(200).json({ data: user });
} catch (e) {
if(!e.status) {
res.status(500).json( { error: { code: 'UNKNOWN_ERROR', message: 'An unknown error occurred.' } });
} else {
res.status(e.status).json( { error: { code: e.code, message: e.message } });
}
}
}
The code works, but requires some corrections. I have a problem with error handling. I want to handle only some errors. If another error has occurred, the 500 Internal Server Error will be returned.
1) Can I use "throw" object from the service layer? Is this a good practice?
2) How to avoid duplication of this code in each controller:
if(!e.status) {
res.status(500).json( { error: { code: 'UNKNOWN_ERROR', message: 'An unknown error occurred.' } });
} else {
res.status(e.status).json( { error: { code: e.code, message: e.message } });
}
3) Does the code require other corrections? I'm just learning Node.js and I want to write the rest of the application well.
Yes, you can throw errors from service layer, it is good practice to catch errors with try/catch block in controller
I handle this with a custom error middleware, just use a next function in a catch block.
catch (e) {
next(e)
}
Example of error middleware (for more info check docs, fill free to move a middleware to file)
app.use(function (err, req, res, next) {
// err is error from next(e) function
// you can do all error processing here, logging, parsing error messages, etc...
res.status(500).send('Something broke!')
})
From my point of view it looks good. If you looking for some best practice and tools, try eslint (with AirBnb config for example) for linting, dotenv for a environment variables management, also check Node.js Best Practice
i want to give you an example:
this code in your controller
findCar(idCar)
} catch (error) {
switch (error.message) {
case ErrorConstants.ELEMENT_NOT_FOUND('LISTING'): {
return {
response: {
message: ErrorMessages.ELEMENT_NOT_FOUND_MESSAGE('LISTING'),
},
statusCode,
}
}
default: {
return {
response: {
message: ErrorMessages.UNKNOWN_ERROR_MESSAGE,
},
statusCode,
}
}
}
}
and this code in your service
findCar: async listingId => {
try {
if (some condition) {
throw new Error(ErrorConstants.ELEMENT_NOT_FOUND('LISTING'))
}
return { ... }
} catch (error) {
console.error(error.message)
throw new Error(ErrorConstants.UNKNOWN_ERROR)
}
},
controller is going to catch the service's errors

How can I update attribute in Sequelize Promise?

I'm trying to validate a user email by decoding a JWT token given as parameter in a GET request.
In case the token is valid and the User is not verified yet, I want to update isVerified column to true.
Why is my Promise always rejected ?
const jwt = require('jsonwebtoken');
const request = require('request');
const models = require('../models');
/**
* GET /verify-email/:token
*/
exports.verifyEmail = function(req, res) {
jwt.verify(req.params.token, process.env.TOKEN_SECRET, function(err, decoded) {
if (err) {
res.send({
msg: 'Token is invalid or has expired'
})
}
models.User.findOne({
where: {
email: decoded.email,
id: decoded.id
}
}).then(record => {
if (!record) {
res.send({
msg: 'User not found'
})
} else if (record.isVerified) {
res.send({
msg: 'User already verified'
})
} else {
console.log('user mail ' + record.email + ' will be verified in db')
record.update({
isVerified: true,
})
.then(() => res.status(200).send({
msg: 'User has been verified'
}))
.catch((error) => res.status(400).send(error));
}
})
});
}
Problem solved, I had a beforeValidation hook in my model :
if (!user.changed('password')) {
return sequelize.Promise.reject("not modified");
}

Mongoose query promise in function make code became ugly

I was creating my users API, I want to check if username had been used.
So I wrote a static function
static findByName(name) {
const query = User.where({
username: name,
});
query.findOne((queryErr, user) => {
if (queryErr) {
console.log(queryErr);
return false;
}
return user;
});
}
when I called it in signUp
signup(req, res) {
if (!req.body.username || !req.body.password || !req.body.email) {
return res.status(400).json({ success: false, message: 'Bad Request' });
}
if (!Users.findByName(req.body.username)) {
return res.status(409).json({ success: false, message: 'Username has been used' });
}
const hashedPassword = this.genHash(req.body.password);
const newUser = User({
username: req.body.username,
});
}
findByName return Undefined.
Finally I use promise.
signup(req, res) {
if (!req.body.username || !req.body.password || !req.body.email) {
return res.status(400).json({ success: false, message: 'Bad Request' });
}
return Users.findByName(req.body.username).then((existingUser) => {
if (existingUser) {
return res.status(409).json({ success: false, message: 'Username has been used' });
}
const hashedPassword = this.genHash(req.body.password);
const newUser = User({
username: req.body.username,
password: hashedPassword,
email: req.body.email,
});
return newUser.save().then((user) => {
res.json({ success: true, user });
}).catch((err) => {
res.status(500).json({ success: false, message: 'Internal Server Error' });
});
}).catch((err) => {
res.status(500).json({ success: false, message: 'Internal Server Error' });
});
}
That is really horrible code.
Is there better way to clean the code?
Is there better way to clean the code
Yes. I am going to assume /signup is defined as a POST route on the usual Express app instance
With that said you, since you are already using promises, you can go a step further and use async/await which is enabled by default in Node.js v7.6+.
This will make your code read more synchronously:
async signup(req, res) {
if (!req.body.username || !req.body.password || !req.body.email) {
return res.status(400).json({ success: false, message: 'Bad Request' });
}
try {
const existingUser = await Users.findByName(req.body.username)
if (existingUser) {
return res.status(409).json({ success: false, message: 'Username has been used' })
}
const hashedPassword = this.genHash(req.body.password);
const newUser = await User({
username: req.body.username,
password: hashedPassword,
email: req.body.email,
}).save()
res.json({ success: true, newUser });
} catch (error) {
res.status(500).json({ success: false, message: 'Internal Server Error' });
}
}
You may have noticed the use of try/catch. This is because since we are not using .catch() and we still have to handle any error that occurs. To further clean up to code, we can write an error handler middleware that will take care of errors for us:
src/middleware/error-handlers.js
// Wraps the router handler, catches any errors, and forwards to the next middleware that handles errors
exports.catchErrors = action => (req, res, next) => action(req, res).catch(next);
// Notice the first parameter is `error`, which means it handles errors.
exports.displayErrors = (error, req, res, next) => {
const err = error;
const status = err.status || 500;
delete err.status;
err.message = err.message || 'Something went wrong.';
if (process.env.NODE_ENV === 'production') {
delete err.stack;
} else {
err.stack = err.stack || '';
}
res.status(status).json({
status,
error: {
message: err.message,
},
});
};
Now we just need to use our error handlers:
app.js
const { catchErrors, displayErrors } = require('./middleware/error-handlers')
// Whenever you defined the function, needs to have the `async` keyword
async function signup(req, res) { ... }
// Wrap the function call
app.post('/signup', catchErrors(signUp))
// Handle any errors
app.use(displayErrors)
Using the above middleware transforms our code to:
async signup(req, res) {
const error = new Error()
if (!req.body.username || !req.body.password || !req.body.email) {
error.status = 400
error.message = 'Bad Request'
throw error
}
const existingUser = await Users.findByName(req.body.username)
if (existingUser) {
error.status = 409
error.message = 'Username has been used'
throw error
}
const hashedPassword = this.genHash(req.body.password);
const newUser = await User({
username: req.body.username,
password: hashedPassword,
email: req.body.email,
}).save()
res.json({ success: true, newUser });
}
You can see how the code is much easier to read without all the noise.
Be sure to read up on:
Writing middleware for use in Express apps
Express error handling
I can't test that my solution actually works (probably not) or that I'm covering every functionality of your original code. What I tried to do is to make functions that handle specific things, then chain them together, so that you can see a more clear flow of the program.
signup(req, res) {
if (!req.body.username || !req.body.password || !req.body.email) {
return res.status(400).json({ success: false, message: 'Bad Request' });
}
const handleError = error => res.status(500).json({ success: false, message: 'Internal Server Error' });
const handleExistingUser = existingUser => {
if (existingUser) {
return res.status(409).json({ success: false, message: 'Username has been used' });
} else {
return Promise.resolve();
}
}
const handleCreateUser = () => {
const hashedPassword = this.genHash(req.body.password);
const newUser = User({
username: req.body.username,
password: hashedPassword,
email: req.body.email,
});
return newUser.save().then((user) => {
res.json({ success: true, user });
});
};
// the flow of the program is hopefully more clear here
return Users.findByName(req.body.username)
.then(handleExistingUser)
.then(handleCreateUser)
.catch(handleError);
}
If you handle the error of the inner and outer promise the same, then I think it's enough to have the error handling in the outer layer. (In your last example.) But I'm not 100% certain.
Your function returns undefined, because it does not have a return statement. The return user statement, is the (useless) return value for the findOne callback function, not for findByName.
If you go for promises, then define the function as follows:
static findByName(name) {
return User.where({ username: name }).findOne().exec();
}
And your promise chain can be simplified a bit, like this:
signup(req, res) {
function report(message, status = 200) {
res.status(status).json({ success: status === 200, message });
}
if (!req.body.username || !req.body.password || !req.body.email) {
return report('Bad Request', 400);
}
Users.findByName(req.body.username).then((existingUser) => {
return existingUser ? null // treat this condition in the next `then`
: User({
username: req.body.username,
password: this.genHash(req.body.password),
email: req.body.email,
}).save().exec();
}).then((user) => {
return existingUser ? report(user)
: report('Username has been used', 409);
}).catch((err) => {
report('Internal Server Error', 500);
});
}

Categories

Resources