How to get the current user using jsonwebtoken in Sails.js? - sails.js

I've been working with Sails since couple of weeks ago, I came from Rails and I don't have any experience working with Node.js.
Now I'm trying to make a robust token authentication using jsonwebtoken.
https://github.com/auth0/node-jsonwebtoken
I followed this guide http://thesabbir.com/how-to-use-json-web-token-authentication-with-sails-js/ and everything worked fine.
I'm able to make a sign up, sign in and then use the token correctly for different actions.
Now, there are some actions where I'd like to use the login user,
something like devise current_user helper.
For example, when creating a comment, this comment should belongs to the current user.
Using Sabbir Ahmed guide, in the line 33 from the isAuthorized.js policy the token gets decrypted so I can get the current user id from there.
So, my question is, what should be the best way to get the current user and be able to use it later in some controller?
For example I tried something like:
# isAuthorized.js line 34, after getting decrypted token
User.findOne({id: token.id}).exec(function findOneCB(err, found){
currentUser = found;
});
But, on this way, because this is an async action I can't use this currentUser in a controller.
I want to store the current user in order to be able to use it later in some controller without repeated the same code in each controller, something like a helper or maybe a service.

The trick is where you place the next(). Since you are making an async call, the control should only be transferred to next policy/ controller once the database action is competed.
You should modify the policy to:
User.findOne({id: token.id}).exec(function findOneCB(err, found){
if(err) next(err);
req.currentUser = found;
next();
});
And you should be able to access the user details in controllers that use isAuthorized policy via req.currentUser

If by
For example, when creating a comment, this comment should belongs to the current user.
what you mean is certain attributes like username, and country etc, rather than querying the database after verification, what you can choose to do is to send these additional attributes to jwToken.issue in api/controllers/UsersController.js
eg.
jwToken.issue({
id: user.id,
username: user.name,
country: user.country
})
How that helps is, you can keep api/policies/isAuthorized.js as is, and in all the controllers that you use in the future, you can access the payload values from as
token.username or token.country
Instead of having to query the database again, thereby saving you valuable response time.
Beware however, of the data you choose to send in the token (you could also send {user:user} if you want to) however, as the secret key or hashing is not required to decrypt the payload as you can figure # jwt.io , you might want to exercise restraint.

Related

Using OAuth2 how do I pull the access token into a variable?

I am trying to make a call to an authorization endpoint using OAuth2 with grant type Client Credentials - my call is successful - that is not an issue. However, I, now, want to take the access token that is returned and put it in a variable so I may use it in subsequent calls without having to manually cut-and-paste to my other calls.
When the call returns I see the token I desire to copy in the Access Token field at the bottom of the OAuth2 window (the one shown below that says expires in 42 minutes) AND I see it in the Authorization field on the Timeline tab of the results. I just can't figure out how to get access to it so I may dump it into variable.
The gif on the FAQ goes really fast, and does not provide step by step. Also, I didnt find any answer on YouTube or other websites, so I thought to share step by step for chaining requests on Insomnia.
Create a POST query to obtain your access token. Notice that my access token is returned in the field called "access_token", we will use this in step 3. Your return field may be different.
Create a second GET request for the API that would return the data for you. In my case, I wanted to get all users from a SCIM interface. In the Bearer tab, type in Response => Body Attribute (Insomnia will autofill).
Mouse click on the Request => Body Attribute (the one you just typed in), and select the authentication post in the dropdown "Request" (this is the one you created in step 1), and in the "Filter (JSONPath)" field, type in the $.[attribute name] - where attribute name is the response that returns from authentication call. In my case, it was access_token, see step 1 for yours.
Enjoy!!
Click No Environment > Manage Environments and you will see a base environment in JSON.
Since this is in JSON, create a { "jwt_token": "Response => Body Attribute" }" pair for your token variable. Please note that "Response => Body Attribute" needs to be configured. When you type response, hit space and this option should be available.
Once done choosing "Response => Body Attribute", it will show with some gibberish content and with red background, no worries... just click it to configure. Make sure you have the same setup.
However... you need to change your request to the route where you get the token from the server and another thing is the Filter (JSONPath or XPath) change it depending on your setup.
You should have the token, stored in jwt_token variable and can use the variable on a route that you like.
Example:
If you want to save a token that is returned in a response into an environment variable, you can use request chaining in your environment variable. Take a look at this url for more details on that https://support.insomnia.rest/article/43-chaining-requests...
Here is what you could do (what I did)
Create an environment variable
For the value of the variable, use the Response => Body Attribute and under Filter (JSONPath or XPath), choose the attribute of the token in your response body (if it is "token" then put $.token).
After that just put the token environment variable wherever you need it in the following requests.
I was not able to resolve this question but was able to get around it by defining the fields in the body of the request and bypassing the OAuth2 tab completely.
You can add it as a header, by referencing the outputs of the OAuth2 request:

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.

