Node.js / Express make variable available in layout on all routes - node.js

Once a user has logged in, I want to display their username in the header, which is currently part of layout.jade. Their details are in req.currentUser but req isn't accessible from the layout.
I know I can do the following for each of the render calls in my routes:
res.render('help', {
locals: { currentUser: req.currentUser }
});
But it seems there must be a better way than adding { currentUser: req.currentUser } into the locals every single one of my routes.
I'm still a Node newbie, so apologies if this is a stupid question.

You need to use a dynamic helper. It is a function that is passed the request and response objects, just like any route or middleware, and can return data specific to the current request. (including session data)
So, in app.js:
app.dynamicHelpers({
currentUser: function (req, res) {
return req.currentUser;
}
});
and in your template:
<div><%= currentUser %></div>

Related

typescript: req.user is possibly 'undefined' - express and passport js

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);

Optimal way to write middleware for checkingadmin with and without redirecting

I have written an express authentication middleware. The first one which uses app.all('*') is used to setup a flash object which is then used to setup a locals object. The checkAdmin middleware allows the route to proceed but gives me a local variable which I can check in my ejs for displaying portions of the page that should be viewed by admin only. However, other users still have access to this page. They just cannot see everything. Therefore, in my checkAdmin() middleware, I am using return next() wheather a user is admin or not.
The middleware function:
app.all('*',middleware.checkAdmin)
middleware.isAdmin =function(req,res,next){
//Check if the admin is set to true or not
pool.query(`Select RoleID from userroles where UserID = ${req.user.id}`,function(err,rows,fields){
if(err){
console.log(err)
}
if(rows[0]){ //This should not really happen, where the role is undefined. Every user should have a role, but checking it just in case
if (rows[0].RoleID == 1){
return next()
}
}
req.flash("message", "You need to have admin previledge to acccess this page")
res.redirect('back'); //From express 4 onwards this should allow me to redirect to the same page the request came from.
})
}
middleware.checkAdmin=function(req,res,next){
//Check if the admin is set to true or not
if(req.user){
pool.query(`Select RoleID from userroles where UserID = ${req.user.id}`,function(err,rows,fields){
if(err){
console.log(err)
}
if(rows[0]){ //This should not really happen, where the role is undefined. Every user should have a role, but checking it just in case
if (rows[0].RoleID == 1){
req.flash("checkAdmin","admin")
return next()
}
}
return next()
})
}else{
return next()
}
}
app.use(function(req,res,next){
res.locals.currentUser=req.user;
res.locals.error=req.flash("error");
res.locals.success=req.flash("success");
res.locals.message=req.flash("message");
res.locals.checkAdmin=req.flash("checkAdmin"); //I am using this so that I can show the admin navbar element only if the user is signed in as admin
next()
})
My isAdmin middleware is used in routes like:
router.get("/admin", middleware.isAdmin, function (req, res) {
res.render("admin.ejs")
})
I could not find an authenticaiton setup online that solves this issue therefore I came up this this code. But I am not sure if this is an optimal way to do this.
Why not use sessions?
Then you can set the role on login and you can write middleware like this:
function authMiddleware(req, res, next) {
if(req.session.isAdmin) {
next();
}
else {
//set error message here.
res.redirect('/login');
}
}
Then you could add this with app or router .use like this:
//protected routers
app.use('/protected', authMiddleWare, (req, res) => {});
The library you should use for sessions is express-session.
I am not sure this answered your question, I hope it helped.

Migrating Node JS code to Apollo server

