How to add auth endpoints to existing Sails V1 project? - sails.js

I have an existing Sails V1 project that was generated as an empty app (it uses a React front-end). I'd now like to add in the auth endpoints that would have been created if the app had been generated as a web app. Is that possible?

Yes, it is possible.
You need to hook up the policies and related actions. Your best bet, I would say, is to generate a new project, with the front-end included, and see how that is set up. It utilizes the policy-middleware to call the policy-actions.
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,
};
Here you see that the policy.js in the /config folder, calls is-logged-in for all controllers by default. You also see that there is some exceptions added below.
is-logged-in is the file /api/policies/is-logged-in.js:
module.exports = async function (req, res, proceed) {
// If `req.me` is set, then we know that this request originated
// from a logged-in user.
if (req.me) {
return proceed();
}
// Otherwise, this request did not come from a logged-in user.
return res.unauthorized();
};
This is the part that does the check for the logged-in status of the user. You can see that it uses the req.me part, which is set up in the api/hooks/custom/index.js. Here it loads the user from the database and makes the logged in users data available on the req object.
If you don't have, or want to use, this hook, you can exchange req.me with req.session.userId, assuming that you set the userId on the session-object on your login-handler. Example from Sails-code:
....
var userRecord = await User.findOne({
emailAddress: inputs.emailAddress.toLowerCase(),
});
// check user exist
....
// check password
....
//check remember-me
....
// Modify the active session instance.
this.req.session.userId = userRecord.id;
// Send success response (this is where the session actually gets persisted)
return exits.success();
....
I hope this gets you on the right path, at least in terms of where to dig deeper.

Related

How to define before tag for only one scenario/feature from more ones

I need to run one before tag for specific scenario. How to do that?
So I have one scenario where I need to set up some request like user no.1. I have my one token getting from system based on provided username and password. This is all set up in before tag in hook file - code no.1
Then I need to run another before tag in hook file to get token as user no.2 different user and finish this task. code no.2
I am not able to differentiate it or my system still gets new token like to be always user no.1
code no.1 - it runs each time
Before(async () => {
const page = World.page;
const token = retrieveToken();
console.log('Using environment url: ' + EnvironmentVariables.URL);
await page.goto(EnvironmentVariables.URL + `/?access_token=${token}&expires_in=3600&token_type=bearer`);
await page.waitForSelector(SelectorUtils.nameSelector(NavigationComponent.searchClientInputName));
await waitForSpinnerToEnd();
});
code no.2 - here I am trying to use annotation with name of scenario but with no effect. I get token but when I look at console I see login details from code no.1
Before({tags: '#SubmitSRForReviewFinish'}, async () => {
const page = World.page;
const token = retrieveTokenLogin();
console.log('Using environment url: ' + EnvironmentVariables.URL);
await page.goto(EnvironmentVariables.URL + `/?access_token=${token}&expires_in=3600&token_type=bearer`);
await page.waitForSelector(SelectorUtils.nameSelector(NavigationComponent.searchClientInputName));
await waitForSpinnerToEnd();
});
I need to be able to run before tag only for specific scenario. How to do that? How to log with another scenarion as another user?
SOLVED:by myself
SOLUTION:this solution with annotation works but you need to remember that if you have some beforeAll tag where you set up window and other properties once at the beginning you need to change it to only before tag cause your browser window is still open and no matter what it still gets old values.

Meteor - Password recovery / Email confirmation dynamic url