Logging with Email and Password , Not with Username Yii 2

How I can manage to login with email address,not with the standart (username + password).I enter the website with my Users in my DataBase , but is there a way to change that to be with email address instead of that user name , because when I use Gii , I got a lot of errors , even I try to fix those errors
First, try to locate your SiteController or any other Controller you use for the index route. It should have an action function that corresponds to the login route; it is usually with signature public function actionLogin().
You should see the initialized model (usually, the LoginForm model). The model should have a function for login logic which is checked to determine user authenticity. You should find that this function invokes another login function which requires the User object as first argument/parameter. The function is usually the $this->getUser() function.
Looking into this will point to you a call to the actual data model that fetches user by whatever criteria/property you specify; this can be email or anything else that might not even need be unique but generally, you want to use a unique data property like username and email. This function relies on the User data model. It, by Gii default, calls the function User::findByUsername(search_property)
Yii2 provides a default User model that implements the Identity interface; that's where you want to make the adjustment you need. It should have the required static function findByUsername() or something similar. You would find that Yii2 default searches within static data to find user, you should link that to you (User) data model which I assume you generated using Gii.
My Gii sequence usually looks like such:
List item
Generate the yii2-basic/yii2-advanced using composer
Create Database (I have a user table in there) and set proper db credentials in config/db.php
Rename the default model/User.php to model/OldUser.php
Create Data Models using Gii
Make the newly generated User Model implement IdentityInterface to allow Yii2 freely-given session management by adding implements yii\web\IdentityInterface to the class declaration.
Implement all the required methods of the IdentityInterface. You can check in `model/OldUser.php' for guidance.
Create static functions to findUserByEmail($email) or findUserByUsername($username)
Mine usually look like this
I hope this helps.
I made it just change everywhere where must be email,instead of username,because of Yii default username loggin , thank u for the advices

Testing stateful Mojolicious apps

I want to test hiding and unhiding of an entry. I conduct the following tests in Mojolicious t/basic.t:
my $t = Test::Mojo->new('AdminApi');
$t->get_ok('/publications/hide/1');
$t->get_ok('/read/publications/meta')->content_unlike(qr/Paper with id 1:/i);
$t->get_ok('/read/publications/meta/1')->content_like(qr/Cannot find entry id: 1/i);
$t->get_ok('/publications/unhide/1');
$t->get_ok('/read/publications/meta')->content_like(qr/Paper with id 1: <a href/i);
$t->get_ok('/read/publications/meta/1')->content_unlike(qr/Cannot find entry id: 1/i);
My problem is that the two lines '/publications/hide/1' and '/publications/unhide/1' do not hide and unhide the entry. The state of the entry remains untouched.
If I repeat the steps manually in the browser everything works well. For the obvious reasons I want to have it automated with the tests. How to do this?
EDIT: The calls '/publications/hide/1' and '/publications/unhide/1' change the state of the database - just a simple UPDATE query. The change applies to the whole application - for all users. But one needs to be logged in as a user to hide/unhide. Question: how do I emulate a logged user during the test?
Contents generated by '/read/publications/meta' and '/read/publications/meta/1' can be read without login.
Bitbucket Repo
File with test code: basic.t
As you have already said, you need to be logged in to perform the hide and unhide action.
my $t = Test::Mojo->new('AdminApi');
You are creating a new UserAgent here. The Test::Mojo class inherits from Mojo::UserAgent. It has a cookie_jar and thus keeps a session alive. You need that to perform this action:
$t->get_ok('/publications/hide/1');
But right now you are not logged in. What you need to do is log in the user. Looking at the code in your repository, you actually assert that you are not logged in.
$t->get_ok('/')->status_is(200)->content_like(qr/Please login or register/i);
Before you perform the hide, you need to log in the user. After digging a bit in your code I found the action and the template to do that, so I know what the request needs to look like.
$t->post_ok(
'/do_login' => { Accept => '*/*' },
form => { user => 'admin', pass => 'hunter2' }
);
Now your $t UserAgent should be logged in and you can do the hide. Note that get_ok only checks if there was no transport error. So in fact it would make sense to now check if in fact you are now logged in.
You could do that by introspecting the session in the application, by checking the logfile (you are writing "Login success" there) or by checking the page for some string that says that the user is logged in. In templates/display/start.html.ep there is a text that welcomes the logged-in user, so you can use that.
$t->post_ok(
'/do_login' => { Accept => '*/*' },
form => { user => 'admin', pass => 'hunter2' }
)->text_like(qr/Nice to see you here admin/i);
Because text_like uses the text-nodes, the <em> around the username is not relevant in the test.
Right, now we know you are logged in. Time to switch the thing on and off.
$t->get_ok('/publications/hide/1');
Because there is no obvious error thrown for that as far as I can tell, I don't see how to test the success of that. Status code is one way, but there might be something in the content as well that you could test.
To verify the state of the application, you would now call the publication.
$t->get_ok('/read/publications/meta')->content_unlike(qr/Paper with id 1:/i);
$t->get_ok('/read/publications/meta/1')->content_like(qr/Cannot find entry id: 1/i);
Right. But remember, our $t is still logged in. Maybe the logged-in user is allowed to see hidden stuff as well as unhidden stuff. Maybe they are not.
It's probably safer to make a second UserAgent that's not logged in, and check with that one as well.
# check with an unauthorized user
my $t_not_logged_in = Test::Mojo->new('AdminApi');
$t_not_logged_in
->get_ok('/read/publications/meta')
->content_unlike(qr/Paper with id 1:/i);
$t_not_logged_in
->get_ok('/read/publications/meta/1')
->content_like(qr/Cannot find entry id: 1/i);
Now basically you repeat the same thing by unhiding your content and testing again. Rinse and repeat.
Keep in mind that unless you are using an explicit testing database (which you seem not to do), you cannot be sure that there even is an entry 1. Or what the name of that is. You should use fixtures for the tests. You could, for example, create a fresh instance of the DB using sqlite and work with that.

Do you need to save ParseUser before Cloud Function Calls?

I have an app on unity that uses ParseUser to store the user Level and Experience. When the user reaches a new Level a ParseCloud function call is made. However whenever user is retrieved via:
Parse.Cloud.define("LevelUp", function (request, response){
var user = request.user;
});
All the attributes in user have the values that are currently saved on the database, but not the ones that the ParseUser that made the request have in the Parse client.
Do I need to call ParseUser.CurrentUser.SaveAsync(); before every call to the server to get current user values or is there a way to send the dirty ParseUser values to the ParseCloud function and update them from that function.
Thanks!
Short answer yes.
Long answer: Not necessarily, but you need to wrap the unsaved (or dirty) attributes in an object and send it to ParseCloud, due the fact that you can't send ParseObject in a ParseCloud call.
In my case, I ended up changing the approach: retrieve the necessary
information about the level, and do the modifications on the ParseUser on the client.