Skip to content

add support otp lifetime token functionality with expiration handling #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
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
30 changes: 13 additions & 17 deletions .github/workflows/php-cs-fixer.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
name: Check & fix styling

on: [push, pull_request]
on: [push,pull_request]

jobs:
php-cs-fixer:
runs-on: ubuntu-latest
name: PHP-CS-Fixer
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: PHP-CS-Fixer
uses: docker://oskarstark/php-cs-fixer-ga
with:
args: --config=.php-cs-fixer.dist --allow-risky=yes

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}

- name: Run PHP CS Fixer
uses: docker://oskarstark/php-cs-fixer-ga
with:
args: --config=.php-cs-fixer.dist --allow-risky=yes

- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Fix styling
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Fix styling
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 4.3.0 - 2024-06-21
- Add support only confirm token

## 4.2.0 - 2024-04-12
- Add support Laravel 11
- Detracted Laravel 9
Expand Down
116 changes: 74 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,46 +19,60 @@ Laravel | Laravel-OTP
6.0.x to 8.0.x | 1.0.x

## Basic Usage:

```php
<?php

/**
* Send OTP via SMS.
*/
OTP()->send('+989389599530');
// or
OTP('+989389599530');

/**
* Send OTP via channels.
*/
/*
|--------------------------------------------------------------------------
| Send OTP via SMS.
|--------------------------------------------------------------------------
*/
OTP()->send('+98900000000');
// Or
OTP('+98900000000');

/*
|--------------------------------------------------------------------------
| Send OTP via channels.
|--------------------------------------------------------------------------
*/
OTP()->channel(['otp_sms', 'mail', \App\Channels\CustomSMSChannel::class])
->send('+989389599530');
// or
OTP('+989389599530', ['otp_sms', 'mail', \App\Channels\CustomSMSChannel::class]);

/**
* Send OTP for specific user provider
*/
->send('+98900000000');
// Or
OTP('+98900000000', ['otp_sms', 'mail', \App\Channels\CustomSMSChannel::class]);

/*
|--------------------------------------------------------------------------
| Send OTP for specific user provider
|--------------------------------------------------------------------------
*/
OTP()->useProvider('admins')
->send('+989389599530');

/**
* Validate OTP
*/
OTP()->validate('+989389599530', 'token_123');
// or
OTP('+989389599530', 'token_123');
// or
->send('+98900000000');

/*
|--------------------------------------------------------------------------
| Validate OTP
|--------------------------------------------------------------------------
*/
OTP()->validate('+98900000000', 'token_123');
// Or
OTP('+98900000000', 'token_123');

/*
|--------------------------------------------------------------------------
| Validate OTP for specific user provider
|--------------------------------------------------------------------------
*/
OTP()->useProvider('users')
->validate('+989389599530', 'token_123');
// or
OTP()->useProvider('users')
->onlyConfirmToken()
->validate('+989389599530', 'token_123');
->validate('+98900000000', 'token_123');
/*
|--------------------------------------------------------------------------
| You may wish to only confirm the token
|--------------------------------------------------------------------------
*/
OTP()->onlyConfirmToken()
->validate('+98900000000', 'token_123');
```

## Installation

You can install the package via composer:
Expand Down Expand Up @@ -132,7 +146,24 @@ php artisan migrate

> **Note:** When you are using OTP to login user, consider all columns must be nullable except for the `mobile` column. Because, after verifying OTP, a user record will be created if the user does not exist.

## User providers
### Token Life Time
You can specify an OTP `token_lifetime`, ensuring that once an OTP token is sent to the user, no new OTP token will be generated or sent until the current token has expired.

```php
// config/otp.php

<?php

return [
//...

'token_lifetime' => env('OTP_TOKEN_LIFE_TIME', 5),
],

//...
];
```
### User providers