Basically, I'm using the accounts-base package on meteor and on meteor startup, I set up what template the server should use for the password recovery mail, email confirmation mail, etc.
For example, in my server/startup.js on meteor startup I do many things like :
Accounts.urls.verifyEmail = function (token) {
return Meteor.absoluteUrl(`verify-email/${token}`);
};
Accounts.emailTemplates.verifyEmail.html = function (user, url) {
return EmailService.render.email_verification(user, url);
};
The problem is that my app is hosted on multiple host names like company1.domain.com, company2.domain.com, company3.domain.com and if a client wants to reset his password from company1.domain.com, the recovery url provided should be company1.domain.com/recovery.
If another client tried to connect on company2.domain.com, then the recovery url should be company2.domain.com.
From my understanding, this is not really achievable because the method used by the Accounts Package is "Meteor.absoluteUrl()", which returns the server ROOT_URL variable (a single one for the server).
On the client-side, I do many things based on the window.location.href but I cannot seem, when trying to reset a password or when trying to confirm an email address, to send this url to the server.
I'm trying to find a way to dynamically generate the url depending on the host where the client is making the request from, but since the url is generated server-side, I cannot find an elegent way to do so. I'm thinking I could probably call a meteor server method right before trying to reset a password or create an account and dynamically set the ROOT_URL variable there, but that seems unsafe and risky because two people could easily try to reset in the same timeframe and potentially screw things up, or people could abuse it.
Isn't there any way to tell the server, from the client side, that the URL I want generated for the current email has to be the client current's location ? I would love to be able to override some functions from the account-base meteor package and achieve something like :
Accounts.urls.verifyEmail = function (token, clientHost) {
return `${clientHost}/verify-email/${token}`;
};
Accounts.emailTemplates.verifyEmail.html = function (user, url) {
return EmailService.render.email_verification(user, url);
};
But I'm not sure if that's possible, I don't have any real experience when it comes to overriding "behind the scene" functionalities from base packages, I like everything about what is happening EXCEPT that the url generated is always the same.
Okay so I managed to find a way to achieve what I was looking for, it's a bit hack-ish, but hey..
Basically, useraccounts has a feature where any hidden input in the register at-form will be added to the user profile. So I add an hidden field to store the user current location.
AccountsTemplates.addField({
_id: 'signup_location',
type: 'hidden',
});
When the template is rendered, I fill in this hidden input with jQuery.
Template.Register.onRendered(() => {
this.$('#at-field-signup_location').val(window.location.href);
});
And then, when I'm actually sending the emailVerification email, I can look up this value if it is available.
Accounts.urls.verifyEmail = function (token) {
return Meteor.absoluteUrl(`verify-email/${token}`);
};
Accounts.emailTemplates.verifyEmail.html = function (user, url) {
const signupLocation = user.profile.signup_location;
if (signupLocation) {
let newUrl = url.substring(url.indexOf('verify-email'));
newUrl = `${signupLocation}/${newUrl}`;
return EmailService.render.email_verification(user, newUrl);
}
return EmailService.render.email_verification(user, url);
};
So this fixes it for the signUp flow, I may use the a similar concept for resetPassword and resendVerificationUrl since the signupLocation is now in the user profile.
You should probably keep an array of every subdomains in your settings and keep the id of the corresponding one in the user profile, so if your domain changes in the future then the reference will still valid and consistent.

make meteor restful api/web-service

