From fdff36c553c42f8cc5a552d2f6154cc8cff31b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Thu, 21 May 2026 19:13:11 +0300 Subject: [PATCH] =?UTF-8?q?refactor(auth):=20=D1=83=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BE=D0=B4=D0=BD=D0=BE=D1=88=D0=B0=D0=B3=D0=BE?= =?UTF-8?q?=D0=B2=D1=8B=D0=B9=20register=20(=D0=B7=D0=B0=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=91=D0=BD=20start/verify)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Удалён старый POST /api/auth/register (метод + роут + RegisterRequest + 3 теста); регистрация теперь только через register/start → register/verify. SPA-роут страницы /register сохранён. Larastan baseline перегенерирован (counts AuthControllerTest 9→8 / 14→10 после удаления тестов). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Http/Controllers/Api/AuthController.php | 31 ----------- .../Auth/Concerns/HasPasswordRules.php | 2 +- .../Http/Requests/Auth/RegisterRequest.php | 43 ---------------- app/phpstan-baseline.neon | 4 +- app/routes/web.php | 1 - app/tests/Feature/Auth/AuthControllerTest.php | 51 ------------------- 6 files changed, 3 insertions(+), 129 deletions(-) delete mode 100644 app/app/Http/Requests/Auth/RegisterRequest.php diff --git a/app/app/Http/Controllers/Api/AuthController.php b/app/app/Http/Controllers/Api/AuthController.php index 6000e840..f80ea240 100644 --- a/app/app/Http/Controllers/Api/AuthController.php +++ b/app/app/Http/Controllers/Api/AuthController.php @@ -6,7 +6,6 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Http\Requests\Auth\LoginRequest; -use App\Http\Requests\Auth\RegisterRequest; use App\Http\Requests\Auth\RegisterStartRequest; use App\Http\Requests\Auth\RegisterVerifyRequest; use App\Mail\RegisterEmailVerificationCode; @@ -147,36 +146,6 @@ class AuthController extends Controller ]); } - public function register(RegisterRequest $request): JsonResponse - { - // На MVP — attach нового user'а к первому tenant'у (для UI-разводки). - // Production: wizard с tenant_name + ИНН + создание Tenant + первый user owner-роли. - $tenant = Tenant::first(); - if (! $tenant) { - return response()->json([ - 'message' => 'Tenants не настроены. Обратитесь к администратору.', - ], 503); - } - - $user = User::create([ - 'tenant_id' => $tenant->id, - 'email' => $request->string('email')->toString(), - 'password_hash' => Hash::make($request->string('password')->toString()), - 'first_name' => 'Новый', - 'last_name' => 'Пользователь', - 'is_active' => true, - 'totp_enabled' => false, - ]); - - Auth::login($user); - $request->session()->regenerate(); - - return response()->json([ - 'user' => $this->userResource($user), - 'requires_2fa' => false, - ], 201); - } - /** * Шаг 1 регистрации: валидирует форму, генерирует 6-значный код, * кладёт pending-данные в session, шлёт код письмом. Аккаунт НЕ создаётся. diff --git a/app/app/Http/Requests/Auth/Concerns/HasPasswordRules.php b/app/app/Http/Requests/Auth/Concerns/HasPasswordRules.php index b335a251..a8a6a20d 100644 --- a/app/app/Http/Requests/Auth/Concerns/HasPasswordRules.php +++ b/app/app/Http/Requests/Auth/Concerns/HasPasswordRules.php @@ -8,7 +8,7 @@ trait HasPasswordRules { /** * Правила валидации поля password. - * Используется в LoginRequest и RegisterRequest для DRY. + * Используется в LoginRequest и RegisterStartRequest для DRY. * * @return array */ diff --git a/app/app/Http/Requests/Auth/RegisterRequest.php b/app/app/Http/Requests/Auth/RegisterRequest.php deleted file mode 100644 index 75f85b04..00000000 --- a/app/app/Http/Requests/Auth/RegisterRequest.php +++ /dev/null @@ -1,43 +0,0 @@ - */ - public function rules(): array - { - return [ - 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users', 'email')], - 'password' => $this->passwordRules(), - 'accept_offer' => ['required', 'accepted'], - 'accept_pdn' => ['required', 'accepted'], - ]; - } - - /** @return array */ - public function messages(): array - { - return array_merge($this->passwordMessages(), [ - 'email.required' => 'Укажите email.', - 'email.email' => 'Email указан некорректно.', - 'email.unique' => 'Аккаунт с таким email уже существует.', - 'accept_offer.accepted' => 'Необходимо принять оферту.', - 'accept_pdn.accepted' => 'Необходимо согласие на обработку персональных данных.', - ]); - } -} diff --git a/app/phpstan-baseline.neon b/app/phpstan-baseline.neon index a152c9e9..f91551fa 100644 --- a/app/phpstan-baseline.neon +++ b/app/phpstan-baseline.neon @@ -627,7 +627,7 @@ parameters: - message: '#^Access to an undefined property Pest\\PendingCalls\\TestCall\:\:\$tenant\.$#' identifier: property.notFound - count: 9 + count: 8 path: tests/Feature/Auth/AuthControllerTest.php - @@ -639,7 +639,7 @@ parameters: - message: '#^Call to an undefined method Pest\\PendingCalls\\TestCall\:\:postJson\(\)\.$#' identifier: method.notFound - count: 14 + count: 10 path: tests/Feature/Auth/AuthControllerTest.php - diff --git a/app/routes/web.php b/app/routes/web.php index 86cc74b0..325619d9 100644 --- a/app/routes/web.php +++ b/app/routes/web.php @@ -19,7 +19,6 @@ use Illuminate\Support\Facades\Route; // добавляется только к web-группе. См. laravel.com/docs/sanctum#spa-authentication. Route::prefix('/api/auth')->group(function () { Route::post('/login', 'App\Http\Controllers\Api\AuthController@login'); - Route::post('/register', 'App\Http\Controllers\Api\AuthController@register'); Route::post('/register/start', 'App\Http\Controllers\Api\AuthController@registerStart'); Route::post('/register/verify', 'App\Http\Controllers\Api\AuthController@registerVerify'); Route::post('/register/resend', 'App\Http\Controllers\Api\AuthController@registerResend'); diff --git a/app/tests/Feature/Auth/AuthControllerTest.php b/app/tests/Feature/Auth/AuthControllerTest.php index 80e5c513..c2ac7e2f 100644 --- a/app/tests/Feature/Auth/AuthControllerTest.php +++ b/app/tests/Feature/Auth/AuthControllerTest.php @@ -119,57 +119,6 @@ test('POST /api/auth/login обновляет last_login_at у user', function ( expect($user->fresh()->last_login_at)->not->toBeNull(); }); -test('POST /api/auth/register создаёт user + возвращает 201', function () { - $response = $this->postJson('/api/auth/register', [ - 'email' => 'new-signup@example.ru', - 'password' => 'fresh-pass-123', - 'accept_offer' => true, - 'accept_pdn' => true, - ]); - - $response->assertStatus(201); - $response->assertJsonPath('user.email', 'new-signup@example.ru'); - $response->assertJsonPath('requires_2fa', false); - - $user = User::where('email', 'new-signup@example.ru')->first(); - expect($user)->not->toBeNull(); - expect(Hash::check('fresh-pass-123', $user->password_hash))->toBeTrue(); -}); - -test('POST /api/auth/register отвергает существующий email (unique)', function () { - User::factory()->create([ - 'tenant_id' => $this->tenant->id, - 'email' => 'duplicate@example.ru', - ]); - - $response = $this->postJson('/api/auth/register', [ - 'email' => 'duplicate@example.ru', - 'password' => 'any-password-123', - 'accept_offer' => true, - 'accept_pdn' => true, - ]); - - $response->assertStatus(422); - $response->assertJsonValidationErrors(['email']); -}); - -test('POST /api/auth/register требует accept_offer=true И accept_pdn=true (ТЗ §1.5/§4.1)', function () { - $base = [ - 'email' => 'no-consent@example.ru', - 'password' => 'fresh-pass-123', - ]; - - // Без оферты. - $this->postJson('/api/auth/register', array_merge($base, ['accept_pdn' => true])) - ->assertStatus(422) - ->assertJsonValidationErrors(['accept_offer']); - - // Без ПДн. - $this->postJson('/api/auth/register', array_merge($base, ['accept_offer' => true])) - ->assertStatus(422) - ->assertJsonValidationErrors(['accept_pdn']); -}); - test('GET /api/auth/me возвращает 401 без авторизации', function () { $this->getJson('/api/auth/me')->assertStatus(401); });