Add a new route with SailsJS - javascript

I'm newbie with SailJS.
I want to create a new route, so I added this line in config/routes.js:
'GET /operation/:operationId/sums': 'OperationController.getSums',
Then my function:
getSums: async function (req, res) {
return res.status(200).json("OK");
}
But I just get:
Forbidden
with a 403 status code. Other routes works well.
What can I do to fix it ?

Probably some middleware is rejecting this request. Try looking in your config/policies.js file.
It's possible there to set default permissions for all routes, or all routes for a given controller to false, ie, permit nobody (and in that case then you'd usually go in and override the defaults for particular routes).
Look for '*': false or OperationController: { '*': false }. If you don't see those, then try to see what routes are attached to your new route. Possibly, a custom created policy is rejecting the request.

Related

Same passport js strategy with different configuration (SAML)

I'm trying to create SSO in my Nest.js application using Okta as Identity Provider and passport-saml library. I read documentation of Nest authentication and passport-saml. I have no problems with understanding of examples, but I really need to use SAML strategy with different configuration which depend on request body value of POST api/auth/saml. In other words, I have one strategy, but with different entryPoint, issuer, cert params for my custom LoginSSOStrategy class which extends PassportStrategy class of Nest.js. Any ideas how can I handle this?
I'm not quite sure if this is a good approach, but if you want, you can make the class request scoped and inject the request via the constructor, then have access to the request object and be able to work with a new instance of your passport strategy each request. You can use the request to pass req.whatever to the super() class's constructor.
#Injectable({ scope: Scope.REQUEST })
export class LoginSSOStrategy exends PassportStrategy(Strategy) {
constructor(#Inject(REQUEST) request: Request, ...) {
super({/* options matching to request.field */});
}
validate(/* validate params*/) {
/* validate functionality */
}
}
This seems like something you'll want to do a lot of testing around and making sure it works with concurrent requests, but overall it could work, in theory at least.
I think this issue is similar to this GitHub issue. Due to the global nature of Passport.js and the fact that Nest is unable to determine which routes use a Passport passport strategy it is not possible to create a request scoped Passport strategy by using #Injectable({ scope: Scope.REQUEST }).
Recently, I had to implement a Azure Active Directory login with a dynamic redirect URL based on some data from the incoming request. Depending on the Strategy you use, you might be able to overwrite some options when calling the authenticate method of a Passport Strategy by using the (undocumented) extraAuthReqQueryParams property.
One way to know wether or not you're able to overwrite some options is to check the documentation, if you're feeling lucky you could look in the source code of the Passport strategy you're using. After reading about the undocumented feature and seeing these lines in the source code of the Azure AD Passport strategy (in particular lines #1355 and #1374) I was able to change the value I previously specified in the redirectUrl property by using the redirect_uri property (note the slight difference here).
#Injectable()
export class AzureOIDCStrategy extends PassportStrategy(OIDCStrategy,'AzureOIDC') {
constructor() {
super({
// Even though it is overwritten in the 'authenticate' method the Passport Strategy expects this to be set to a valid URL.
redirectUrl: `https://your-backend-domain.com/auth/azure/callback`,
// This ensures we have access to the request in the `authenticate` method
passReqToCallback: true,
});
}
authenticate(req: Request, options: Record<string, any>): void {
return super.authenticate(req, {
// `options` may contain more options for the `authenticate` method in Passport.
...options,
extraAuthReqQueryParams: {
// This overwrites the `redirectUrl` specified in the constructor
redirect_uri: `https://${req.headers.host}/auth/callback`,
},
});
}
}
I hope you will be able to apply this 'strategy' to update the entryPoint, issuer and cert params.
In an Express app you could do something like this:
app.get('/login',
(req, res, next) =>
passport.authenticate('azure-ad', {
extraAuthReqQueryParams: {
redirect_uri: `https://${req.headers.host}/auth/callback`,
},
})(req, res, next)
);

SailsJS policy on request method rather than route