I have created a new url/route in my app where I need to write a web-service. I need to write a service that deletes user according to the parameters passed in the service. For now, anyone should be able to call that service (will make it secure at later stage). App is built on meteor.
My url is : loaclhost:3000/deleteUser. Now one should be able to call my delete user function defined on this page and pass json structure data as an argument to it. If the data is valid, then the user should be deleted.
Using simple:rest package
Meteor.publish("delUser", function (a, b) {
UserDetails.remove({}); //delete user according to data received
}, {
url: "/testing/delUser", //url where third party will call the function
getArgsFromRequest: function (request) {
// Let's say we want this function to accept a form-encoded request
// with fields named `a` and `b`.
console.log('received : ' + JSON.stringify(request.body) );
var content = request.body;
// Since form enconding doesn't distinguish numbers and strings, we need
// to parse it manually
return [content.a, content.b];
}
})
How to access the function, delUser from a thrid party? I also need to add authentication at a later stage.
Personnally, I use this :
simple:rest
simple:json-routes
simple:rest-accounts-password
I find it easier to implement.
even iron:router comes with server side routes where you can build your own functions and api calls.
http://iron-meteor.github.io/iron-router/#restful-routes
Sample (Server side code) :
Router.map(function () {
this.route("api", {path: "/api/:paramsYouNeed",
where: "server",
action: function(){
this.response.writeHead(200, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
});
if (this.request.method == 'POST') {
var response;
//do whatever you want to do
this.response.end(response);
}
}
});
The other user can call this by making a http.post request to the above url (http:www.a****a.com/api/params)
The easiest way to do this is use the restivus package.
https://atmospherejs.com/nimble/restivus
Restivus makes building REST APIs in Meteor 0.9.0+ easier than ever
before! The package is inspired by RestStop2 and Collection API, and
is built on top of Simple JSON Routes to provide:
A simple interface for creating REST APIs
Easy setup of CRUD endpoints for Mongo Collections
User authentication via the API
Optional login and logout endpoints
Access to this.user in authenticated endpoints
Custom authentication if needed
Role permissions for limiting access to specific endpoints
Works alongside the alanning:roles package - Meteor's accepted role permission package

Hook for REST validation with sailsjs

I need to create a validation layer for my REST services, I'm using sailsjs.
Someone know how can I do that?
I tried to create a hook but I cant access routes definitions and the hook is called before start policies :'(
The way is something like picture below.
It is perfectly fine to use policies to pre-process requests before they are passed to the controllers. Policies are not just for authentication and acl. They are so versatile you can use them for anything.
E.g.
policies/beforeUpdateTicket.js
module.exports = function(req, res, ok) {
TicketService.checkTicket(req.params.id, null, true).then(function(ticket) {
# You can even modify req.body
req.body.checked = true;
return ok();
}).fail(function(err) {
# Don't go to the controller, respond with error
return res.send(JSON.stringify({
message: 'some_error'
}), 409);
});
};

Sails.js socket.io general security

Using sails sockets.
From a browser I can get all 'tasks' where the user id is 1.
I can now listen for the 'task' event and look for 'created' in the verb to get new tasks and add them to the list.
However I get events from ALL created tasks regardless of user. This seems to be me as a major security issue. All someone needs to do jump into the console and set up a listener to get notified whenever any user creates a new task.
I had a look around for sometime but can't find any posts on the topic.
Being new to this kind of thing - can someone be kind enough to help out?
What is the best practise for dealing with lists over socket.io in Sails?
Cheers!
This should be what you're looking for; it prevents you from subscribing to all existing tasks on the client side. It only subscribes if you're logged in and only to tasks that belong to you. Keep in mind that this is just a first-step in implementing a secure REST API for your app - but it should get you started.
In your client-side app you'd write:
socket.on('connect', function socketConnected()
{
// This subscribes the user to all tasks that belong to him and only him.
socket.get('/task/subscribe', null, function response(data, jwres)
{
// We don’t really care about the response.
});
// This 1.) creates a new task and 2.) subscribes the user to that task.
// If the 'rest' blueprint is on, POSTing to /task gets redirected to TaskController.create automatically by sails.
// If it's not on, you write "socket.get('/task/create' ..."
socket.post('/task', {name : 'MyNewTask'}, function response(data, jwres)
{
// Add the created task inside of 'data' to your client side app.
});
})
Then in TaskController.js you would write:
subscribe : function(req, res)
{
// Is the user logged in?
if(!req.session.user)
{
return res.badRequest();
}
// Find all tasks that belong to the currently logged in user.
Task.find({userID : req.session.user.id}, findUsersCB(err, tasks)
{
// Subscribe the user to all of his tasks.
Task.subscribe(req.socket, tasks);
// Send user's tasks back to the client.
res.json(tasks);
});
}
create : function(req, res)
{
//Is the user logged in?
if(!req.session.user)
{
return res.badRequest();
}
var taskToBeCreated =
{
name : req.param('name'),
userID : req.session.user.id;
};
// Attempt to create the given task.
Task.create(taskToBeCreated, function createTaskCB(err, createdTask)
{
// Subscribe the user to the newly-created task.
Task.subscribe(req.socket, createdTask);
// Send user's task back to the client.
res.json(task);
});
}
I haven't shown an example for the 'update' and 'destroy' actions but the idea is the same for both.