Sails 1.0 POST route gives 403 error - sails.js

In my routes.js file, I have defined a route like this:
'PUT /api/v1/entrance/login': { action: 'entrance/login' },
'POST /api/v1/entrance/signup': { action: 'entrance/signup' },
'POST /api/v1/entrance/send-password-recovery-email': { action: 'entrance/send-password-recovery-email' },
'POST /api/v1/entrance/update-password-and-login': { action: 'entrance/update-password-and-login' },
'POST /api/v1/deliver-contact-form-message': { action: 'deliver-contact-form-message' },
'POST /api/v1/getEventsForICalUrl': 'IcalController.getEvents',
I have just used the default generated code and added the last route for getEventsForIcalUrl.
I created an IcalController inside the controllers directory and it has an action getEvents which simply renders a json like this:
module.exports = {
/**
* `IcalController.getEvents()`
*/
getEvents: async function (req, res) {
console.log("hit here");
let events = [];
for (var i = 0; i < 20; i++) {
events.push({foo: "bar" + i});
}
return res.json({
events: events
});
}
};
My problem is that whenever i try to access this controller from the client side, it gives 403 forbidden error.
When I change the route from POST to GET, it works as expected(ofc I am using proper GET/POST request from client end for the route).
Not sure what is breaking.
I also checked the logs. Its printing "hit here" when I use the GET.
In my policy file looks like this(as it was generated. I did not change it):
module.exports.policies = {
'*': 'is-logged-in',
// Bypass the `is-logged-in` policy for:
'entrance/*': true,
'account/logout': true,
'view-homepage-or-redirect': true,
'deliver-contact-form-message': true,
};
And my "is-logged-in" policy file is this:
module.exports = async function (req, res, proceed) {
// If `req.me` is set, then we know that this request originated
// from a logged-in user. So we can safely proceed to the next policy--
// or, if this is the last policy, the relevant action.
// > For more about where `req.me` comes from, check out this app's
// > custom hook (`api/hooks/custom/index.js`).
console.log("req.me=" + req.me);
if (req.me) {
return proceed();
}
//--•
// Otherwise, this request did not come from a logged-in user.
return res.unauthorized();
};
I just put that console.log in this file. others are as they were by default generation from sails new.
The logs show that using POST, this one does not get hit either.(I dont see the "req.me=".. in console.logs.) But this one gets hit when Using GET.
It seems that the route is not working for POST requests. I wonder if its an error in sails js itself or I am doing something wrong.

There are at least two ways how to solve this.
Probably you are using csrf. If you do, your config probably includes this:
module.exports.security = { // With Sails <1.01 this probably is in another file
csrf: true
};
And (if you are using sails v1.01), you should make this route:
'GET /csrfToken': { action: 'security/grant-csrf-token' },
So, to get data on your frontend, you just:
function get_some_records(object_with_data) {
$.get("/csrfToken", function (data, jwres) {
if (jwres != 'success') { return false; }
msg = {
some_data: object_with_data,
_csrf: data._csrf
};
$.post("get_some_records", msg, function(data, status){});
});
}
But if you are using some background jobs, Sails wont give you csrf easily(there is some way probably). So , you just create a route like this:
'post /get_some_records': {
action: 'home/get_some_records',
csrf: false
}

You probably use a Rest client to test (like Postman). Simply disable the csrf protection. in the /config/security.js file
csrf: false

Related

check if api endpoint exits before send request - axios

