Sails.js Policies, is there an OR operator to allow an action if one of a group of policies succeeds? - policy

When configuring policies in sails in config/policies.js such as:
ActivityController: {
create: ['authenticated'],
update: ['authenticated', 'isActivityOwner'],
destroy: ['authenticated' ,'isActivityOwner']
}
Is there any functionality that would allow me to grant access to the action provided one or more of a group of policies succeeds maybe something like:
ActivityController: {
create: ['authenticated'],
update: ['authenticated', {or:['isActivityOwner', 'isAdmin']}],
destroy: ['authenticated' ,'isActivityOwner']
}
Alternatively is it possible to create composite policies so that in one policy I may check one or more other policies?
If both of these options seem like poor solutions, can you suggest an approach that would would be considered better practice?
Forgive me if this is a bit obvious but I'm fairly new to sails and node in general, and thanks in advance for any help!

I haven't found any official support for operators in sails policies but here is what I am doing.
ActivityController: {
update: ['authenticated', 'orActivityOwner', 'orAdmin', orPolicy],
}
Both orActivityOwner and orAdmin return next() as if they are valid. But they also set a boolean value to a session variable. Remember, policies are executed from left to right. I added an orPolicy to the end which will then evaluate the state of our session variable.

check out sails-must:
ActivityController: {
create: 'authenticated',
update: ['authenticated', must().be.the.owner.or.be.a.member.of('admins')],
destroy: ['authenticated', must().be.the.owner]
}

I've created a sails hook to be able to add parameters to policies:
https://github.com/mastilver/sails-hook-parametized-policies
I've setup an example where I defined an or policy:
module.exports = function(firstPolicy, secondPolicy){
return function(req, res, next){
var fakeRes = {};
for(var i in res){
if(i === 'forbidden'){
// override the functions you want the `or` factory to handle
fakeRes[i] = function(){
secondPolicy(req, res, next);
};
}
else{
fakeRes[i] = res[i];
}
}
firstPolicy(req, fakeRes, next);
}
}
Which you can use that way:
ActivityController: {
create: ['authenticated'],
update: ['authenticated', 'or(isActivityOwner, isAdmin)'],
destroy: ['authenticated' ,'isActivityOwner']
}

