feat: login via OTP

This commit is contained in:
Phan An 2024-04-19 15:25:08 +02:00
parent ddd5f3f38e
commit 7f1429377e
4 changed files with 52 additions and 5 deletions

View file

@ -2,13 +2,15 @@
namespace App\Http\Controllers\API;
use App\Exceptions\InvalidCredentialsException;
use App\Http\Controllers\Controller;
use App\Http\Requests\API\UserLoginRequest;
use App\Services\AuthenticationService;
use App\Values\CompositeToken;
use Closure;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Throwable;
class AuthController extends Controller
{
@ -19,6 +21,26 @@ class AuthController extends Controller
}
public function login(UserLoginRequest $request)
{
$compositeToken = $this->throttleLoginRequest(
fn () => $this->auth->login($request->email, $request->password),
$request
);
return response()->json($compositeToken->toArray());
}
public function loginUsingOneTimeToken(Request $request)
{
$compositeToken = $this->throttleLoginRequest(
fn () => $this->auth->loginViaOneTimeToken($request->input('token')),
$request
);
return response()->json($compositeToken->toArray());
}
private function throttleLoginRequest(Closure $callback, Request $request): CompositeToken
{
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
@ -26,8 +48,8 @@ class AuthController extends Controller
}
try {
return response()->json($this->auth->login($request->email, $request->password)->toArray());
} catch (InvalidCredentialsException) {
return $callback();
} catch (Throwable) {
$this->incrementLoginAttempts($request);
abort(Response::HTTP_UNAUTHORIZED, 'Invalid credentials');
}

View file

@ -74,9 +74,17 @@ class AuthenticationService
public function generateOneTimeToken(User $user): string
{
$token = bin2hex(random_bytes(16));
Cache::set("one-time-token.$user->id", $token, 60 * 10);
$token = bin2hex(random_bytes(12));
Cache::set("one-time-token.$token", encrypt($user->id), 60 * 10);
return $token;
}
public function loginViaOneTimeToken(string $token): CompositeToken
{
/** @var User $user */
$user = $this->userRepository->getOne(decrypt(Cache::get("one-time-token.$token")));
return $this->logUserIn($user);
}
}

View file

@ -65,6 +65,8 @@ Route::prefix('api')->middleware('api')->group(static function (): void {
Route::get('ping', static fn () => null);
Route::post('me', [AuthController::class, 'login'])->name('auth.login');
Route::post('me/otp', [AuthController::class, 'loginUsingOneTimeToken']);
Route::delete('me', [AuthController::class, 'logout']);
Route::post('forgot-password', ForgotPasswordController::class);

View file

@ -2,6 +2,7 @@
namespace Tests\Feature;
use App\Services\AuthenticationService;
use Illuminate\Support\Facades\Hash;
use Tests\TestCase;
@ -33,6 +34,20 @@ class AuthTest extends TestCase
->assertUnauthorized();
}
public function testLoginViaOneTimeToken(): void
{
$user = create_user();
$authService = app(AuthenticationService::class);
$token = $authService->generateOneTimeToken($user);
$this->post('api/me/otp', ['token' => $token])
->assertOk()
->assertJsonStructure([
'token',
'audio-token',
]);
}
public function testLogOut(): void
{
$user = create_user([