I have axios request in my vue application:
async get_banner(id:number) : Promise<any> {
return global.axios.get(`${process.env.VUE_APP_DOMAIN}/banners/${id}`)
}
it works while banner/${id} response exits, but I have situation when I should disable banner in my admin panel so api endpoint becomes empty. (not exits) so I am getting Request failed with status code 404 in my vue app console.
question is how to prevent error and know if url exits or not? what is best practice to do this?
You can't tell whether an API exists or not without trying it (or relying on another API to get status of the former API)
It's usually just a manner of handling the response properly. Usually this would look something like this...
getTheBanner(id){
this.errorMessage = null; // reset message on request init
get_banner(id)
.then(r => {
// handle success
this.results = r;
})
.catch(e => {
// handle error
if (e.status === 404){
// set error message
this.errorMessage = "Invalid banner Id";
}
})
}
then in your template you could have something like this
<div v-if="errorMessage" class="alert danger">{errorMessage}</div>
Explaination:
Yes, you're absolutely right. This is the default behavior of strapi. Whenever the response is empty it throws a 404 error. This is basically because the findOne method in service returns null to the controller and when the controller sends this to the boom module it returns a 404 Not Found error to the front end.
Solution:
Just override the find one method in the controller to return an empty object {} when the response is null.
Implementation
// Path - yourproject/api/banner/controllers/banner.js
const { sanitizeEntity } = require('strapi-utils');
module.exports = {
/**
* Retrieve a record.
*
* #return {Object}
*/
async findOne(ctx) {
const { id } = ctx.params;
const entity = await strapi.services.restaurant.findOne({ id });
// in case no entity is found, just return emtpy object here.
if(!entity) return {};
return sanitizeEntity(entity, { model: strapi.models.restaurant });
},
};
Side Note:
There's no need to make any changes to the browser side axios implementation. You should always handle such cases in controller rather the client side.
Reference:
Backend Customizations

Google Action Webhook Inline Editor Returns Before the API call

This is my first Google Action project. I have a simple slot after the invocation. User enters the value on prompt and slot invokes the webhook and make a call to API using the user input. All works fine. However the webhook returns to users even before the API call finish processing and returns the value (line 1 conv.add). I do see in the logs that everything from API is logged fine after the webhook returns to user. Below is the code I am using. I am using inline editor. What am I missing? Thanks for help in advance.
const { conversation } = require('#assistant/conversation');
const functions = require('firebase-functions');
var https = require('https');
const fetch = require('node-fetch');
const app = conversation({debug: true});
app.handle('SearchData', conv => {
const body = JSON.stringify({
val: "this is my body"
});
// prepare the header
var postheaders = {
'Content-Type' : 'application/json',
'Auth' : 'MyAuthCreds'
};
fetch('https://host.domain.com/data', {
method: 'post',
body: body,
headers: postheaders,
})
.then(res => res.json())
.then(d => {
console.log(d);
var profile = d;//JSON.parse(d);
console.log(d.entries);
console.log("Length: "+ d.entries.length);
if(d.entries.length > 0)
{
console.log("Data found");
conv.add("Data found"); //line 1
}
else
{
console.log("no data found");
conv.add("no data found"); //line 1
}
})
.catch(function (err) {
// POST failed...
console.log(err);
});
});
exports.ActionsOnGoogleFulfillment = functions.https.onRequest(app);
Your issue is that your handler is making API calls which are asynchronous, but the Assistant Conversation library doesn't know that you're doing so. So as soon as the handler finishes, it tries to send back a response, but your asynchronous responses (the stuff in the then() blocks) haven't executed yet.
To address this, you need to return a Promise object so the library knows to wait till the Promise is fulfilled before it returns.
Fortunately, in your case, this should be pretty straightforward. fetch and all the .then() blocks return a Promise. So all you need to do is add a return statement in front of the call to fetch. So something like this:
return fetch('https://host.domain.com/data', {

Expose return value

I have this piece of code that I am working on. To provide context, I am using an event source to stream an server sent event. Once I receive the data/response I want to pass that into my template(handlebars) view. The code below is a GET request in which I am trying to display the data returned from SSEvents.addEventListener.
method: 'GET',
path: '/students',
config: {
handler: (request, reply) => {
SSEvents.addEventListener('score', function(e) {
const data = JSON.parse(e.data);
}, false);
reply.view('students', {result: data});
},
description: "Endpoint lists all users that have received at least one test score.",
tags: ['api']
}
}
The issue with this code is the constant "data" is not available outside of the scope of the event listener. I need to find a way to expose the constant so that I can use it in reply.view('students', {result: data});
NOTE: I have tried adding "reply.view('students', {result: data});" within the event listener and it throws the following error: reply interface called twice.
Any help would be appreciated.
--Thanks!
It should be working like this. How long it takes your event source to produce an answer? Did you call the reply method twice? This is only one call.
handler: (request, reply) => {
SSEvents.addEventListener('score', function(e) {
const data = JSON.parse(e.data);
return reply.view('students', {
result: data
});
}, false);
},
description: "Endpoint lists all users that have received at least one test score.",
tags: ['api']
}

