HapiJS assert request in handler - hapi.js

I have multiple endpoints starting with /networks/{networkId}/*. I don't want to have logic to find a network and execute some extra validation on it, in every handler. Is there any way to solve this on a higher level? Ex. plugins/server method etc?
In every handler I have the following boilerplate code:
import networkRepo from 'common/repositories/network';
// handler.js
export default (req, reply) => {
return networkRepo.findById(req.params.networkId).then(network => {
// Logic to validate whether the logged user belongs to the network
// Logic where I need the network instance
});
}
The best situation would be:
// handler.js
export default (req, reply) => {
console.log(req.network); // This should be the network instance
}

Best way to achieve what you want is to either create a generic function that you can call first inside your handler or alternatively create an internal hapi route which will perform you look-up and return value to your other handler. Internal routes can then be accesses by server.inject from within your other handler, see options called allowInternals for more details, I can write pseudo-code to help!
[{
method: 'GET',
path: '/getNetworkByID/{id}',
config: {
isInternal: true,
handler: function (request, reply) {
return networkRepo.findById(req.params.networkId).then(network => {
// Logic to validate whether the logged user belongs to the network
// Logic where I need the network instance
reply(network.network);
});
}
}
},
{
method: 'GET',
path: '/api/networks/{id}',
config: {
isInternal: true,
handler: function (request, reply) {
request.server.inject({
method: 'GET',
url: '/getNetworkByID/' + request.params.id,
allowInternals: true
}, (res) => {
console.log(res.result.network) //network
});
}
}
}]

Related

Store Ability in Express Session?

I have seen the express example, where an ability is stored via middleware in the req object. It then uses the following method to evaluate the permissions:
ForbiddenError.from(req.ability).throwUnlessCan('read', article);
I want to achieve a similar thing. My idea is to save the ability inside an express session that is shared with socket io websockets. Through the sharing req.session = socket.handshake.session. My approach is the following, I make a request from the frontend application to get rules to update the ability on the frontend. The backend saves the ability inside the express session:
// abilities.js file
import { Ability } from '#casl/ability';
export const defineAbilitiesFor = (rules) => {
return new Ability(rules);
};
export default defineAbilitiesFor;
// handler for express route to get permissions from the frontend
export const getPermissions = async (req, res) => {
...
rules.push({
action: ['view'],
subject: views,
});
// manage all own processes
rules.push({
action: ['manage'],
subject: 'Process',
conditions: {
userId: req.kauth.grant.access_token.content.sub,
},
});
// store ability in session
req.session.rules = defineAbilitiesFor(rules);
const token = jwt.sign({ token: packRules(rules) }, 'secret');
if (token) {
return res.status(200).json(token);
} else {
return res.status(400).json('Error');
}
...
Then when a websocket request happens, I want to check in the backend if the user has the permissions to do that action:
ForbiddenError.from(socket.handshake.session.rules).throwUnlessCan('view', 'Process');
However, this throws the following error:
TypeError: this.ability.relevantRuleFor is not a function
at ForbiddenError.throwUnlessCan
The session object seems to have the correct ability object. When I console.log socket.handshake.session.rules, I get the following output:
{
h: false,
l: {},
p: {},
'$': [
{ action: [Array], subject: 'Process', conditions: [Object] },
{ action: [Array], subject: [Array] },
{ action: [Array], subject: 'Process', conditions: [Object] }
],
m: {}
}
Also the can function and everything else I tried wasn't working. I think storing the plain rules as an object inside the session and then updating the ability class before each request would work, but I don't want to do that. I want to store the ability right inside the session, so that I only have to execute the throwUnlessCan or can functions.
Is this even possible and if so, how would you do this?
Thanks so far.
Instead of storing the whole Ability instance, you need to store only its rules! rules is a plain js array of objects, so it can be easily serialized.So, change the code to this:
export const getPermissions = async (req, res) => {
...
rules.push({
action: ['view'],
subject: views,
});
// manage all own processes
rules.push({
action: ['manage'],
subject: 'Process',
conditions: {
userId: req.kauth.grant.access_token.content.sub,
},
});
// store ability RULES in session
req.session.rules = rules;
const token = jwt.sign({
token: packRules(rules) // packRules accepts an array of RawRule! not an Ability instance
}, 'secret');
if (token) {
return res.status(200).json(token);
} else {
return res.status(400).json('Error');
}
To use Ability in other handlers add a middleware:
function defineAbility(req, res, next) {
if (req.session.rules) {
req.ability = new Ability(req.session.rules);
next();
} else {
// handle case when there is no rules in session yet
}
}
// later
app.get('/api/users', defineAbility, (req, res) => {
req.ability.can(...);
// or
ForbiddenError.from(req.ability).throwUnlessCan(...);
})

how to add attributes to a PUT request in GUN?

I have the following code in my HTML page
Gun.on('opt', function (ctx) {
if (ctx.once) {
return
}
this.to.next(ctx)
window.auth = ctx.opt.auth
ctx.on('get', function (msg) {
msg.auth = window.auth
this.to.next(msg)
})
ctx.on('put', function (msg) {
msg.put.auth = window.auth
this.to.next(msg)
})
})
var gun = Gun({
peers: ['http://localhost:8765/gun'],
auth: {
user: 'mroon',
password: 'titi'
}
})
On the server, I simply watch the requests
Gun.on('create', function(db) {
console.log('gun created')
this.to.next(db);
db.on('get', function(request) {
// this request contains the auth attribute from the client
this.to.next(request);
});
db.on('put', function(request) {
// this request does not contain the auth attribute from the client
this.to.next(request);
});
});
every time I query the graph with gun.get('someAttribute') the request on the server contains the auth attribute.
but when a gun.get('someAttribute').put({attribute: 'my new value'}) is called, the request on the server does not contain the auth attribute.
How can I add the auth attribute to the put request in such a way that all the peers will get it too?
#micha-roon you jumped straight to GUN's core/internal wire details, which is not the easiest thing to start with, but here is something I do that I'm guessing is what you are looking for:
(if not, please just comment & I'll update)
What this does is it adds a DEBUG flag to all outbound messages in GUN, you can change this to add other metadata or info
Gun.on('opt', function(root){
if(!root.once){
root.on('out', function(msg){
msg.DBG = msg.DBG || +new Date;
this.to.next(msg);
});
}
this.to.next(root);
})
Also another good reference: https://github.com/zrrrzzt/bullet-catcher

How to pass data from pre middleware to route handler?

I have pre response something like this
function middleware(req: HapiRequest, res: Hapi.ReplyNoContinue) {
res({data: "something"})
}
And later I need to access the object from route handler how can I do that?
When defining a route with a prerequisite, you may assign a name for each prerequisite. Like this:
server.route({
method: `get`,
path: `/pre`,
config: {
pre: [
{
method: function (request, reply) {
reply(`pizza`);
},
assign: `cheekibreeki`
}
]
},
handler: function (request, reply) {
reply(request.pre.cheekibreeki);
}
});
I made a route and assigned name cheekibreeki to it's prerequisite which replies pizza. Then the replied data inside a prerequisite is available in route handler inside a request.pre['assignedname'].
Another way is using request.app object.
If you don't want to proceed to the route handler, you must use reply().takeover() method.
Hope this helps.

mean.js angular - express routing

I'm new to the MEAN stack and I'm having some trouble with routing...
I have a module called "applications".
the APIs i want on the server side are:
get: http://localhost:3000/api/applications/(_appid)
getByMakeathonId: http://localhost:3000/api/applications/makeathons/(_mkid)
Applications Service
function ApplicationsService($resource) {
return $resource('api/applications/:path/:applicationId', {
path: '#path',
applicationId: '#id'
}, {
get: {
method: 'GET',
params: {
path: '',
applicationId: '#_id'
}
},
getByMakeathonId: {
method: 'GET',
params: {
path: 'makeathon',
applicationId: '#_id'
}
},
update: {
method: 'PUT'
}
});
Server Routing
app.route('/api/applications').post(applications.create);
app.route('/api/applications').all(applicationsPolicy.isAllowed)
.get(applications.list);
app.route('/api/applications/makeathon/:makeathonId').all(applicationsPolicy.isA llowed)
.get(applications.applicationByMakeathonID);
1) what I'm getting when I call $save and the object and the save is successful, there is a call for .get, and the request URL is: http://localhost:3000/api/applications//56f15736073083e00e86e170 (404 not found)
the problem here of course is the extra '/' - How do I get rid of it.
2) when I call getByMakeathonId, the request url is: http://localhost:3000/api/applications/makeathon?id=56e979f1c6687c082ef52656 400 (Bad Request)
I do I configure so that I'll get the two requests that I want?
10x!
You are getting the repeated // in your request url because you have declared that there will be a :path in your applications resource, and you are providing an empty string to interpolate there.
As $resource is meant to provide RESTful interaction with your API, I think the most appropriate approach would be to have separate $resources to deal with applications and makeathons. Something like this:
For applications:
function ApplicationsService($resource) {
return $resource('api/applications/:applicationId', {
applicationId: '#id'
}, {
update: {
method: 'PUT'
}
});
}
For makeathons:
function MakeathonsService($resource) {
return $resource('api/applications/makeathons/:makeathonId', {
makeathonId: '#id'
}
});
}
/** your server route would then be updated to
* app.route('/api/applications/makeathons/:makeathonId')...
*/

