How to use only PART of the functionality in nested routes NodeJS - node.js

So I have a router schoolsRouter where all the school-specific functionality is being handled { login school, adding a new teacher, ...etc.). And I want the admin of the app to be able to add and delete new schools. Now the pattern I'm using encapsulates all the routing functionality in one file schools.routes.js where the School model is exposed. So the createSchool and deleteSchool routes are in the schools.routes.js but I need only the admin to be able to perform those operations and that seems pretty easy with merged routes like this (in admins.routes.js):
adminsRouter.use('/schools/', schoolsRouter);
but the problem is that now the admin can access all the other routes in schools.routes.js like schools/login which is something that I don't want to happen. So how can I make the adminsRouter use the create and delete operations from the schoolsRotuer without being able to access all these other functionalities? (Keeping in mind I'm using JWT authentication).

You could use middlewares in the routes that you wish to controll.
This is the middleware that I will name of admin-middleware.js
module.exports = (req, res, next) => {
if (user.admin === true) {
return next();
} else {
return res.status(403).send('Unauthorized')
}
}
So, this is your route declaration at schools.routes.js
const adminMiddleware = require('../YOUR_FOLDERS/admin-middleware.js');
schools.delete('/:id', adminMiddleware, (req, res) => {
return res.send('ok');
});
If you wish disregard a route, you can use this validation at your code in the middleware.
if(req.originalUrl.includes('/schools/login'))
return next();
I hope that it works to you.

Related

Override NestJS route within controller

I want to completely override a controller route. E. g:
#Controller('shipments')
export class ShipmentsController {
#Post('/create')
async find(): Promise<Activities> {
return service.find()
}
}
In order to make a request to the previous example, The URL will be: http://localhost:8080/shipments/create
I want to change that URL without moving the controller to another class. For example, I want the URL for that specific function to be http://localhost:8080/whatever/i/want.
Is this possible?
This is not possible, and goes against the ideas of the framework of having easy to configure routes with structure and uniformity. If you want a route like that, you can use express on it;s own, or technically add the route in the bootstrap file like so
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.getHttpServer().get('/whatever/you/want', (req, res, next) => {});
await app.listen(3000);
}
But now you don't have (easy) access to services, testing this is a pain, and generally it's confusing, not to mention no use of any Nest enhancers like interceptors or pipes.

per-request session in meteor server?

I am adding an auth layer and I think I have it figured out except for one tricky detail.
My Meteor app doesn't have any routes but I've added a hook into the connect middleware so that the "/" route errors if there isn't a correct API token. If the token is okay then I call next() to forward the route to Meteor.
The problem is that, depending on the token, I need to set server-side parameters for the connection, and I don't know how to do this. For example, say I have a static list of API keys mapped to permission levels. If a user sends a request with "ADMIN_API_KEY" then I would like to set Session.permission_level = "admin" for use by the Meteor server's functions. Session is just for the client in Meteor, though.
# this code's in coffeescript
WebApp.connectHandlers.use '/', (req, res, next) ->
validator = new RequestValidator(req, next)
validations = [
"valid_namespace",
"only_https"
]
error = validator.validate(validations)
next(error)
# <<<<<<<<<<<<<<<<<<<<<<<<
# Here I want to set some config option which can be
# read by the server in the same way it can read things like
# Meteor.user()
In Rails I would just say session[:permission_level] = "admin". But it seems to not work this way in Meteor.
By the way, I am not using a Routing package yet in Meteor, though if that would make this easier than I would.
I'm not sure about Session I've been doing something like
import { DDP } from 'meteor/ddp';
import { DDPCommon } from 'meteor/ddp-common';
export const authMiddleware = (req, res, next) => {
const userId = identifyUser(req); // parse the request to get the token you expect
if (!userId) {
return next();
}
DDP._CurrentInvocation.withValue(new DDPCommon.MethodInvocation({
isSimulation: false,
userId,
}), () => {
next();
// in that context, Meteor.userId corresponds to userId
});
};
for my REST api and that works well regarding the user Id and being able to call Meteor function that should be invoke in a DDP context, like Users.find(...).

Unable to register Express middleware