DotNetNuke Service API Authorization throwing 401 Unauthorized code

I am having a bit of difficulty figuring out why I am getting 401 Unauthorized status from service framework. At the moment I have it setup to allow everyone to do as they please but that because when I try to enable authorisation I get 401 error code.
//[SupportedModules("Boards")]
//[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
[AllowAnonymous]
public class BoardsServiceController : DnnApiController
{ ... }
The strange thing is I have another module which is more than happy to work away with DnnModuleAuthorize
[SupportedModules("Assignments")]
[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
public class AsgnsServiceController : DnnApiController
{ ... }
In both cases I have checked to make sure the user has permissions to view the page on which the module lives.
I have cross referenced both projects and everything seems to be spot on. Yet one is working away just fine and the other one returns 401.
Any suggestions?
Update
For Assignments module I am mostly using jQuery style of ajax request just because I haven't got around to revising the module. So a typical GET request would look something like this:
$.ajax({
type: "GET",
url: sf.getServiceRoot( "Assignments" ) + "AsgnsService/GetAssignments",
data: data,
beforeSend: sf.setModuleHeaders
}).done( function ( items ) {
//removed for brevity
}).fail( function ( xhr, result, status ) {
//removed for brevity
});
As for Boards module the code structure is slightly different due knockout implementation. There is a dedicated ServiceCaller but it all boils down to the same ajax call to the server except that instead of having full blown ajax call defined as above it looks much neater.
var that = this;
that.serviceCaller = new dnn.boards.ServiceCaller($, this.moduleId, 'BoardsService');
var success = function (model) {
if (typeof model !== "undefined" && model != null) {
viewModel = new boardViewModel(model.colLists);
ko.bindingHandlers.sortable.beforeMove = viewModel.verifyAssignments;
ko.bindingHandlers.sortable.afterMove = viewModel.updateLastAction;
// normally, we apply moduleScope as a second parameter
ko.applyBindings(viewModel, settings.moduleScope);
}
//console.log('success', model);
};
var failure = function (response, status) {
console.log('request failure: ' + status);
};
var params = {
BoardId: this.boardId
};
that.serviceCaller.get('GetBoardLists', params, success, failure);
And the ServiceCaller ajax function itself looks like this:
function (httpMethod, method, params, success, failure, synchronous) {
var options = {
url: that.getRoot() + method,
beforeSend: that.services.setModuleHeaders,
type: httpMethod,
async: synchronous == false,
success: function (d) {
if (typeof (success) != 'undefined') {
success(d || {});
}
},
error: function (xhr, textStatus, errorThrown) {
if (typeof (failure) != 'undefined') {
var message = undefined;
if (xhr.getResponseHeader('Content-Type').indexOf('application/json') == 0) {
try {
message = $.parseJSON(xhr.responseText).Message;
} catch (e) {
}
}
failure(xhr, message || errorThrown);
}
}
};
if (httpMethod == 'GET') {
options.data = params;
} else {
options.contentType = 'application/json; charset=utf-8';
options.data = ko.toJSON(params);
options.dataType = 'json';
}
$.ajax(options);
};
This would be the two GET requests from two different modules where one is happy and the other throws a status 401 when I enable the same annotations.
Does this provide any clues?
Update
Now in saying all of the above if one takes a look at the original Boards module code base one will notice [DnnAuthorize] annotation attached to every function.
During module revision I removed all instances of [DnnAuthorize] annotation and replaced it with two of my own on the service class itself.
When I add [DnnAuthorize] as annotation on service class itself things work as expected. So why [SupportedModules("Boards")] and [DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)] combination doesn't !?
I am not sure but working with the WebAPI you have to register the Service Framework anti forgery stuff
ServicesFramework.Instance.RequestAjaxAntiForgerySupport();
This is part of asking the API to work with a specific module.

Backbone.js Model different url for create and update?