Is there any way I can trigger a policy on a specific request method (e.g. DELETE) rather than on specific routes?
I'd imagine something like this:
module.exports.policies = {
'DELETE *': 'isAdmin'
}
My goal here is to expose the blueprint api to admins only, so that I can keep it in production, as it's a very useful tool for allowing third party scripts to add extra functionality.
I'm on Sails 1.0 right now.
One way to do that might be to add the check for the request method to the actual admin policy, however that doesn't quite seem like the best solution to me.
You can override the blueprint for all models for a particular method. You can do this for DELETE by creating a file destroy.js in /api/blueprints/ and then adding your code for what you want to do when a DELETE comes through:
module.exports = function(req,res, next) {
if(ACLService.hasPermission(req.user.acl, 'admin')) {
//Ok to allow delete here
} else {
return res.unauthorized();
}
};
This is how I've done it in the past, but looking at the docs for the just released SailsJS 1.0:
https://sailsjs.com/documentation/reference/blueprint-api
You may need to add this hook for overriding blueprints in 1.0
https://www.npmjs.com/package/sails-hook-custom-blueprints
Here is one method that you can use, I am not claiming that it is the right way, but you can consider it:
You can write your own hook. How to do this: https://sailsjs.com/documentation/concepts/extending-sails/hooks/project-hooks
Basically here is the solution with a hook:
1 Create a hooks folder under your api folder.
2 In the hooks folder create another folder - the name will be the name of your hook (say my-hook).
3 In api/hooks/my-hook create a file index.js and in it put the following code:
module.exports = function myHook(sails) {
return {
routes: {
before: {
'/*': function (req, res, next) {
if (req.method.toUpperCase() === 'DELETE') {
return sails.hooks.policies.middleware.isadmin(req, res, next); // note - your policy function name must be called here with all lowercase, otherwise it will not work.
}
return next();
}
}
}
};
};
Then in your isAdmin.js policy you can check if your user is an admin and if not:
return res.forbidden();
if it is admin:
return next();

Node.js:Route precedence and ordering (sails.js)

In my sails.js application i have two routes like this:
'/': {controller:'HomeController',action:'home'},
'GET /:category/:subcategory/:keyword':{controller:'SearchController',action:'index'
When I run the default route (/) it will always execute this route
GET /:category/:subcategory/:keyword .
Why is this happening??
The order of routes in route file is
1) /
2) GET /:category/:subcategory/:keyword
As mentioned in the comment above, your very general route /:category/:subcategory/:keyword is being hit because it must match asset urls on your homepage. This route will match any three-part path, ex:
/images/icons/smiley.png
/scripts/thirdparty/jquery.min.js
Etc!
There would be two approaches to fix this. One would be making your SearchController urls more specific. Maybe /search/:category/:subcategory/:keyword would be a good idea? This is the simplest and should clear up any conflicts with your assets right away.
But if you really need catch-all routes that can interfere with other specific routes, then the solution is to catch the specific routes first. For example, in routes.js:
'GET /images/*': 'RouteController.showAsset',
'GET /scripts/*': 'RouteController.showAsset',
'GET /styles/*': 'RouteController.showAsset',
//...
'GET /:category/:subcategory/:keyword': 'SearchController.index',
Then create a controller RouteController with the method:
showAsset: function(req, res) {
var pathToAsset = require('path').resolve('.tmp/public', req.path);
// ex should be '.tmp/public/images/icons/smiley.png'
return res.sendfile(pathToAsset);
},
You may need to add something in to check for file existence first, but this is the idea.
I found this approach worthwhile when I wanted a /:userName route that would not conflict with all of my /contact, /about, /robots.txt, /favicon.ico, etc. However, it takes work to maintain, so if you think the first approach can work for you, I would use that.

Ember Route gets stuck after error loading model