How can I compose handler off Hapijs?

I need to do some additional authentication in a few of my handlers. Is there a way of doing that way in a composable way?
export async function handler(request) {
const user = request.auth.credentials;
const customer = FindCustomer(user);
if (!customer) {
throw Boom.forbidden('user is not a customer');
}
if (!customer.verified) {
throw Boom.forbidden('customer not validated');
}
// actual handler logic
}
Is there a way to wrap this so that some routes already provide the customer in the request object ?
You can make use of the extension points in the request life cycle. In your case, the 'onPostAuth' extension point would be ideal.
server.ext('onPostAuth', function (request, reply){
const user = request.auth.credentials;
const customer = FindCustomer(user);
if (!customer) {
return reply (Boom.forbidden('user is not a customer'));
}
if (!customer.verified) {
return reply(Boom.forbidden('customer not validated'));
}
reply.continue();
});
Complementing ZeMoon's answer, you can implement the onPostAuth like this:
server.ext('onPostAuth', function (request, reply) {
if(request.route.settings.plugins.verifyCustomer) {
const user = request.auth.credentials;
const customer = FindCustomer(user);
if (!customer) {
return reply (Boom.forbidden('user is not a customer'));
}
if (!customer.verified) {
return reply(Boom.forbidden('customer not validated'));
}
}
reply.continue();
});
And then add a configuration plugins.verifyCustomer to the route:
server.route({
method: 'get',
path: '/test1',
handler: function(request, reply) {
// Handler logic here
},
config: {
plugins: {
verifyCustomer: true
}
}
});
i think a more robust way would be to assign scope to the credentials when the customer is authenticated, and to require the scope in the routes you want it.