I'm trying to write a very basic piece of middleware for Express that checks to see if a user has some specified role required to access a resource. I have another piece of middleware that comes before this, which adds a user object to the request req for every route requiring authentication (and subsequent authorization).
As such, I define the authorization middleware like this:
_ = require('lodash');
function authorize(req, res, next, roles){
// check to see if user has one of the allowed roles
if(_.contains(roles, req.user.role)){
req.authorized = true;
return next();
}
// otherwise, pass an error
return next(new Error("Unauthorized"));
}
Every user object has a property called role on it, so I use _.contains(roles, req.user.role) to figure out whether or not the allowed roles contain the user's assigned role.
However, when I do this, I get TypeError: Cannot read property 'role' of undefined as soon as I start my Express server. This seems very weird to me, because I have not even made a request, and so of course req.user will be undefined until then.
Is there a way around this?
Example of how I use this middleware:
var app = express();
var router = express.Router();
router.get('/protected/:id', authorize(['ADMINISTRATOR', 'MANAGER', 'OWNER']), controllers.protected.retrieve);
When you register the route with
router.get(
'/protected/:id',
authorize(['ADMINISTRATOR', 'MANAGER', 'OWNER']),
controllers.protected.retrieve
)
the authorize method gets executed straight away by authorize(...) with the ['ADMINISTRATOR', ...] array being passed as the req param. Hence it is called as soon as you run the code and dies on user object not being present. Even if it didn't die on that, it wouldn't work as intended. You are mixing a middleware and a factory function together.
Express middleware is a function with a (req, res, next) signature, that you don't execute yourself. You need to pass a reference to such a middleware function and Express itself executes it on the request when needed, i.e.:
function authorize(req, res, next) {
...
};
router.get('/protected/:id', authorize, ...);
A parametrized middleware function, as in your case, can be easily created by splitting up to a factory and a middleware function:
// a factory function to create authorization middleware functions for given roles
function authorize(roles) {
// create and return an actual authorization middleware function
// to handle requests using the roles given when created
return function(req, res, next) {
if(_.contains(roles, req.user.role)){
req.authorized = true;
return next();
}
return next(new Error("Unauthorized"));
}
}
router.get(
'/protected/:id',
authorize(['ADMINISTRATOR', 'MANAGER', 'OWNER']),
controllers.protected.retrieve
)

Is it possible to apply basic authentication / middleware in on routes with a whitelist in Express?

I'm implementing a RESTful API with Express in Node, and I'm new to both. I'd like to use basic authentication to control access.
I would like to apply it using something like a whitelist but I'm not sure how to do that.
Blacklisting is easy, I can just pepper my #VERB calls with the second argument:
app.get('/', asyncAuth, requestHandler);
I can take that even further and blacklist everything with:
app.all('*', asyncAuth, requestHandler);
But I want to apply my basicAuth to every single route, except for POST /users. Is there an elegant way to do that? Can I use the 'blacklist' approach then selectively remove it from the routes I'd like? I couldn't figure out how.
Define your route for POST /users before the blacklisted routes:
app.post('/users', function(req, res) {
...
});
app.all('*', asyncAuth, requestHandler);
You could maintain a list of regexps that are whitelisted, and match the url against each url in the list, if it matches any then proceed, else require auth
app.all('*', asyncAuth);
function asyncAuth(req, res, next) {
var done = false;
whitelist.forEach(function(regexp) {
if (req.url.match(regexp)) {
done = true;
next();
}
});
if (!done) requireAuth(next);
}
Something along those lines

NodeJS + Express: How to secure a URL

I am using latest versions of NodeJS and ExpressJS (for MVC).
I usually configure my rest paths like this, for example:
app.get('/archive', routes.archive);
Now i want my /admin/* set of URLs to be secured, I mean I need just simple authentication, it's just a draft.
When a user tries to access, for example, /admin/posts, before sending him the corresponding view and data, I check for a req.session.authenticated. If it's not defined, I redirect to the login page.
Login page has a simple validation form, and a sign-in controller method: if user does send "right user" and "right password" I set the session variable and he's authenticated.
What I find difficult, or I don't understand, is how to actually make the "filter" code, I mean, the auth check, before every /admin/* path call.
Does this have something to do with "middleware" express functions?
Thank you
Yep, middleware is exactly what you want. A middleware function is just a function that works just like any other Express route handler, expept it gets run before your actual route handler. You could, for example, do something like this:
function requireLogin(req, res, next) {
if (req.session.loggedIn) {
next(); // allow the next route to run
} else {
// require the user to log in
res.redirect("/login"); // or render a form, etc.
}
}
// Automatically apply the `requireLogin` middleware to all
// routes starting with `/admin`
app.all("/admin/*", requireLogin, function(req, res, next) {
next(); // if the middleware allowed us to get here,
// just move on to the next route handler
});
app.get("/admin/posts", function(req, res) {
// if we got here, the `app.all` call above has already
// ensured that the user is logged in
});
You could specify requireLogin as a middleware to each of the routes you want to be protected, instead of using the app.all call with /admin/*, but doing it the way I show here ensures that you can't accidentally forget to add it to any page that starts with /admin.
A even simpler approach would be to add the following code in the App.js file.
var auth = function(req, res, next) {
if(isAdmin) {
return next();
} else {
return res.status(400)
}
};
app.use('/admin', auth, apiDecrement);
As you can see the middleware is being attached to the route. Before ExpressJS goes forward, it executes the function that you passed as the second parameter.
With this solution you can make different checks before displaying the site to the end user.
Best.
Like brandon, but you can also go the connect route
app.use('/admin', requireLogin)
app.use(app.router)
app.get('/admin/posts', /* middleware */)

Resources