Just to complete the previous answer, that works like a charm :
Piece of information
But they also set a boolean value to a session variable
I myself prefer setting this boolean to the req object, that :
Is more semantic (access granted or not to ressource for the request, not for entire session)
Does not requires me to manually reset this variable
(I should add that, if you DO want to use session like in #Travis solution , the last orPolicy policy must reset (even unset) the variable in order to protect the next request)
My implementation
config/policies.js :
MyController: {
find: ['orIsTest1', 'orIsTest2', 'protectedResourceGranted']
}
api/policies/orIsTest1.js :
module.exports = function(req, res, next) {
req.protectedResourceGranted = req.protectedResourceGranted || WHATEVERFIRSTTEST;
return next();
};
api/policies/orIsTest2.js
module.exports = function(req, res, next) {
req.protectedResourceGranted = req.protectedResourceGranted || WHATEVERSECONDTEST;
return next();
};
api/policies/protectedResourceGranted.js
module.exports = function(req, res, next) {
if(req.protectedResourceGranted) {
return next();
}
return res.forbidden();
};
NB: Just answering 'cause I haven't got enough reputation to comment.

The other answers here work great, but here is an implementation that I find slightly cleaner.
Instead of creating policies designed for an OR situation that call next() even though they should fail, you can modify your existing policies to use in an AND/OR context, while hardly changing their behavior. Then create a composite policy (like the OP suggested) that checks the modified existing policies.
config/policies.js with example controllers and policies:
AdminController: {
adminDashboard: 'isAuthenticated',
},
ItemController: {
findOne: 'isPublishedOrIsAuthenticated'
}
api/policies/isAuthenticated.js and api/policies/isPublished.js and any other policy you want to use as a part of an AND/OR check:
If next was set to the true boolean (as opposed to a callback), just return true or false before the policy would normally return next(), res.notFound(), etc.
module.exports = function(req, res, next) {
// do some checking
if(next === true) return true; // or return false
return next();
};
Note that we need to use the triple-equals sign here.
api/policies/isPublishedOrIsAuthenticated.js
module.exports = function(req, res, next) {
var isPublished = require('./isPublished.js');
var isAuthenticated = require('./isAuthenticated.js');
// This reads like what we are trying to achieve!
// The third argument in each call tells the function to return a boolean
if(isPublished(req, res, true) || isAuthenticated(req, res, true))
return next();
return res.notFound();
};

Related

express-validator on PUT methods

I'm creating an API and decided to use express-validator for validation (duh), I've never used this before so I'm unsure on some aspects of it so my validations might not be the best but I'm getting by.
I have built two validation middle ware using this and export them from the same folder like this:
module.exports = {
create: require('./create'),
update: require('./update')
}
So I can then do this in my router:
const validation = require('../validations/plotValidation')
// ...
router.get('/', controller.all)
router.post('/create', validation.create(), controller.create)
router.get('/:plotId', controller.read)
router.put('/:plotId/update', validation.update(), controller.update)
router.delete('/:plotId/delete', controller.delete)
// ...
I'm not good enough with express-validator to do both validate both routes with the same file, maybe I'll try it at some point, anyway.
The .post method works fine and validates everything I want it to however the .put method just seems to be ignoring every check here are is file in case you want to see the checks:
const { body, check, param } = require('express-validator');
module.exports = () => {
return [
param('plotId')
.exists().withMessage('URI requires plot id'),
body('price')
.optional()
.isObject()
]
}
As you can probably tell I only just stated it, but even with only these two tiny checks it just doesn't seem to run.
Does express-validator not work on PUT methods?
For anyone else who has this issue I solved this by using .run on my checks, you can read more about this here essentially this is the code that saved me:
// parallel processing
const validate = validations => {
return async (req, res, next) => {
await Promise.all(validations.map(validation => validation.run(req)));
const errors = validationResult(req);
if (errors.isEmpty()) {
return next();
}
res.status(400).json({ errors: errors.array() });
};
};

Loopback3 - Find current user by access token

I have read in Loopback3 docs that getCurrentContext() has been deprecated. What I'd like to do is grab the access token and use that to find the associated user in the db, so I can get a company_id that the user belongs to and alter the query to include that in the where clause. I am using MySQL and a custom UserAccount model which extends from User.
I am new to Loopback so struggling to figure this out especially as most of the online help seems to point to getCurrentContext();.
I've set up some middleware to run on the parse phase:
middleware.json
"parse": {
"./modifyrequest": {
"enabled": true
}
}
modifyrequest.js
var loopback = require('loopback');
module.exports = function(app) {
return function tracker(req, res, next) {
console.log('Middleware triggered on %s', req.url);
console.log('-------------------------------------------------------------------');
console.log(req.accessToken);
}
};
However req.accessToken is always undefined. I have added to server.js:
app.use(loopback.token());
Any ideas? Is this the wrong approach ?
SOLUTION
As per Kamal's comment below...
Try setting "loopback#token": {} in middleware.json under "initial:before"
This populates req.accessToken
First, try setting "loopback#token": {} in middleware.json under "initial:before".
Then, if you are accessing accessToken from request object, you can find the userId within that accessToken object. Try to log req.accessToken, you will find the userId therein.
You can use that user id to search for corresponding user in the database.
User.findById(req.accessToken.userId, function(err, user){
if(err) {
//handle error
}
else {
//access user object here
}
});

Restify: Set default formatter

Also asked in official Restify repo: #1224
Hi,
Is it possible to have one default formatter that can handle any accept type that is not defined.
For Example:
restify.createServer({
formatters: {
'application/json': () => {},
// All other requests that come in are handled by this, instead of throwing error
'application/every-thing-else': () => {}
}
});
By all appearances, this is impossible. Since the formatters are stored in a dictionary, there is no way to create a key that matches every input (that would kind of defeat the point of a dictionary anyway...) The only way to accomplish this kind of thing outside of JSON would be with a regular expression, and regular expressions don't work with JSON.
Here is a program I wrote to test this.
var restify = require("restify");
var server = restify.createServer({
formatters: {
'application/json': () => { console.log("JSON") },
"[\w\W]*": () => { console.log("Everything else") } // Does not work
}
});
server.get("/", (req, res, next) => {
console.log("Root");
res.setHeader("Content-Type", "not/supported");
res.send(200, {"message": "this is a test"});
next()
});
server.listen(10000);
Also here is a link to the documentation on this in case you can find some hint that I couldn't see.
Restify documentation

How do I call {express,connect}.bodyParser() from within middleware?

I have some custom middleware. In some of my handlers, I want to use req.body, but that only works if the user did
app.use(express.bodyParser());
I could always tell the user, "you must use express.bodyParser() first," but I prefer to be safe and instantiate it if it has not been loaded yet.
Is there any way to invoke express.bodyParser() or connect.bodyParser() inside middleware?
I'm not sure if this is will work and if it'll always work, but I believe it'll be the "closer" way of doing what you want, without depending on connect/express (thing that you haven't specified in your question).
// Beware that the main module of the node process _must_
// be able to resolve express with this! This will allow to not depend on express.
var express = require.main.require( "express" );
// If you can depend on express, use this instead!
//var express = require( "express" );
function yourMiddleware( req, res, next ) {
var me = function( req, res ) {
// do your things
next();
};
if ( req.body === undefined ) {
express.bodyParser()( req, res, me );
} else {
me( req, res );
}
}

Documentation for "ensureAuthentication" "isAuthenticated" passport's functions?

I've been looking for a while, and can't see to find a definitive documentation source. When I search for these, the first Google results are to StackOverflow.
Are there any more middleware functions similar to this?
While not explicitly documented anywhere easily found, you can see where the the isAuthenticated and isUnauthenticated flags are set in the Passport code at https://github.com/jaredhanson/passport/blob/a892b9dc54dce34b7170ad5d73d8ccfba87f4fcf/lib/passport/http/request.js#L74.
ensureAuthenticated is not official, but can be implemented via the following:
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated())
return next();
else
// Return error content: res.jsonp(...) or redirect: res.redirect('/login')
}
app.get('/account', ensureAuthenticated, function(req, res) {
// Do something with user via req.user
});
the reason it return false is mostly because its declared below the route definition.
i am doing it in other file so i use it like this
//auth check
function auth(req,res,next){
if(req.isAuthenticated()){
next();
}
else{
res.redirect("/fail");}
}
//routes
require("./routes/myroute")(app,auth);