Skip to content

Commit

Permalink
LP-5 Reset password using otp and temporary password and reset link i…
Browse files Browse the repository at this point in the history
…s successful
  • Loading branch information
hafijul233 committed Oct 16, 2023
1 parent 836df2d commit cd5f1cb
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 74 deletions.
4 changes: 2 additions & 2 deletions config/auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
| Exclude auth fields
| Example: reset_link, otp, temporary_password
*/
'self_password_reset' => false,
'password_reset_method' => 'temporary_password',
'self_password_reset' => true,
'password_reset_method' => 'otp',
'temporary_password_length' => 8,


Expand Down
4 changes: 4 additions & 0 deletions lang/en/messages.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@
'warning' => 'Sorry, You entered wrong credentials! You already attempt :attempt. times out of :threshold',
'lockup' => 'Sorry, Your Account is has been Locked. Please contact support!',
'reset' => [
'success' => 'Your account password reset successful.',
'temporary_password' => 'We have send you a temporary password. Please log into you account with credentials.',
'reset_link' => 'We have send you a password reset link. Please follow that instruction to proceed.',
'otp' => 'We have send you a verification code. Please verify your account with given code.',
'notification_failed' => 'There is a error while processing your request. Please try again later',
'invalid_token' => 'The reset link token is invalid. Please try again later.',
'expired_token' => 'The password reset token has expired. Please try again later.',
'user_not_found' => 'Unable to find valid user associated with this token',
]
];
72 changes: 39 additions & 33 deletions src/Http/Controllers/PasswordResetController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@
use Fintech\Auth\Facades\Auth;
use Fintech\Auth\Http\Requests\ForgotPasswordRequest;
use Fintech\Auth\Http\Requests\PasswordResetRequest;
use Fintech\Core\Exceptions\UpdateOperationException;
use Fintech\Core\Traits\ApiResponseTrait;
use Illuminate\Http\JsonResponse;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Password;
use Illuminate\Validation\ValidationException;

class PasswordResetController extends Controller
{
use ApiResponseTrait;

/**
* Handle an incoming password reset link request.
*
* @lrd:start
* This api receive `login_id` as unique user then as per configuration
* and send temporary password or reset link or One Time Pin verifcation
* to proceed
* @lrd:end
* @param ForgotPasswordRequest $request
* @return JsonResponse
* @throws \Exception
Expand All @@ -36,7 +38,7 @@ public function store(ForgotPasswordRequest $request): JsonResponse
return $this->failed(__('auth::messages.failed'));
}

$response = Auth::passwordReset()->notify($attemptUser->first());
$response = Auth::passwordReset()->notifyUser($attemptUser->first());

if (!$response['status']) {
throw new \Exception($response['message']);
Expand All @@ -51,39 +53,43 @@ public function store(ForgotPasswordRequest $request): JsonResponse
}

/**
* Handle an incoming new password request.
*
* @throws ValidationException
* @LRDparam password_confirmation string|required|min:8
* @lrd:start
* This api receive `token`, `password` & `password_confirmation` to reset
* user with given password. If otp or token didn't match throws exception
* to proceed
* @lrd:end
* @param PasswordResetRequest $request
* @return JsonResponse
*/
public function update(PasswordResetRequest $request): JsonResponse
{
$request->validate([
'token' => ['required'],
'login_id' => ['required', 'email'],
'password' => ['required', 'confirmed', 'min:8'],
]);

// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'),
function ($user) use ($request) {
$user->forceFill([
'password' => Hash::make($request->password),
'remember_token' => Str::random(60),
])->save();

event(new PasswordReset($user));
$passwordField = config('fintech.auth.password_field', 'password');

$token = $request->input('token');

$password = $request->input($passwordField);

try {

$activeToken = Auth::passwordReset()->verifyToken($token);

$targetedUser = Auth::user()->list([config('fintech.auth.auth_field', 'login_id'), $activeToken->email]);

if ($targetedUser->isEmpty()) {
throw new \ErrorException(__('auth::messages.reset.user_not_found'));
}
);

if ($status != Password::PASSWORD_RESET) {
throw ValidationException::withMessages([
'email' => [__($status)],
]);
}
$targetedUser = $targetedUser->first();

if (!Auth::user()->update($targetedUser->getKey(), [$passwordField => $password])) {
throw (new UpdateOperationException)->setModel(config('fintech.auth.user_model'), $targetedUser->getKey());
}

return $this->updated(__('auth::messages.reset.success'));

return response()->json(['status' => __($status)]);
} catch (\Exception $exception) {
return $this->failed($exception->getMessage());
}
}
}
2 changes: 1 addition & 1 deletion src/Http/Requests/LoginRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function rules(): array
config('fintech.auth.auth_field', 'login_id')
=> config('fintech.auth.auth_field_rules', ['required', 'string', 'min:6', 'max:255']),

config('fintech.auth.password_field', 'login_id')
config('fintech.auth.password_field', 'password')
=> config('fintech.auth.password_field_rules', ['required', 'string', 'min:8'])
];
}
Expand Down
6 changes: 4 additions & 2 deletions src/Http/Requests/PasswordResetRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class PasswordResetRequest extends FormRequest
*/
public function authorize(): bool
{
return false;
return true;
}

/**
Expand All @@ -22,7 +22,9 @@ public function authorize(): bool
public function rules(): array
{
return [
//
'token' => ['required', 'string'],
config('fintech.auth.password_field', 'password')
=> [...config('fintech.auth.password_field_rules', ['required', 'string', 'min:8']), 'confirmed']
];
}
}
11 changes: 9 additions & 2 deletions src/Interfaces/OneTimePinRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ public function create(string $authField, string $token);
/**
* Determine if a token record exists and is valid.
*
* @param string $authField
* @param string $token
* @return bool
*/
public function exists(string $authField, string $token);
public function exists(string $token);

