53fb7b7760
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
122 lines
4.4 KiB
PHP
122 lines
4.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Concerns\WritesAuthLog;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Requests\Auth\ConfirmEmailRequest;
|
|
use App\Http\Requests\Auth\RegisterRequest;
|
|
use App\Http\Requests\Auth\ResendCodeRequest;
|
|
use App\Models\User;
|
|
use App\Services\Auth\RegistrationException;
|
|
use App\Services\Auth\RegistrationService;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
/**
|
|
* Самозапись клиента (G1/SP1): register → confirm-email → (вход).
|
|
* Подтверждение почты 6-значным кодом; новый тенант создаётся в статусе
|
|
* pending_email_confirm, активируется и получает 300 ₽ при подтверждении.
|
|
*/
|
|
class RegistrationController extends Controller
|
|
{
|
|
use WritesAuthLog;
|
|
|
|
public function register(RegisterRequest $request, RegistrationService $service): JsonResponse
|
|
{
|
|
try {
|
|
$result = $service->register(
|
|
$request->string('email')->toString(),
|
|
$request->string('password')->toString(),
|
|
$request->input('captcha_token'),
|
|
$request->ip(),
|
|
);
|
|
} catch (RegistrationException $e) {
|
|
return $this->registrationError($e);
|
|
}
|
|
|
|
$payload = [
|
|
'status' => $result['status'],
|
|
'email' => $result['user']->email,
|
|
'expires_at' => $result['verification']->expires_at->toIso8601String(),
|
|
];
|
|
if ($result['dev_code'] !== null) {
|
|
$payload['_dev_plain_code'] = $result['dev_code'];
|
|
}
|
|
|
|
return response()->json($payload, 201);
|
|
}
|
|
|
|
public function confirmEmail(ConfirmEmailRequest $request, RegistrationService $service): JsonResponse
|
|
{
|
|
try {
|
|
$user = $service->confirm(
|
|
$request->string('email')->toString(),
|
|
$request->string('code')->toString(),
|
|
);
|
|
} catch (RegistrationException $e) {
|
|
$payload = ['message' => 'Код подтверждения недействителен.', 'reason' => $e->reason];
|
|
if ($e->attemptsRemaining !== null) {
|
|
$payload['attempts_remaining'] = $e->attemptsRemaining;
|
|
}
|
|
|
|
return response()->json($payload, 422);
|
|
}
|
|
|
|
Auth::login($user);
|
|
$request->session()->regenerate();
|
|
$this->logAuthEvent('register_success', $user->id, $user->tenant_id, $user->email, $request->ip(), $request->userAgent(), null);
|
|
|
|
return response()->json([
|
|
'user' => $this->userResource($user),
|
|
'requires_2fa' => false,
|
|
]);
|
|
}
|
|
|
|
public function resendCode(ResendCodeRequest $request, RegistrationService $service): JsonResponse
|
|
{
|
|
$devCode = $service->resend($request->string('email')->toString());
|
|
|
|
$payload = ['message' => 'Если аккаунт ожидает подтверждения, мы отправили новый код на указанный email.'];
|
|
if ($devCode !== null) {
|
|
$payload['_dev_plain_code'] = $devCode;
|
|
}
|
|
|
|
return response()->json($payload);
|
|
}
|
|
|
|
private function registrationError(RegistrationException $e): JsonResponse
|
|
{
|
|
$map = [
|
|
'captcha_failed' => ['captcha_token', 'Проверка «я не робот» не пройдена.'],
|
|
'email_taken' => ['email', 'Аккаунт с таким email уже существует.'],
|
|
];
|
|
[$field, $message] = $map[$e->reason] ?? ['email', 'Не удалось зарегистрировать аккаунт.'];
|
|
|
|
return response()->json([
|
|
'message' => $message,
|
|
'errors' => [$field => [$message]],
|
|
], 422);
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
private function userResource(User $user): array
|
|
{
|
|
return [
|
|
'id' => $user->id,
|
|
'email' => $user->email,
|
|
'first_name' => $user->first_name,
|
|
'last_name' => $user->last_name,
|
|
'phone' => $user->phone,
|
|
'timezone' => $user->timezone,
|
|
'tenant_id' => $user->tenant_id,
|
|
'totp_enabled' => $user->totp_enabled,
|
|
'last_login_at' => $user->last_login_at,
|
|
'notification_preferences' => $user->notification_preferences,
|
|
'sound_enabled' => $user->sound_enabled,
|
|
];
|
|
}
|
|
}
|