You may wish to use the OTP for variant users. Laravel OTP allows you to define and manage many user providers that you
need. In order to set up, you should open `config/otp.php` file and define your providers:
Expand Down Expand Up @@ -242,15 +273,15 @@ return [

## Practical Example

Here we have prepared a practical example. Suppose you are going to login/register a customer by sending an OTP:
Here we have prepared a practical example. Suppose you are going to login/register a user by sending an OTP:

```php
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Fouladgar\OTP\Exceptions\InvalidOTPTokenException;
use Fouladgar\OTP\Exceptions\OTPException;
use Fouladgar\OTP\OTPBroker as OTPService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
Expand All @@ -269,10 +300,10 @@ class AuthController
$user = $this->OTPService->send($request->get('mobile'));
} catch (Throwable $ex) {
// or prepare and return a view.
return response()->json(['message'=>'An unexpected error occurred.'], 500);
return response()->json(['message' => 'An unexpected error occurred.'], 500);
}

return response()->json(['message'=>'A token has been sent to:'. $user->mobile]);
return response()->json(['message' => 'A token has been sent to:'. $user->mobile]);
}

public function verifyOTPAndLogin(Request $request): JsonResponse
Expand All @@ -283,13 +314,13 @@ class AuthController

// and do login actions...

} catch (InvalidOTPTokenException $exception){
return response()->json(['error'=>$exception->getMessage()],$exception->getCode());
} catch (OTPException $exception){
return response()->json(['error' => $exception->getMessage()],$exception->getCode());
} catch (Throwable $ex) {
return response()->json(['message'=>'An unexpected error occurred.'], 500);
return response()->json(['message' => 'An unexpected error occurred.'], 500);
}

return response()->json(['message'=>'Logged in successfully.']);
return response()->json(['message' => 'Logged in successfully.']);
}
}

Expand All @@ -305,6 +336,7 @@ channel. In order to replace, you should specify channel class here:
```php
//config/otp.php
<?php

return [
// ...

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4.2.0
4.3.0
7 changes: 6 additions & 1 deletion src/Contracts/TokenRepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ public function create(OTPNotifiable $user): string;
/**
* Determine if a token record exists and is valid.
*/
public function exists(OTPNotifiable $user, string $token): bool;
public function exists(string $mobile): bool;

/**
* Determine if the given token matches the provided one.
*/
public function isTokenMatching(OTPNotifiable $user, string $token): bool;

/**
* Delete all existing tokens from the storage.
Expand Down
13 changes: 0 additions & 13 deletions src/Exceptions/InvalidOTPTokenException.php

This file was deleted.

23 changes: 23 additions & 0 deletions src/Exceptions/OTPException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Fouladgar\OTP\Exceptions;

use Exception;

class OTPException extends Exception
{
public static function whenOtpTokenIsInvalid(): static
{
return new static('The token has been expired or invalid.');
}

public static function whenUserNotFoundByMobile(): static
{
return new static('User not found by mobile.');
}

public static function whenOtpAlreadySent(): static
{
return new static('OTP has already been sent for this mobile.');
}
}
13 changes: 0 additions & 13 deletions src/Exceptions/UserNotFoundByMobileException.php

This file was deleted.

19 changes: 12 additions & 7 deletions src/OTPBroker.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
use Fouladgar\OTP\Contracts\NotifiableRepositoryInterface;
use Fouladgar\OTP\Contracts\OTPNotifiable;
use Fouladgar\OTP\Contracts\TokenRepositoryInterface;
use Fouladgar\OTP\Exceptions\InvalidOTPTokenException;
use Fouladgar\OTP\Exceptions\UserNotFoundByMobileException;
use Fouladgar\OTP\Exceptions\OTPException;
use Illuminate\Support\Arr;
use Throwable;

Expand Down Expand Up @@ -38,7 +37,8 @@ public function send(string $mobile, bool $userExists = false): OTPNotifiable
{
$user = $userExists ? $this->findUserByMobile($mobile) : null;

throw_if(! $user && $userExists, UserNotFoundByMobileException::class);
throw_if(! $user && $userExists, OTPException::whenUserNotFoundByMobile());
throw_if($this->tokenExists($mobile), OTPException::whenOtpAlreadySent());

$notifiable = $user ?? $this->makeNotifiable($mobile);

Expand All @@ -53,13 +53,13 @@ public function send(string $mobile, bool $userExists = false): OTPNotifiable
}

/**
* @throws InvalidOTPTokenException|Throwable
* @throws OTPException|Throwable
*/
public function validate(string $mobile, string $token, bool $create = true): OTPNotifiable
{
$notifiable = $this->makeNotifiable($mobile);

throw_unless($this->tokenExists($notifiable, $token), InvalidOTPTokenException::class);
throw_unless($this->verifyToken($notifiable, $token), OTPException::whenOtpTokenIsInvalid());

if(!$this->onlyConfirm){
$notifiable = $this->find($mobile, $create);
Expand Down Expand Up @@ -136,9 +136,14 @@ private function getDefaultChannel(): array
return is_array($channel) ? $channel : Arr::wrap($channel);
}

private function tokenExists(OTPNotifiable $user, string $token): bool
public function verifyToken(OTPNotifiable $user, string $token): bool
{
return $this->tokenRepository->exists($user, $token);
return $this->tokenRepository->isTokenMatching($user, $token);
}

private function tokenExists(string $mobile): bool
{
return $this->tokenRepository->exists($mobile);
}

private function makeNotifiable(string $mobile): OTPNotifiable
Expand Down
11 changes: 8 additions & 3 deletions src/Token/CacheTokenRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ public function deleteExisting(OTPNotifiable $user): bool
return $this->cache->forget($this->getSignatureKey($user->getMobileForOTPNotification()));
}

public function exists(OTPNotifiable $user, string $token): bool
public function exists(string $mobile): bool
{
return $this->cache->has($this->getSignatureKey($mobile));
}

public function isTokenMatching(OTPNotifiable $user, string $token): bool
{
$exist = $this->exists($user->getMobileForOTPNotification());
$signature = $this->getSignatureKey($user->getMobileForOTPNotification());

return $this->cache->has($signature) &&
$this->cache->get($signature)['token'] === $token;
return $exist && $this->cache->get($signature)['token'] === $token;
}

protected function save(string $mobile, string $token): bool
Expand Down
Loading
Loading