Files
Verde-Web/app/Http/Controllers/Api/V1/Auth/VerifyOtpController.php
admin dc297d4cd7 feat(backend): complete Module 1 auth (register/login/OTP/reset)
Closes Module 1: 9 auth endpoints under /api/v1/auth, OTP via SMS
(Semaphore + log + fake drivers), role middleware, role + admin seeders,
27 feature tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:46:53 +08:00

67 lines
2.2 KiB
PHP

<?php
namespace App\Http\Controllers\Api\V1\Auth;
use App\Http\Controllers\Api\V1\ApiController;
use App\Http\Requests\Auth\VerifyOtpRequest;
use App\Http\Resources\UserResource;
use App\Models\OtpCode;
use App\Models\User;
use App\Services\Otp\OtpService;
use App\Services\Otp\OtpVerifyResult;
use Illuminate\Http\JsonResponse;
class VerifyOtpController extends ApiController
{
public function __invoke(VerifyOtpRequest $request, OtpService $otp): JsonResponse
{
$data = $request->validated();
$result = $otp->verify($data['phone'], $data['purpose'], $data['code']);
if (! $result->isOk()) {
return $this->fail(
$this->messageFor($result->status),
['code' => [$result->status]],
422,
);
}
$user = User::where('phone', $data['phone'])->first();
if ($user && in_array($data['purpose'], [OtpCode::PURPOSE_REGISTER, OtpCode::PURPOSE_PHONE_VERIFY], true)) {
$user->forceFill([
'phone_verified_at' => now(),
'status' => $user->status === User::STATUS_PENDING ? User::STATUS_ACTIVE : $user->status,
])->save();
}
$payload = [
'verified' => true,
'purpose' => $data['purpose'],
];
if ($data['purpose'] === OtpCode::PURPOSE_LOGIN && $user) {
$token = $user->createToken($request->userAgent() ?? 'otp-login');
$payload['user'] = new UserResource($user);
$payload['token'] = $token->plainTextToken;
$payload['token_type'] = 'Bearer';
} elseif ($user) {
$payload['user'] = new UserResource($user);
}
return $this->ok($payload, 'OTP verified');
}
private function messageFor(string $status): string
{
return match ($status) {
OtpVerifyResult::STATUS_INVALID => 'Invalid code',
OtpVerifyResult::STATUS_EXPIRED => 'Code expired',
OtpVerifyResult::STATUS_EXHAUSTED => 'Too many attempts. Request a new code.',
OtpVerifyResult::STATUS_NOT_FOUND => 'No active code for this destination',
default => 'Verification failed',
};
}
}