I'm trying to keep my controller actions as lightweight as possible so i am implementing service layer. Now i've stucked with validation and sanitization. I know that validation should be done in service layer but what about sanitization? I would like to re-render the with the input data when there are validation errors.
//userService.js function
function register(data, callback) {
if (!data) {
return callback(new Error('Here some error...'));
}
/* Sanitize and validate the data */
/* Method #1 */
//If not valid then call back with validationErrors
if (notValid) {
return callback({
validationErrors: {
'username': 'Username is already in use.',
'email': 'Invalid characters.',
}
});
}
/* Method #2 */
if (notValid) {
return callback({
fields: {
//We put here a sanitized fields
},
validationErrors: {
'username': 'Username is already in use.',
'email': 'Invalid characters.',
}
});
}
};
//userController.js function
// GET/POST: /register
function registerAction(request, response, next) {
if (request.method === 'POST') {
var registerData = {
username: request.body['username'],
password: request.body['password'],
email: request.body['email'],
firstName: request.body['firstName'],
lastName: request.body['lastName'],
};
register(registerData, function(error, someDataIfSucceed) {
if (error) {
//Re-post the data so the user wont have to fill the form again
//Sanitize registerData variable here.
return response.render('register', {
error: error,
validationErrors: error.validationErrors
});
};
//User registered succesfully.
return response.render('registerSuccess');
});
return;
}
return response.render('register');
}
I see there 2 options.
Call service function 'register' with raw POST data, sanitize and validate it then push back only validation errors. If there are validation errors then sanitize them in controller before rendering the view.
Same as first one but we push back validation errors and sanitized fields.
If you're using Express, an interesting option is:
Create a middleware and use it as a validation layer, using express-validator, which is based on node-validator.
For example (see node-validator documentation for all validation/sanitization options):
exports.validate = function(req, res, next) {
req.assert('username').notEmpty();
req.assert('password').notEmpty();
req.assert('email').isEmail();
req.assert('firstName').len(2, 20).xss();
req.assert('lastName').len(2, 20).xss();
var errors = req.validationErrors(true);
if (errors){
res.status(400).json({ status: 'ko', errors: errors });
}
else {
next();
}
}
Then, in your controller, simply obtain the validated request params and run the sign up logic (your register function call and response rendering),
IMHO, this way you can keep your code more clean and decoupled.
If it's a small project don't bother, just do what works.
If, however, it is a large (read: long-lived) project, then:
If by "sanitization" you mean HTTP/HTML sanitization (or inputs, or of display messages), then this belongs in the controller. Think about it this way: the controller may not be the only place where you pass input to your service layer from. In the future you might have API access to the service. Or a test driver may invoke it directly, without going thru HTTP. So HTTP/HTML is just the transport, and as such logic specific to it should be outside of the service.
If, however, by "sanitization" you mean business-logic sanitization (e.g.: you don't allow non-existing country codes), then by all means this should be in the service, for exactly the same reasons.
Related
I have a Nodejs typescript authentication system that works using passport.
My problem is that when I use req.user in a route I get this error on req.user: Object is possibly 'undefined'.
This is a normal behavior of Typescript but I am using a middleware to protect routes that I want to use req.user in them and this way req.user cannot be undefined.
This is where I extend Express.User Type:
import { users } from "#prisma/client";
declare global {
namespace Express {
interface User extends users {}
}
}
This is the middleware that I'm using to protect routes for logged in users:
export function checkIsAuthenticated(req: Request, res: Response, next: NextFunction) {
if (req.isAuthenticated()) {
if (!req.user) req.logOut();
else return next();
}
res.status(400).json({
errors: [{ message: "no user logged in" }],
});
}
And this is the route for getting the user info:
export function userRoute(req: Request, res: Response) { // defining the route
res.json({
id: req.user.id, // I'm getting the error in these 4 lines
username: req.user.username, //
email: req.user.email, //
role: req.user.role, //
});
}
router.get("/user", checkIsAuthenticated, userRoute); // using the route
I don't want to check if user is defined because I don't want to do it in every route and that's not a good practice. That's why middleware are for.
I'm not good at Typescript so I need some help to fix it. Thanks
I don't want to check if user is defined because I don't want to do it in every route and that's not a good practice.
My first solution trying to accommodate that requirement didn't work, so I've removed it. I've left my second solution, which is:
But, I'd quibble that there's nothing wrong with checking the request for a user and giving yourself a nice useful error message if you've accidentally used one of these handlers on a route that you forgot to put the authentication on. That would look like this:
type RequestWithUser = Request & {user: typeOfUserObject};
function assertHasUser(req: Request): asserts req is RequestWithUser {
if (!( "user" in req)) {
throw new Error("Request object without user found unexpectedly");
}
}
Then your handler for those routes:
export function userRoute(req: Request, res: Response) {
assertHasUser(req);
// ...you can use `req.user` here...
});
Playground example
(You don't really need the explicit RequestWithUser type, you could just use asserts req is Request & {user: typeOfUserObject}.)
export function userRoute(req: Request, res: Response) { // defining the route
res.json({
id: req.user!.id, // you tell typescript that req.user for sure not. null
username: req.user!.username, //
email: req.user!.email, //
role: req.user!.role, //
});
}
router.get("/user", checkIsAuthenticated, userRoute);
I'm using node and express. What I want to do is to do is make a mix of res.render and res.redirect.
Thing is, res.render can only receive a .ejs file and data, and redirect will go to a specific URL. What I need to do is go to a specific URL (e.g. /reviews/new/:id), render a .ejs file and give some data to it.
This is my code. I can't use session or cookies for this project.
This are the routes, user enters to edit a review of some show. If it is a new review, the id of the show is in the URL, if the user is editing one, the ID of the review is on the URL. Either way, if something fails, I have to append something to this URL and send data.
router.get('/new/:id', controller.newReview);
router.post('/store', controller.checkBeforeStoringReview);
router.get('/edit/:id', controller.editReview);
router.post('/update', controller.checkBeforeUpdatingReview);
This is the function to check auth before updating.
checkBeforeUpdatingReview: function(req, res) { // auth before updating review (can't use session or cookies)
console.log(req.body)
DB
.User
.findOne(
{
where : {
email: req.body.email,
},
}
)
.then (function (results) {
if (results[0] != '') {
if (bcrypt.compareSync(req.body.password, results.password)) {
return module.exports.updateReview(req, res, results)
} else { // same as catch
return res.render('reviews/edit/', { // i'm missing the ID (which i have in req.params.id) at the end of the route
id : req.params.id,
errors : "Incorrect username or password",
email : req.body.email,
});
}
}
})
.catch (function (error) {
console.log(error)
return res.render('reviews/edit/', { // i'm missing the ID (which i have in req.params.id) at the end of the route
id : req.params.id,
errors : "An unexpected error happened",
email : req.body.email,
});
})
},
If everything's ok, as seen above, it goes directly to this function
updateReview: function(req, res, results) { // update review
console.log(req.body)
DB
.Review
.update(req.body,
{
where : {
id: req.body.review_id,
}
}
)
.then(function (results) {
return res.redirect('/series/detail/' + req.body.series_id)
})
.catch (error => {
return res.send(error)
})
},
TL;DR: If auth fails, should go back to the review url and send the data that was sent so that the user does not lose it.
So that's it, if I could use sessions/cookies I think I would be able to go back to the last route, but I can't for this.
Thanks in advance!
I have many endpoints in my express app that have many conditions. I want to find what is the best design pattern for them without repeating myself so much.
This is one of my simpler routes:
router.post('/reset/:token',
asyncMiddleware(async(req, res, next) => { await reset(req, res, next, pino); })
);
Inside reset()I need to check a couple of things, such as:
If all the required body params are there
If the email from the decrypted token matches the one from the database
If the password was saved successfully.
I would like to check those conditions that without having a huge function, but I don't know what is the best way to do so.
Entire Route Code
export async function reset(req, res, next) {
const email = req.body.email;
if (!email) return res.status(400).json(Error.paramsMissing('email'));
const user = await userAssociatedWithEmail(req.body.email);
if (!user) {
return res.status(501).json(Error.noActiveUserAssociatedWithEmail);
}
// Generate token
const token = await jwt.sign({ email: user.email, id: user.id }, 'shhhhh');
const emailSent = await sendForgotEmail(token, user);
if (!emailSent) return res.status(500).json(Error.emailNotSent);
else return res.json({ status: 'success', message: 'Email sent successfully.' });
}
What I would like to do
Final Result I'd like to have
export async function reset(req, res, next) {
const email = req.body.email;
if (!email) return res.status(400).json(Error.paramsMissing('email'));
// If error inside userAssociatedWithEmail, I'd like to stop execution and
// return res.status(501).json(Error.noActiveUserAssociatedWithEmail) from inside
// that function, without having to add an if condition below as exists in the
// original code above
const user = await userAssociatedWithEmail(req.body.email);
const token = await jwt.sign({ email: user.email, id: user.id }, 'shhhhh');
// Again I'd like to return res.status(500).json(Error.emailNotSent)
// from inside sendForgotEmail IF there is an error
const emailSent = await sendForgotEmail(token, user);
// If everything is successful, finally I'd return this
return res.json({ status: 'success', message: 'Email sent successfully.' });
}
Explanation of the result in word:
I'd like to be able to handle the conditions and scenarios without having to handle it in the main reset function if that's possible (aka, without having to store a response in a variable, check the variable and return in the main function in the case of error).
So for example, instead of:
const allParamsAreValid = validParams(token, email, new_password, res);
if (!allParamsAreValid) return;
I'd like to do something like:
validateParams(token, email, new_password, res);
And then inside validateParams() if a param is missing, I'd force exit the program besides also setting the response with res.json({}).
Is that possible?
You can make all your asynchronous functions that return promises reject their promise with the status and value you want sent. Then, you can handle that rejected promise in one place:
export async function reset(req, res, next) {
try {
const email = req.body.email;
if (!email) return res.status(400).json(Error.paramsMissing('email'));
// If error inside userAssociatedWithEmail, I'd like to stop execution and
// return res.status(501).json(Error.noActiveUserAssociatedWithEmail) from inside
// that function, without having to add an if condition below as exists in the
// original code above
const user = await userAssociatedWithEmail(req.body.email);
const token = await jwt.sign({ email: user.email, id: user.id }, 'shhhhh');
// Again I'd like to return res.status(500).json(Error.emailNotSent)
// from inside sendForgotEmail IF there is an error
const emailSent = await sendForgotEmail(token, user);
// If everything is successful, finally I'd return this
res.json({ status: 'success', message: 'Email sent successfully.' });
} catch(e) {
res.status(e.status || 500).json(e.errData)
}
}
And, then all of your asynchronous functions would reject if they have an error condition and set both e.status and e.errData on the rejected reason. That would allow you to have one common error handler and let the async function collect any rejected promise into your try/catch for you. This is meant to be the clean way you handle rejections in a series of await calls where you want the whole function to finish.
Then, you also need to make sure your asyncMiddleware() function is NOT also sending a response (can't really tell what its purpose is). You don't show that code, so I can't see what it's doing.
You don't show any code that uses validateParams(), but if it was synchronous, then it could just throw an exception with the right fields set on it and the try/catch would also catch it just like it would catch the async rejections.
For example:
function validateParams(token, email, new_password) {
let err = new Error();
err.errData = {status: 'error'};
if (!token) {
err.errData.message = 'invalid token';
throw err;
}
if (!email) {
err.errData = Error.paramsMissing('email');
throw err;
}
if (!new_password) {
err.errData.message = 'invalid new password');
throw err;
}
}
If you wanted to, you could also send an error response in validateParams(), but I think it's cleaner not to because they you can collect all errors including all your await asynchronous calls in one try/catch in the route handler and frankly, it's a lot more readable and understandable code not to send a response in some function calls, but not in others. I try to keep all my responses both error and success sent at the same level. Then, it's really easy to keep track of and to avoid accidentally trying to send multiple responses.
Then, in your route handler, you'd just call validateParams(...) just like that. If it throws, your try/catch would catch it and send the appropriate error. If no error, then execution would just continue.
Since you are passing res object to the validateParams method, you can do something like this,
async function validateParams(token, email, new_password, res) {
if (token && emal && new_password && res) {
// all values are available
// perform your desired operation
} else {
// exit from the method and pass info to the client
return res.json({ message: 'Invalid parameter' });
}
}
In this case, all you have to do is invoking the validateParams.
await validateParams(token, email, new_password, res);
If there is a missing parameter, the server passes the control to the client immediately. Otherwise, you can perform your operation there.
I have a sails.js app that generates API to my client. In order to secure my API I need to implement OAuth2.0 to my sails app. I have started to follow this tutorial: https://www.npmjs.com/package/sails-generate-auth#requirements
But I get all kinds of diffrent errors when every time when I try to lift the server. I also dont understand to where i'm suppose to send my credentials to the server and get the access token. I'm fairly new to Sails.js and just got to know OAuth and I can't find a proper guide on how to implement OAuth.
How can I implement OAuth to my app? please have a detailed answer so that I can fully understand.
UPDATE:
ok so instead I started to follow this guide: https://www.bearfruit.org/2014/07/21/tutorial-easy-authentication-for-sails-js-apps/
and I think I got everything to work as it should(?) when I register an account it saves the data in the database as it should. The login also seems to work properly But I didn't understood how I can access the actuall data like the username and email address after the login redirects me to the homepage? I've tested the login on postman and when i log in I get a cookie. What am I suppose to do with it?
The AuthController generated by sails-generate-auth doesn't add the user details to the session by default so you should add it manually by adding the following line to the callback function in AuthController.js
req.session.user = user;
This is how the callback looks like with the line:
callback: function (req, res) {
function tryAgain (err) {
// Only certain error messages are returned via req.flash('error', someError)
// because we shouldn't expose internal authorization errors to the user.
// We do return a generic error and the original request body.
var flashError = req.flash('error')[0];
if (err && !flashError ) {
req.flash('error', 'Error.Passport.Generic');
} else if (flashError) {
req.flash('error', flashError);
}
req.flash('form', req.body);
// If an error was thrown, redirect the user to the
// login, register or disconnect action initiator view.
// These views should take care of rendering the error messages.
var action = req.param('action');
switch (action) {
case 'register':
res.redirect('/register');
break;
case 'disconnect':
res.redirect('back');
break;
default:
res.redirect('/login');
}
}
passport.callback(req, res, function (err, user, challenges, statuses) {
if (err || !user) {
return tryAgain(challenges);
}
req.login(user, function (err) {
if (err) {
return tryAgain(err);
}
// Mark the session as authenticated to work with default Sails sessionAuth.js policy
req.session.authenticated = true;
req.session.user = user;
// Upon successful login, send the user to the homepage were req.user
// will be available.
res.redirect('/');
});
});
}
You can now use the user details in any of your controllers and views by referring to req.session.user for example twitter provides your user name so you can use req.session.user.username.
Project:
API over websockets (socket.io)
My goal:
check if a username is unique before storing it
My solution:
Overwrite default createUser function
My problem:
calling User:create doesn't do the validation
code in the user controller:
create: function(req, res, next){
User.findByUsername(req.param('username')).then(function(usr){
if(usr !='')
{
// username already taken
return res.json({'type':'validationError','errorMessage':'Username already taken','usr':usr});
}
else{
// username is unique
User.create( req.params.all(),function userCreated(err,user){
// try to create user
if(err){
console.log(err);
return res.json({
'type':'validationError',
'errorMessage':err}
);
}
res.json(user);
});
}
},
function(err){
// error finding user by username
}
);
I believe approaching the problem in a slightly different way could potentially make this easier for you. You can handle the validation in the model:
attributes: {
username: {
type: 'string',
unique: true
}
}
Then, without overriding anything in the controller you should be able to attempt to create a user via websocket, and handle the error that is returned if you attempt to use a non-unique username.