i want to create a misslware in nodejs for access Level , i create this middlware :
class AccessUser extends middlware {
async AccessUser(access,req, res, next) {
const getTokenFrom = (req) => {
const authorization = req.headers["authorization"];
if (authorization && authorization.toLowerCase().startsWith("bearer ")) {
return authorization.substring(7);
}
return null;
};
const token = getTokenFrom(req);
if (token) {
jwt.verify(token, "shhhhh", (err, decoded) => {
if (err) return new ForbiddenResponse().Send(res);
let permission = decoded.info.permission;
let item = permission.find((x) => x.permissionId == access);
if (!item) {
return new ForbiddenResponse().Send(res);
} else {
next();
}
});
}
}
}
i add the argument name access to input of AccessUser in this middlware :
async AccessUser(access,req, res, next)
and i want to need compare the access with x.permissionId . but it show me this error :
(node:2168) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'authorization' of undefined
this error for this line :
const authorization = req.headers["authorization"];
and i use this middlware by this :
router.post(
"/Create",
access.AccessUser("Role:Add")
);
now whats the problem ? how can i use the middllware with input argument ?????
AccessUser is not a express middleware, the method signature has to be (req,res,next).
You can get around this if you define AccessUser as a factory function (there's no need to define it async as you're not awaiting any async call):
class AccessUser {
accessUser(access) {
return function (req, res, next) {
const getTokenFrom = (req) => {
const authorization = req.headers["authorization"];
if (authorization && authorization.toLowerCase().startsWith("bearer ")) {
return authorization.substring(7);
}
return null;
};
const token = getTokenFrom(req);
if (token) {
jwt.verify(token, "shhhhh", (err, decoded) => {
if (err) return new ForbiddenResponse().Send(res);
let permission = decoded.info.permission;
let item = permission.find((x) => x.permissionId == access);
if (!item) {
return new ForbiddenResponse().Send(res);
} else {
next();
}
});
}// TODO: handle case if no token exists?
}
}
}
module.exports = AccessUser;
Then use it like this:
const AccessUserMiddleware = require('./path-to-access-middleware');
const AccessUser = new AccessUserMiddleware();
app.get('/', AccessUser.accessUser("Role:Add"));
Related
I have 2 servers and one of these is work fine, but second (modified variant of first) is not
`This is not works:
router.post("/", async (req, res, next) => {
const newBriefAppeal = await new BriefAppeal(req.body);
let appealId;
let target;
let goals;
let brand;
let ***;
try {
const savedBriefAppeal = await newBriefAppeal.save(function (err, appeal) {
appealId = appeal.id;
target = appeal.step01target;
goals = appeal.step02goals;
brand = appeal.step03brand;
*** = appeal.***
});
res.status(200).json(savedBriefAppeal);
} catch (err) {
res.status(500).json(err);
}
});
`
and i got error
node:events:491
throw er; // Unhandled 'error' event
^
TypeError: Cannot read properties of undefined (reading 'id')
`but this variant in my similar project works fine:
router.post("/", async (req, res, next) => {
const newAppeal = await new Appeal(req.body);
let appealId;
let name;
let email;
let phone;
let subject;
let message;
let attachments = [];
try {
const savedAppeal = await newAppeal.save(function (err, appeal) {
appealId = appeal.id;
name = appeal.name;
email = appeal.email;
phone = appeal.phone;
subject = appeal.subject;
message = appeal.text;
attachments = appeal.appealAttach.map((attachment) => ({
filename: attachment,
path: "./uploads/media/mailAttachments/" + attachment,
}));
});
res.status(200).json(savedAppeal);
} catch (err) {
res.status(500).json(err);
}
});
Where's i'm wrong and why my appeal is undefined ?
Because you're passing in a callback. As it says in the documentation, save only returns a promise when you don't pass in a callback:
Returns:
...Returns undefined if used with callback or a Promise otherwise.
Either use the old-style callback signature or use the promise feature.
I make a function checkCompanyPermit with paramater companyIdSource and array allowed.
Example:
companyIdSouce: "req.body.companyId", "req.params.companyId"...
allowed: "user", "admin"...
With parameter companyIdSource as string, I want to convert it to data. It's worked if I use eval(companyIdSource) but it's bad. How can I do another?
I try use Function("return " + companyIdSource)() but it return an error: req is not defined.
const checkCompanyPermit = (companyIdSource, ...allowed) => {
return async (req, res, next) => {
try {
const companyId = eval(companyIdSource) //Bad code, change another code
const company = await Company.findById(companyId)
//... some code
} catch (error) {
next(error)
}
}
}
checkCompanyPermit("req.body.companyId", "manager")
Since you already have access to the req object in your middleware, there is no need to pass the full string representation for req.body.companyId, just the property you need to check will suffice. Use the bracket notation to access the value from req.body object i.e.
const checkCompanyPermit = (companyIdSource, allowed) => {
return async (req, res, next) => {
try {
const companyId = req.body[companyIdSource]
const company = await Company.findById(companyId)
//... some code
} catch (error) {
next(error)
}
}
}
checkCompanyPermit("companyId", "manager")
It's Working For You.
const ObjectId = require('mongodb').ObjectId;
const checkCompanyPermit = (companyIdSource, ...allowed) => {
return async (req, res, next) => {
try {
const companyId = ObjectId('companyIdSource') //Replace here new code
const company = await Company.findById(companyId)
//... some code
} catch (error) {
next(error)
}
}
}
checkCompanyPermit("req.body.companyId", "manager")
I already resolve this problem. I find Express.js role-based permissions middleware and use it. It's great and I will re-write my code!
I'm want to check multi permistion but it not working for me.
I create 3 middlwares to check Permistion: requiredAuth, checkAdmin and checkCompanyManager.
Model: User: {name, permistion, isBlocked, company: {id, userPermistion}}
requiredAuth funtion will check and find signedUser, and set it tores.locals.user
const checkAdmin = (req, res, next) => {
let user = res.locals.user
if (user.permission === 2) next()
else res.json({errors: "Only admin can do this action"})
}
const checkCompanyManager = (req, res, next) => {
let user = res.locals.user
let companyId = req.body.companyId ? req.body.companyId : req.query.companyId
if (user.company.id && user.company.id.equals(companyId)
&& user.company.userPermistion === 1) next()
else res.json({errors: "Only company member can do this action"})
}
And last, I use all in router to check action block user (Only admin or company manager can block user)
router.post('/admin/block-by-ids',
requiredAuth,
checkAdmin || checkCompanyManager,
userController.blockByIds
)
But it's not working, because if checkAdmin wrong, it's break and return json, not run checkCompanyManager I can solve this problem as follows:
router.post('/admin/block-by-ids',
requiredAuth,
(req, res, next) => {
let user = res.locals.user
let companyId = req.body.companyId
if ((user.permission === 2) ||
(user.company.id && user.company.id.equals(companyId) &&
user.company.userPermistion === 1)) {
next()
} else next("Only admin or company manager can do this action")
},
userController.blockByIds
)
But it's not fun! I want only use middleware to check and do not want to write code again. How can I do this? I want an idea from you!
The || operator does not do what you think it does. It returns the first truthy value:
var a = 1 || 2; // a is 1
What you need is an OR middleware. Something like:
function or (middleware1, middleware2) {
return function (req, res, next) {
var alreadyCalledNext = false;
function resolve () {
if (!alreadyCalledNext) {
alreadyCalledNext = true;
next();
}
}
middleware1(req,res,resolve);
middleware2(req,res,resolve);
}
}
router.post('/admin/block-by-ids',
requiredAuth,
or(checkAdmin, checkCompanyManager),
userController.blockByIds
)
But the above implementation runs into another problem. Once you've sent res.json you cannot send another response. So if either checkAdmin or checkCompanyManager fails you need to stop them from sending res.json unless both fails. So you need to stub res and pass a fake res (just like what we did with next above):
function or (middleware1, middleware2) {
return function (req, res, next) {
var alreadyCalledNext = false;
function resolve () {
if (!alreadyCalledNext) {
alreadyCalledNext = true;
next();
}
}
var jsonCount = 0;
var fakeRes = {
locals: res.locals,
json: function (data) {
jsonCount ++;
if (jsonCount >= 2) { // both must fail for OR to fail
res.json(data);
}
}
}
middleware1(req,fakeRes,resolve);
middleware2(req,fakeRes,resolve);
}
}
This should work.
IMHO the solution above feels over-engineered. I would personally make checkAdmin and checkCompanyManager regular functions returning boolean then wrap them in a checkPermissions middleware:
const isAdmin = (req,res) => {
let user = res.locals.user
return user.permission === 2
}
const isCompanyManager = (req,res) => {
let user = res.locals.user
let companyId = req.body.companyId ? req.body.companyId : req.query.companyId
return user.company.id && user.company.id.equals(companyId) && user.company.userPermistion === 1
}
const checkPermissions = function (checks) {
return (req, res, next) => {
// Call next if any check passes:
for (let i=0; i<checks.length; i++) {
if (checks[i](req,res)) return next();
}
res.json({errors: "You don't have authorization to do this action"})
}
}
router.post('/admin/block-by-ids',
requiredAuth,
checkPermissions([isAdmin, isCompanyManager]),
userController.blockByIds
)
How can I import this code in module.exports ?
I'm pretty new in node js and in js.
I want that this code could be used in other routes
cache = (duration) => {
return (req, res, next) => {
let key = '__express__' + req.originalUrl || req.url
let cachedBody = mcache.get(key)
if (cachedBody) {
res.send(cachedBody)
return
} else {
res.sendResponse = res.send
res.send = (body) => {
mcache.put(key, body, duration * 1000);
res.sendResponse(body)
}
next()
}
}
}
How can I export it?
I was something like :
module.exports = cache = (duration) => {
return (req, res, next) => {
let key = '__express__' + req.originalUrl || req.url
let cachedBody = mcache.get(key)
if (cachedBody) {
res.send(cachedBody)
return
} else {
res.sendResponse = res.send
res.send = (body) => {
mcache.put(key, body, duration * 1000);
res.sendResponse(body)
}
next()
}
}
}
Also I try:
module.export = {
cache: function(duration) {
return (req, res, next) => {
let key = '__express__' + req.originalUrl || req.url
let cachedBody = mcache.get(key)
if (cachedBody) {
res.send(cachedBody)
return
} else {
res.sendResponse = res.send
res.send = (body) => {
mcache.put(key, body, duration * 1000);
res.sendResponse(body)
}
next()
}
}
}
}
But when I try to use it in a get request:
var expCache = require('../../middleware/cache');
router.get('/:sid/fe',expCache.cache(3000),function(req,res) {
It brings:
TypeError: expCache.cache is not a function
Regards
You need to export an object, if you're expecting to be able to call expCache.cache:
module.exports = {
cache: // your function
}
However, if you want to keep your exported module as it is, just call it like this instead:
// inside middleware/cache:
// module.exports = cache = (duration) => {
var expCache = require('../../middleware/cache');
// you can call it as just a function
router.get('/:sid/fe', expCache(3000), function(req,res) {
Try
var expCache = require('../../middleware/cache');
router.get('/:sid/fe',expCache(3000),function(req,res) {..
You're exporting your cache function already, not an object containing it (which is how you try to use it for your router).
I have researched the topic for hours on Google and books and I could only find very specific implementations. I'm struggling to write a simple Middleware class in node JS with only plain vanilla javascript (no additional module like async, co,..). My goal is to understand how it works not to get the most optimised code.
I would like something as simple as having a string and adding new string to it thru the use of middleware.
The class
"use strict";
class Middleware {
constructor() {
this.middlewares = [];
}
use(fn) {
this.middlewares.push(fn);
}
executeMiddleware(middlewares, msg, next) {
// This is where I'm struggling
}
run(message) {
this.executeMiddleware(this.middlewares, message, function(msg, next) {
console.log('the initial message : '+ message);
});
}
}
module.exports = Middleware;
A possible usage
const Middleware = require('./Middleware');
const middleware = new Middleware();
middleware.use(function(msg, next) {
msg += ' World';
next();
});
middleware.use(function(msg, next) {
msg += ' !!!';
console.log('final message : ' + msg);
next();
});
middleware.run('Hello');
As a result the msg variable will end up being : 'Hello World !!!'
For those looking for a working example.
// MIDDLEWARE CLASS
"use strict";
let info = { msg: '' };
class Middleware {
constructor() {
this.middlewares = [];
}
use(fn) {
this.middlewares.push(fn);
}
executeMiddleware(middlewares, data, next) {
const composition = middlewares.reduceRight((next, fn) => v => {
// collect next data
info = data;
fn(info, next)
}, next);
composition(data);
}
run(data) {
this.executeMiddleware(this.middlewares, data, (info, next) => {
console.log(data);
});
}
}
module.exports = Middleware;
Usage example :
// index.js
const Middleware = require('./Middleware');
const middleware = new Middleware();
middleware.use(function(info, next) {
info.msg += ' World';
next();
});
middleware.use(function(info, next) {
info.msg += ' !!!';
next();
});
// Run the middleware with initial value
middleware.run({msg: 'Hello'});
The accepted answer has a few issues, namely unused/uneeded variables. Also id just like to post an alternative answer since thats what stackexchange is for.
Usage example is the same
class Middleware {
constructor() {
this.middlewares = [];
}
use(fn) {
this.middlewares.push(fn);
}
executeMiddleware(data, done) {
this.middlewares.reduceRight((done, next) => () => next(data, done), done)
(data);
}
run(data) {
this.executeMiddleware(data, done => console.log(data));
}
}
class InputValidation {
constructor() {
}
getData(req, res, next) {
console.log(req)
//your stuff here
next();
}
}
module.exports = new InputValidation();
//Just import into main module and make a middleware
const InputValidation = require("./validation/inputValidation");
app.use(InputValidation.getData);