I've run into an annoying issue when loading data asynchronously in an ember route's model callback. The issue seems to be that if the model method of my route returns a promise which is rejected then the route will never attempt to re-evaluate that route model. It just automatically returns the same rejected promise the next time it tries to go to that route without even trying to re-fetch the data!
I understand from this answer that an ember route will only call it's model method when trying to convert the url into a model. I'm guessing that in the case of routes with dynamic segments it may be called if it has never encountered that particular dynamic segment before.
Here is what I've got in my router setup.
window.App = Ember.Application.create({
LOG_TRANSITIONS: true,
LOG_TRANSITIONS_INTERNAL: true
});
App.Router.map(function() {
this.route('login');
this.resource('users', { path: '/users' }, function() {
this.resource('user', { path: '/:user_id' });
this.route('create', { path: '/create' });
});
});
And this is my route.
App.UserRoute = Ember.Route.extend({
model: function(params) {
// This returns a promise
return App.User.fetch(params.user_id);
}
});
I have some special handling for errors in my application route so that routes which fail due to authentication exceptions redirect the user to the login screen.
App.ApplicationRoute = Ember.Route.extend({
actions: {
sessionExpired: function() {
this.controllerFor('login').set("tokenExpired", true);
this.transitionTo('login');
},
error: function(err) {
if (err.type === "TokenException") {
this.send('sessionExpired');
}
}
}
});
The Problem
I navigate to the /users route
For some reason my token expires (inactivity, whatever...)
I navigate to the /users/1 route
The route's model method returns a promise which rejects and I am kicked out to the login screen
I log back in and try to navigate back to the /users/1 route
The route automatically just returns the same failed promise it did last time and I'm kicked out to the login screen. :(
I'm thinking that what I want is some way to clear all the evaluated route models after a user logs in. If this was a multi-user system and one user logs out and another user logs in on the same computer without refreshing the page then that new user shouldn't have routes automatically resolved from the previous user's session.
This seems to me like it would be a common problem yet I can't find any sort of app-wide invalidate cache method. How should I solve this?
I'm not sure where ember data stands on the cache clearing feature, but here is one way to do it
clearCache: function (type) {
var map = App.store.typeMapFor(type);
map.idToCid = {};
map.clientIds = [];
map.recordArrays = [];
map.findAllCache = null;
}
And here is an example as to how the ember firebase library handles a fail find using cache clearing.
delete store.typeMapFor(store.modelFor('user')).idToRecord[username];
Full example here:
https://github.com/firebase/emberFire/blob/master/examples/blog/js/app.js
For anyone else who finds this - I never found a way to reset the ember application and cause it to forget all resolved routes. I did find a few other work-arounds.
In the end, I opted to just window.reload() any time that a user logged out of the system or had their authentication token expire.
Authenticated URLs
Another reasonable approach would be to put a random unique id in the hash state. Essentially just do this.
Instead of a route like:
#/contacts/1
prefix every authenticated route with some kind of unique id
#/PyUE4E+JEdOaDAMF6CwzAQ/contacts/1
App.reset
I tried tried a number of things. One of the more promising things I tried was redirecting to the login screen and using the Application's reset method on my global App object. http://emberjs.com/api/classes/Ember.Application.html#method_reset
That didn't work though, it seems that even a reset Application remember's the models of any routes that it has resolved - weird.

Nodejs and Connect "next" functionality

It seems that if I want to move to a "next" function in Nodejs (and possibly Javascript in general?) I cannot pass parameters to the next function.
Here is what I mean:
app.get('/webpage', SomeFunction, NextFunction);
function SomeFunction (req, res, next) {
// Do things
next();
}
function NextFunction (req, res) {
// Do other things
}
Now, if in SomeFunction I were to say next(req, res); it does not seem to work. It never gets to the method. Obviously I cannot directly pass parameters...but my question is why? How does the next function know which parameters to use? Is it because they are named the same or does it automatically pass the 1st and 2nd parameters? If NextFunction used blah, bleet instead of req, res would it still work?
This is an intentional aspect of the design of Connect (the node.js middleware that's responsible for this behaviour). The next function your middleware receives is not the next middleware in the stack; it's a function that Connect generates which asks the next middleware to handle it (as well as doing some extra stuff to handle special cases, like when there isn't a "next middleware").
If your middleware should return a response, just do so. If it shouldn't, it's implied that some later middleware should return a response. If you need to pass along data to that later part of the process, you should attach it to an appropriate part of the request object req.
For example, the bundled bodyParser middleware is responsible for populating req.rawBody and req.body based on the contents of the request body. The bundled basicAuth middleware populates req.remoteUser based on the HTTP authentication.
This is the pattern you should try to emulate: a stack of middleware, each of which does a basic incremental thing to process the request. If what you're trying to model doesn't fit into this paradigm, then you should probably just have a single function to handle the request, from which you can call all of your own application logic however you like.

Categories

Resources