refactor(auth): убрать одношаговый register (заменён start/verify)

Удалён старый 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) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-21 19:13:11 +03:00
parent 3711a92958
commit fdff36c553
6 changed files with 3 additions and 129 deletions
@@ -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, шлёт код письмом. Аккаунт НЕ создаётся.
@@ -8,7 +8,7 @@ trait HasPasswordRules
{
/**
* Правила валидации поля password.
* Используется в LoginRequest и RegisterRequest для DRY.
* Используется в LoginRequest и RegisterStartRequest для DRY.
*
* @return array<int, string>
*/
@@ -1,43 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Auth;
use App\Http\Requests\Auth\Concerns\HasPasswordRules;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
/**
* Валидация POST /api/auth/register.
*
* По ТЗ §1.5/§4.1: 2 обязательных click-wrap'а оферта + согласие на ПДн
* (3-й «маркетинговый» из handoff НЕ требуется расхождение #2 реестра v1.13).
*/
class RegisterRequest extends FormRequest
{
use HasPasswordRules;
/** @return array<string, mixed> */
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<string, string> */
public function messages(): array
{
return array_merge($this->passwordMessages(), [
'email.required' => 'Укажите email.',
'email.email' => 'Email указан некорректно.',
'email.unique' => 'Аккаунт с таким email уже существует.',
'accept_offer.accepted' => 'Необходимо принять оферту.',
'accept_pdn.accepted' => 'Необходимо согласие на обработку персональных данных.',
]);
}
}
+2 -2
View File
@@ -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
-
-1
View File
@@ -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');
@@ -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);
});