/**
* Delete expired tokens.
Expand All @@ -29,4 +28,12 @@ public function exists(string $authField, string $token);
* @return void
*/
public function deleteExpired(string $authField);

/**
* Delete existing old tokens.
*
* @param string $authField
* @return void
*/
public function delete(string $authField);
}
22 changes: 10 additions & 12 deletions src/Repositories/Eloquent/OneTimePinRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
*/
class OneTimePinRepository implements InterfacesOneTimePinRepository
{
/**
* @var \Illuminate\Contracts\Foundation\Application|Model|\Illuminate\Foundation\Application
*/
private $model;

public function __construct()
Expand Down Expand Up @@ -60,11 +57,10 @@ public function create(string $authField, string $token)
*
* @param string $authField
* @param string $token
* @return bool
*/
public function exists(string $authField, string $token)
public function exists(string $token)
{
return $this->model->where(['email' => $authField, 'token' => $token])->first();
return $this->model->where(['token' => $token])->first();
}

/**
Expand All @@ -74,9 +70,9 @@ public function exists(string $authField, string $token)
* @param string $token
* @return void
*/
private function recentlyCreatedToken(string $authField, string $token)
private function recentlyCreatedToken(string $token)
{
$token = $this->exists($authField, $token);
$token = $this->exists($token);

$expireInSeconds = config('auth.passwords.users.expire', 5) * 60;

Expand All @@ -87,12 +83,14 @@ private function recentlyCreatedToken(string $authField, string $token)
/**
* Delete a token record.
*
* @param CanResetPasswordContract $user
* @param string $authField
* @return void
*/
public function delete(CanResetPasswordContract $user)
public function delete(string $authField)
{

$this->model->where('email', $authField)->get()->each(function ($entry) {
$entry->delete();
});
}

/**
Expand All @@ -103,7 +101,7 @@ public function delete(CanResetPasswordContract $user)
*/
public function deleteExpired(string $authField)
{
$this->model->where('email', $authField)->get()->each(function ($entry) {
$this->model->where('created_at', '<', now()->subMinutes(config('auth.passwords.users.expire')))->get()->each(function ($entry) {
$entry->delete();
});
}
Expand Down
81 changes: 59 additions & 22 deletions src/Services/PasswordResetService.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class PasswordResetService
/**
* @var string|null
*/
private string $resetMethod;
private ?string $resetMethod;

/**
* OneTimePinService constructor.
Expand All @@ -45,7 +45,7 @@ public function __construct(OneTimePinRepository $oneTimePinRepository)
* @param $user
* @return array
*/
public function notify($user)
public function notifyUser($user)
{
try {

Expand Down Expand Up @@ -79,6 +79,10 @@ public function notify($user)
}
}

/**
* @param $user
* @return array
*/
private function viaTemporaryPassword($user): array
{
$password = Str::random(config('fintech.auth.temporary_password_length', 8));
Expand All @@ -104,6 +108,11 @@ private function viaTemporaryPassword($user): array
];
}

/**
* @param $user
* @return array
* @throws \Exception
*/
private function viaResetLink($user)
{
$authField = $user->authField();
Expand Down Expand Up @@ -136,11 +145,16 @@ private function viaResetLink($user)
];
}

/**
* @param $user
* @return array
* @throws \Exception
*/
private function viaOneTimePin($user)
{
$authField = $user->authField();

$this->oneTimePinRepository->deleteExpired($authField);
$this->oneTimePinRepository->delete($authField);

$min = (int)str_pad('1', config('fintech.auth.otp_length', 4), "0");
$max = (int)str_pad('9', config('fintech.auth.otp_length', 4), "9");
Expand Down Expand Up @@ -169,23 +183,46 @@ private function viaOneTimePin($user)
];
}

// public function find($id, $onlyTrashed = false)
// {
// return $this->oneTimePinRepository->find($id, $onlyTrashed);
// }
//
// public function update($id, array $inputs = [])
// {
// return $this->oneTimePinRepository->update($id, $inputs);
// }
//
// public function destroy($id)
// {
// return $this->oneTimePinRepository->delete($id);
// }
//
// public function restore($id)
// {
// return $this->oneTimePinRepository->restore($id);
// }
/**
* @param string $token
* @return bool
* @throws \Exception
*/
public function verifyToken(string $token)
{

if ($this->resetMethod == 'reset_link') {
try {
$token = json_decode(base64_decode($token), true, 512, JSON_THROW_ON_ERROR);

if (!is_array($token)) {
throw new \JsonException(__('auth::messages.reset.invalid_token'));
}

$token = array_key_first($token);

} catch (\Exception $exception) {
throw new \Exception($exception->getMessage());
}
}


if ($passwordResetToken = $this->oneTimePinRepository->exists($token)) {

$expireInSeconds = config('auth.passwords.users.expire', 5) * 60;

$duration = now()->diffInSeconds($passwordResetToken->created_at);

if ($expireInSeconds < $duration) {

$this->oneTimePinRepository->delete($passwordResetToken->email);

throw new \Exception(__('auth::messages.reset.expired_token'));
}

return $passwordResetToken;
}

throw new \Exception(__('auth::messages.reset.invalid_token'));
}
}

0 comments on commit cd5f1cb

Please sign in to comment.