lets say I have a Backbone Model and I create an instance of a model like this:
var User = Backbone.Model.extend({ ... });
var John = new User({ name : 'John', age : 33 });
I wonder if it is possible when I use John.save() to target /user/create when I use John.save() on second time (update/PUT) to target /user/update when I use John.fetch() to target /user/get and when I use John.remove() to target /user/remove
I know that I could define John.url each time before I trigger any method but I'm wondering if it could be happen automatically some how without overriding any Backbone method.
I know that I could use one url like /user/handle and handle the request based on request method (GET/POST/PUT/DELETE) but I'm just wondering if there is a way to have different url per action in Backbone.
Thanks!
Methods .fetch(), .save() and .destroy() on Backbone.Model are checking if the model has .sync() defined and if yes it will get called otherwise Backbone.sync() will get called (see the last lines of the linked source code).
So one of the solutions is to implement .sync() method.
Example:
var User = Backbone.Model.extend({
// ...
methodToURL: {
'read': '/user/get',
'create': '/user/create',
'update': '/user/update',
'delete': '/user/remove'
},
sync: function(method, model, options) {
options = options || {};
options.url = model.methodToURL[method.toLowerCase()];
return Backbone.sync.apply(this, arguments);
}
}
To abstract dzejkej's solution one level further, you might wrap the Backbone.sync function to query the model for method-specific URLs.
function setDefaultUrlOptionByMethod(syncFunc)
return function sync (method, model, options) {
options = options || {};
if (!options.url)
options.url = _.result(model, method + 'Url'); // Let Backbone.sync handle model.url fallback value
return syncFunc.call(this, method, model, options);
}
}
Then you could define the model with:
var User = Backbone.Model.extend({
sync: setDefaultUrlOptionByMethod(Backbone.sync),
readUrl: '/user/get',
createUrl: '/user/create',
updateUrl: '/user/update',
deleteUrl: '/user/delete'
});
Are you dealing with a REST implementation that isn't to spec or needs some kind of workaround?
Instead, consider using the emulateHTTP option found here:
http://documentcloud.github.com/backbone/#Sync
Otherwise, you'll probably just need to override the default Backbone.sync method and you'll be good to go if you want to get real crazy with that... but I don't suggest that. It'd be best to just use a true RESTful interface.
No you can't do this by default with backbone. What you could to is to add to the model that will change the model url on every event the model trigger. But then you have always the problem that bckbone will use POST add the first time the model was saved and PUT for every call afterward. So you need to override the save() method or Backbone.sync as well.
After all it seems not a good idea to do this cause it break the REST pattern Backbone is build on.
I got inspired by this solution, where you just create your own ajax call for the methods that are not for fetching the model. Here is a trimmed down version of it:
var Backbone = require("backbone");
var $ = require("jquery");
var _ = require("underscore");
function _request(url, method, data, callback) {
$.ajax({
url: url,
contentType: "application/json",
dataType: "json",
type: method,
data: JSON.stringify( data ),
success: function (response) {
if ( !response.error ) {
if ( callback && _.isFunction(callback.success) ) {
callback.success(response);
}
} else {
if ( callback && _.isFunction(callback.error) ) {
callback.error(response);
}
}
},
error: function(mod, response){
if ( callback && _.isFunction(callback.error) ) {
callback.error(response);
}
}
});
}
var User = Backbone.Model.extend({
initialize: function() {
_.bindAll(this, "login", "logout", "signup");
},
login: function (data, callback) {
_request("api/auth/login", "POST", data, callback);
},
logout: function (callback) {
if (this.isLoggedIn()) {
_request("api/auth/logout", "GET", null, callback);
}
},
signup: function (data, callback) {
_request(url, "POST", data, callback);
},
url: "api/auth/user"
});
module.exports = User;
And then you can use it like this:
var user = new User();
// user signup
user.signup(data, {
success: function (response) {
// signup success
}
});
// user login
user.login(data, {
success: function (response) {
// login success
}
});
// user logout
user.login({
success: function (response) {
// logout success
}
});
// fetch user details
user.fetch({
success: function () {
// logged in, go to home
window.location.hash = "";
},
error: function () {
// logged out, go to signin
window.location.hash = "signin";
}
});