Files
Verde-Web/app/Http/Controllers/Api/V1/Auth/ResetPasswordController.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

57 lines
1.8 KiB
PHP

<?php
namespace App\Http\Controllers\Api\V1\Auth;
use App\Http\Controllers\Api\V1\ApiController;
use App\Http\Requests\Auth\ResetPasswordRequest;
use App\Models\OtpCode;
use App\Models\User;
use App\Services\Otp\OtpService;
use App\Services\Otp\OtpVerifyResult;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Hash;
use Laravel\Sanctum\PersonalAccessToken;
class ResetPasswordController extends ApiController
{
public function __invoke(ResetPasswordRequest $request, OtpService $otp): JsonResponse
{
$data = $request->validated();
$user = User::where('phone', $data['phone'])->first();
if (! $user) {
return $this->fail('Invalid code', ['code' => ['not_found']], 422);
}
$result = $otp->verify($data['phone'], OtpCode::PURPOSE_PASSWORD_RESET, $data['code']);
if (! $result->isOk()) {
return $this->fail(
$this->messageFor($result->status),
['code' => [$result->status]],
422,
);
}
$user->forceFill(['password' => Hash::make($data['password'])])->save();
PersonalAccessToken::query()
->where('tokenable_type', $user->getMorphClass())
->where('tokenable_id', $user->id)
->delete();
return $this->ok(null, 'Password reset successful. Please log in.');
}
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 => 'Reset failed',
};
}
}