I am setting up Apollo Server on my Node app and wondered about moving the functionality over to Apollo.
I have business logic like this:
router.post(
'/login',
(req, res, next) => {
if (!req.body.email || !req.body.password) {
return 'You must send the username and the password.';
}
Users.findOne({ email: req.body.email })
.then(user => {
bcrypt.compare(req.body.password, user.password, (err, success) => {
req.user = user;
next();
});
})
},
auth.createToken,
auth.createRefreshToken,
auth.logUserActivity,
(req, res) => {
res.status(201).send({
success: true,
authToken: req.authToken,
refreshToken: req.refreshToken
});
}
);
It follows Node router architecture where I add the found user object to req object, which passes the user to the next functions - createToken etc.. using the next() function. This was ok for my server before trying to introduce GraphQL/Apollo, but now I want all this logic to be easily accessible to the Apollo resolvers.
I often hear that people are having an easy time turning their server from REST/non-GraphQL into a GraphQL server, but at the moment it's looking like it's going to be a bit of a job to go through all the logic and separate everything in to their own functions which take parameters directly rather than using the req object.
Is this a correct assumption? Or am I missing something?
Thanks!
Migrating the code you have shown above would be a very easy task. Once you build your graphql server and create your schema, etc. Then all you need to do is create login mutation. Then your resolver would handle the logic you have shown above. Then, instead of pulling the values from from req.body they would be function parameters.
A good pattern I am currently following is creating a login method on the model itself. Then the resolver calls the method on the schema (Here is an example of a project I'm doing it on now: Login method. Then here is an example of what the resolver looks like: Resolver
Hopefully that helped!

I am wondering how to communicate between controllers

I want to invoke the user creation API after confirming the token internally in the server when I click the authentication link in the e-mail to implement the membership method using e-mail authentication.
//emailcontroller.js
router.get('/register/token', function(req, res) {
// check token
if(check(req.params.token)) {
request('http://localhost:8080/api/user', function(data) {
});
}
});
//usercontroller.js
router.post('/api/user', function(req, res) {
var user = new User();
user.userId = req.body.userId;
user.userPw = req.body.userPw;
user.save();
});
I want to invoke the user creation API after confirming the token internally in the server when I click the authentication link in email in order to implement membership method using email authentication.
As mentioned above, the email controller and the user controller are divided and each is routed. I want to modularize the code so that I want to call the existing user creation API to use it for general purpose rather than creating and exports common functions for a specific controller.
/*I do not want to implement it this way.*/
//emailController.js
router.get('/register/token', function(req, res) {
// check token
if(check(req.params.token)) {
userContoller.createUserFromEmail(userId, userPw);
}
});
//userController.js
exports.createUserFromEmail = function(userId, userPw) {
var user = new User();
user.userId = userId;
user.userPw = userPw;
user.save();
}
However, I have never seen communication between controllers in many examples. So I do not know if the way I thought was right. Rather, I think the cost of calling api internally on the server might be higher.
I want to know the correct pattern for communication between controllers. Please bear in mind that there is only a stack overflow when raising a question.
You got the right idea about exposing your API functionality as stand-alone functions (or classes). To avoid duplication, just call your internal methods from within your route handlers. So in your example:
router.post('/api/user', function(req, res) {
createUserFromEmail(req.body.userId, req.body.userPw);
});
In my own projects, I use classes to create my API. First I define a class with just the functionality and then I expose the methods in the route handlers:
export default class User {
read() {
}
create() {
}
update() {
}
delete() {
}
}
const user = new User();
router.get('/user/:id', (req, res) => user.read(req.params.id));
router.post('/user', (req, res) => user.create(req.body.data));
router.put('/user/:id', (req, res) => user.update(req.params.id, req.body.data));
router.delete('/user/:id', (req, res) => user.delete(req.params.id));
This should give you an idea of what you can do. You can write custom middleware and class decorators to reduce the boilerplate.
From your question what I understood:
You want to validate internally the token passed in query parameter, before doing anything else in the user controller.
I believe you are using express, and with express comes middlewares.
From docs:
Middleware functions are functions that have access to the request object (req), the response object (res), and the next middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable named next.
What I usually do and a generally good practice is, pass the token in create user api and attach to email body.
for example:
api/user?token=somerandomstringloremispum
Route file:
router.post('/user', validateEmail, userController.create);
here validateEmail is a middleware function and will be invoked before create method of userController.
Now in your validateToken method, you can simply validate your token like:
function validateEmail (req, res, next) {
if(!valid(req.query.token)) {
//return with appropriate invalid token msg using res.json() or however you like
}
// if validated call `next middleware` like so:
next();
// this will allow `create` method of userController be invoked
}

How to get object passed into view from Node for use in Angular?

node
app.get('/profile', function(req, res) {
res.render('profile.ejs', { authUser : req.user });
});
angular
function Ctrl($scope) {
$scope.user = authUser; // from the view
}
I need access to the authUser in the Angular code. I'm familiar with using $http.get to get data from a res.json(...), but am not sure how to do it in this scenario.
res.render is only sending the authUser to the EJS view engine, not back to the client. If you only need to display the user info then you can add something like <%= authUser.displayName %> to profile.ejs.

Resources