Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion ProcessMaker/Http/Controllers/Auth/ResetPasswordController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
use Illuminate\Validation\Rules\Password;
use Illuminate\Validation\ValidationException;
use ProcessMaker\Http\Controllers\Controller;
use ProcessMaker\Models\User;

Expand Down Expand Up @@ -76,7 +78,9 @@ public function showResetForm(Request $request, $token)
*/
public function reset(Request $request)
{
$user = User::where('email', $request->input('email'))->first();
$user = User::where('email', $request->input('email'))
->where('username', $request->input('username'))
->first();

if ($user && $user->status === 'BLOCKED') {
return $this->sendResetFailedResponse($request, 'passwords.blocked');
Expand All @@ -86,6 +90,56 @@ public function reset(Request $request)
return $this->sendResetFailedResponse($request, 'passwords.inactive');
}

if (!$user) {
return redirect()->back()
->withInput($request->only('email', 'username'))
->withErrors(['email' => __('passwords.account_not_found')]);
}

return $this->performPasswordReset($request);
}

/**
* Get the password reset validation rules.
*/
protected function rules(): array
{
return [
'token' => 'required',
'email' => 'required|email',
'username' => 'required|string',
'password' => ['required', 'confirmed', Password::defaults()],
];
}

/**
* Get the password reset credentials from the request.
* Include username so the broker resolves the same user as email+username (not email alone).
*/
protected function credentials(Request $request): array
{
return $request->only(
'email',
'username',
'password',
'password_confirmation',
'token'
);
}

/**
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendResetFailedResponse(Request $request, $response)
{
if ($request->wantsJson()) {
throw ValidationException::withMessages([
'email' => [trans($response)],
]);
}

return redirect()->back()
->withInput($request->only('email', 'username'))
->withErrors(['email' => trans($response)]);
}
}
1 change: 1 addition & 0 deletions resources/lang/en/passwords.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
'user' => "We can't find a user with that email address.",
'blocked' => 'Your account has been blocked. Please contact your administrator.',
'inactive' => 'Your account is inactive. Please contact your administrator.',
'account_not_found' => 'We could not find an account with the data provided.',

];
4 changes: 4 additions & 0 deletions resources/views/auth/passwords/reset.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
@endif

</div>
<div class="form-group">
<label for="username">{{__('Username')}}</label>
<input id="username" type="text" class="form-control" name="username" value="{{ old('username') }}" autocomplete="username" autocapitalize="none" spellcheck="false">
</div>
<div class="form-group">
<label for="password">{{__('New Password')}}</label>
<input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password">
Expand Down
32 changes: 32 additions & 0 deletions tests/Feature/Auth/PasswordResetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public function testResetPasswordRejectsBlockedUser(): void
$response = $this->from(route('password.request'))->post('/password/reset', [
'token' => 'will-not-be-used',
'email' => $user->email,
'username' => $user->username,
'password' => 'NewPassword123!',
'password_confirmation' => 'NewPassword123!',
]);
Expand All @@ -142,6 +143,7 @@ public function testResetPasswordRejectsInactiveUser(): void
$response = $this->from(route('password.request'))->post('/password/reset', [
'token' => 'will-not-be-used',
'email' => $user->email,
'username' => $user->username,
'password' => 'NewPassword123!',
'password_confirmation' => 'NewPassword123!',
]);
Expand All @@ -151,6 +153,35 @@ public function testResetPasswordRejectsInactiveUser(): void
]);
}

public function testResetPasswordRejectsWrongUsername(): void
{
/** @var User $user */
$user = User::factory()->create([
'email' => 'wrong-username-reset@example.com',
'username' => 'correct_username',
'status' => 'ACTIVE',
]);

/** @var ConcretePasswordBroker $broker */
$broker = Password::broker();
$token = $broker->createToken($user);

$response = $this->from(route('password.reset', ['token' => $token]))->post('/password/reset', [
'token' => $token,
'email' => $user->email,
'username' => 'some_other_username',
'password' => 'NewSecurePass123!',
'password_confirmation' => 'NewSecurePass123!',
]);

$response->assertSessionHasErrors([
'email' => __('passwords.account_not_found'),
]);

$user->refresh();
$this->assertTrue(Hash::check('oneOnlyPassword', $user->password));
}

public function testResetPasswordUpdatesPasswordForActiveUser(): void
{
/** @var User $user */
Expand All @@ -167,6 +198,7 @@ public function testResetPasswordUpdatesPasswordForActiveUser(): void
$response = $this->post('/password/reset', [
'token' => $token,
'email' => $user->email,
'username' => $user->username,
'password' => $plaintextSecret,
'password_confirmation' => $plaintextSecret,
]);
Expand Down
Loading