How to call the password reset function in Strapi? - strapi

By default, Strapi has a welcome email template and password reset template. For the custom function we are writing, we want to create users without a password (or random password the user doesn't know). Then, when our function is finished, we want to send the user a welcome email (overwriting/disabling the default), with a password reset link. Therefore, we need to call the function/service to reset the password for the user, and receive the URL to send this in the welcome email.
However, currently I cannot find any information regarding the function/service to reset the user password. The only method I now see is to call http://localhost/auth/reset-password with the username or email, but would like to use a service such as strapi.services.user.resetPassword(userID) to get the URL back.
Does this function exists and/or is this possible?
Using Strapi 3.1.2

I have move my original answer here since it was more relevant to the question.
To reset a user password, we have to provide an identifier which is in this case the username. The possible steps are:
Query the user based on the identifier
Generate new hash password based on provided randomly generated value
Update the user password with the newly generated hash-password.
The implementation at a controller can be like this:
module.exports = {
resetPassword: async ctx => {
....
// Provide identifier and newPassword
const params = ctx.request.body;
const identifier = params.identifier
const newPassword = params.newPassword
// Get User based on identifier
const user = await strapi.query('user', 'users permissions').findOne({username: identifier});
// Generate new hash password
const password = await strapi.plugins['users-permissions'].services.user.hashPassword({password: newPassword});
// Update user password
await strapi
.query('user', 'users-permissions')
.update({ id: user.id }, { resetPasswordToken: null, password });
...
}
}
Don't forget to implement isOwner policy, or if the old password can be provided, we can validate the process using isValidPassword
// Validate given old password against user query result password
const isValidPassword = await strapi.plugins['users-permissions'].services.user.validatePassword(old.password, user.password);

Related

Call cloud code without logged in user Parse Server JS SDK

Is it possible to call a cloud function that returns objects without having a current user? The iOS and Android SDKs support anonymous users but I'm asking specifically for JavaScript.
I'd like to allow it so that anyone who visits my web app can read objects without having to sign in. I'm using Back4App.
Yes. You can call a cloud code function no matter the user is logged in or not. Inside the cloud function you can check the user property of the request object to check if the user is either logged in or not. In the case that your user is not logged in and you want to query a class which requires user permission, you can use the useMasterKey option.
Parse.Cloud.define('myFunction', async req => {
const { user, isMaster } = req;
if (isMater) {
// the cloud code function was called using master key
} else if (user) {
// the cloud code function was called by an authenticated user
} else {
// the cloud code function was called without passing master key nor session token - not authenticated user
}
const obj = new Parse.Object('MyClass');
await obj.save(null, { useMasterKey: true }); // No matter if the user is authenticated or not, it bypasses all required permissions - you need to know what you are doing since anyone can fire this function
const query = new Parse.Query('MyClass');
return query.find({ useMasterKey: true }) // No matter if the user is authenticated or not, it bypasses all required permissions - you need to know what you are doing since anyone can fire this function
});

Migrating User to Cognito on Sign In

I am trying to migrate users to Cognito when they sign in the first time. For this I wrote a lambda function that does call an API to check if the users exist in db or not ? if the user exists, it will be created in cognito but I am not sure how do I tell the application that user is created and it should allow the user to login .
Here is the code in c#:
public async Task<Stream> FunctionHandlerAsync(Stream stream, ILambdaContext context)
{
RootObject rootObj = DeserializeStream(stream);
User user = new User(rootObj.userName, rootObj.request.password);
ApiResponse apiResponse = await MobileAuthenticateAsync(user.UserName, user.Password);
// Considering apiResponse returns "user authenticated", we create the user in //cognito. This is working.
// How do I send response back to Application so it knows that user is // //created and authenticated and should be allowed to login.
//Before returning stream, I am setting following 2 status.
rootObj.response.finalUserStatus = "CONFIRMED"; // is this correct ?
rootObj.response.messageAction = "SUPPRESS";
return SerializeToStream(rootObj);;
}
You're pretty close.
You can see the full documentation on the Migrate User Lambda Trigger page, however in short you need your response to look like:
{
response: {
userAttributes: {
email: 'user#example.com',
email_verified: true,
custom:myAttribute: 123,
},
finalUserStatus: 'CONFIRMED',
messageAction: 'SUPPRESS',
forceAliasCreation: false,
}
}
Where:
userAttribute: this is a dictionary/map of the user's attributes keys in cognito (note that any custom attributes need to be prefixed with custom:), to the values from the system you're migrating from. You do not need to provide all of these, although if you're using an email alias you may want to set email_verified: true to prevent the user having to re-verify their e-mail address.
finalUserStatus: if you set this to CONFIRMED then the user will not have to re-confirm their email address/phone number, which is probably a sensible default. If you are concerned that the password is given as plain-text to cognito this first-time, you can instead use RESET_REQUIRED to force them to change their password on first sign-in.
messageAction: should probably be SUPPRESS unless you want to send them a welcome email on migration.
forceAliasCreation: is important only if you're using email aliases, as it stops users who manage to sign-up into cognito being replaced on migration.
If you respond with this (keeping the rest of the original rootObj is convenient but not required then the user will migrated with attributes as specified.
If you throw (or fail to respond with the correct event shape) then the migration lambda fails and the user is told that they couldn't migrated. For example, because they do not exist in your old user database, or they haven't provided the right credentials.

How to send original users password to his mail (before bcryption)

In my system users can't register. Admin adding all users in the admin panel and telling them your password is "xxx". Right now i need to send mail to users. Which contains users email and users password. System is working great. But there is one exception. In the mail, passwords is bcrypted. How can i solve? I don't any clue. I am using observers. In the model:
public static function boot()
{
parent::boot();
self::created(function () {
$customer = Customer::latest()->get()->first();
Mail::send('emails.user_login_informations', ['customer' => $customer], function($message) use($customer) {
$message->to($customer->email, $customer->name, $customer->password)
->subject('Login Information');
});
});
}
ps: this is working. In my mail:
Your email: xxx#example.com
Your Password: $2y$10$/GW5XNH9KGU.Nz05PZHFJuKb2ldhwYhS8oMX9e7HJIuFNJ
But this looks like:
Your email: xxx#example.com
Your Password: 123
You can create a temporary password field and delete it upon user activation. I needed this for a real world example. For instance:
Event::listen('rainlab.user.activate', function($user) {
$user->temp_password = null;
$user->save();
});
User::saving(function ($user) {
$password = post('User.password');
if ($password && ! $user->attributes['is_activated']) {
$user->temp_password = $password;
}
});
As mentioned above though, this includes a big security risk.
You hash user passwords to increase the security. The Hashing functionality is a one way hashing, so it can't be reversed.
A better way would be to create a password reset token und send it to the user. So the user can set a new password with the email address / token combination. To increase this method you could let the token expire after 30 minutes or so.

Retrieve password stored in db

I created user profile for my application. In this user profile, user can update their information just like email address, password, position, company name .. etc. If user want to change one of their information, user need to provide current password. And I will verify current password user provided is correct or not. Error is I can't verify the current password user provided. Below is the part of my user controller codes.
if($request['current_pwd'])
{
if($request['new_pwd'])
{
if($user->password == bcrypt($request['current_pwd']))
{
$user->password = bcrypt($request['new_pwd']);
}else{
return redirect()->back()->with(['message' => 'Wrong current password !']);
}
}elseif($user->password == bcrypt($request['current_pwd']))
{
$user->save();
}else{
return redirect()->back()->with(['message' => 'Wrong Current Password ! Check Again!']);
}
}else{
return redirect()->back()->with(['message' => 'Please enter your current password !']);
}
if the user want to change their current password, user must enter current password, new password, . If current password is equal the password stored in db, the new password will be update. If not, return with messages.
try this
first include Hash namespace in your controller,
use Illuminate\Support\Facades\Hash;
and use the following condition to make sure the current password is entered is valid
if($request->has('current_password')){
$current_password = $request->input('current_password');
if(!Hash::check($current_password,Auth::user()->password)){
return redirect()->back()->withInput()->withErrors([
'current_password' => 'You current password doesnot match with the logined user\'s !'
]);
}
}
You should match the encrypted "current password" entry with the password the user already has. You can get the user's password by using the function Auth::user()
So your condition would be:
if(bcrypt($request['current_pwd']) == Auth::user()) {
// Do password change
} else {
// Invalid password error
}
There is another function: getAuthPassword() which you can use by specifying the user ID, like so:
$user = User::find('1');
$user->getAuthPassword();
This will get the password for user ID 1
Due to salting! this kind of $user->password == bcrypt($request['current_pwd'] checking will not work!
Try making hash of same password you will get different hash!
So Check like this in laravel 5.2
$crypt = new Hashing\BcryptHasher();
$match = $crypt->check($user->password, $request['current_pwd']);

How to invalidate OAuth token when password is changed?

We use ASP.NET Identity in a Web Api project with SimpleAuthorizationServerProvider, we use OAuth-tokens to authorize each request coming from the client. (Tokens have and expire timespan, we don't use refresh tokens.)
When users change their password, I would like to invalidate the tokens they may have, possibly on other devices. Is there any way to explicitly do that? I experimented and saw that the existing tokens work without any problem after a password change, which should be prevented.
I thought about putting the password hash, or part of the hash in the OAuth token as a claim, and validating that in the OnAuthorization method of our derived AuthorizeAttribute filter.
Would this be a correct way to solve the problem?
I've based my approach on Taiseer's suggestion. The gist of the solution is the following. Every time a user changes their password (and when registers), a new GUID is generated and saved in the database in the User table. I call this GUID the password stamp, and store it in a property called LatestPasswordStamp.
This stamp has to be sent down to the client as part of the token as a claim. This can be achieved with the following code in the GrantResourceOwnerCredentials method of the OAuthAuthorizationServerProvider-implementation.
identity.AddClaim( new Claim( "PasswordTokenClaim", user.LatestPasswordStamp.ToString() ) );
This stamp is going to be sent from the client to the server in every request, and it is verified that the stamp has not been changed in the database. If it was, it means that the user changed their password, possibly from another device. The verification is done in our custom authorization filter like this.
public class AuthorizeAndCheckStampAttribute : AuthorizeAttribute
{
public override void OnAuthorization( HttpActionContext actionContext )
{
var claimsIdentity = actionContext.RequestContext.Principal.Identity as ClaimsIdentity;
if( claimsIdentity == null )
{
this.HandleUnauthorizedRequest( actionContext );
}
// Check if the password has been changed. If it was, this token should be not accepted any more.
// We generate a GUID stamp upon registration and every password change, and put it in every token issued.
var passwordTokenClaim = claimsIdentity.Claims.FirstOrDefault( c => c.Type == "PasswordTokenClaim" );
if( passwordTokenClaim == null )
{
// There was no stamp in the token.
this.HandleUnauthorizedRequest( actionContext );
}
else
{
MyContext ctx = (MyContext)System.Web.Mvc.DependencyResolver.Current.GetService( typeof( MyContext ) );
var userName = claimsIdentity.Claims.First( c => c.Type == ClaimTypes.Name ).Value;
if( ctx.Users.First( u => u.UserName == userName ).LatestPasswordStamp.ToString() != passwordTokenClaim.Value )
{
// The stamp has been changed in the DB.
this.HandleUnauthorizedRequest( actionContext );
}
}
base.OnAuthorization( actionContext );
}
}
This way the client gets an authorization error if it tries to authorize itself with a token which was issued before the password has been changed.
I do not recommend putting the hash of the password as claim, and I believe there is no direct way to invalidate token when password is changed.
But if you are Ok with hitting the DB with each request send from the client app to a protected API end point, then you need to store Token Identifier (Guid maybe) for each token granted to the resource owner requested it. Then you assign the token Identifier as a custom claim for this token, after this you need to check this table with each request by looking for the token identifier and the user name for the resource owner.
Once the password is changed you delete this token identifier record for this resource owner (user) and the next time the token sent from the client it will get rejected because the record for this token identifier and resource owner has been deleted.

Resources