refactor: improve security, split routes, add API resources and FormRequests

- Fix timing attack vulnerability in AuthController
- Split web.php (316 lines) into 7 focused route files
- Add 8 API Resources for consistent response formatting
- Add 8 FormRequest classes for centralized validation
- Use Resources instead of manual array mapping in controllers
This commit is contained in:
root
2026-05-20 23:03:16 +02:00
parent 2f30a058a4
commit 75b78c17fa
26 changed files with 745 additions and 404 deletions
+45 -100
View File
@@ -1,26 +1,30 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Actions\Fortify\CreateNewUser;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\ArticleCommentRequest;
use App\Http\Requests\Api\LoginRequest;
use App\Http\Requests\Api\RegisterRequest;
use App\Http\Requests\Api\UpdatePasswordRequest;
use App\Http\Requests\Api\UpdateUserRequest;
use App\Http\Resources\Api\ArticleResource;
use App\Http\Resources\Api\PhotoResource;
use App\Http\Resources\Api\UserApiResource;
use App\Models\Articles\WebsiteArticle;
use App\Models\Miscellaneous\CameraWeb;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
public function login(Request $request): JsonResponse
public function login(LoginRequest $request): JsonResponse
{
$request->validate([
'username' => ['required', 'string'],
'password' => ['required'],
]);
$username = $request->input('username');
$user = User::where('username', $username)
->orWhere('mail', $username)
@@ -29,16 +33,16 @@ class AuthController extends Controller
$credentialsValid = $user && Hash::check($request->input('password'), $user->password);
if (! $credentialsValid) {
Hash::make($request->input('password'));
Hash::check($request->input('password'), Hash::make('timing-attack-prevention'));
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
'username' => ['The provided credentials are incorrect.'],
]);
}
if ($user->is_banned) {
throw ValidationException::withMessages([
'email' => ['Your account has been banned.'],
'username' => ['Your account has been banned.'],
]);
}
@@ -47,66 +51,31 @@ class AuthController extends Controller
$token = $user->createToken('auth-token')->plainTextToken;
return response()->json([
'user' => [
'id' => (string) $user->id,
'email' => $user->mail,
'username' => $user->username,
'look' => $user->look,
],
'user' => new UserApiResource($user),
'token' => $token,
]);
}
public function register(Request $request): JsonResponse
public function register(RegisterRequest $request): JsonResponse
{
$createNewUser = new CreateNewUser;
try {
$validated = $request->validate([
'username' => ['required', 'string', 'max:50'],
'password' => ['required', 'string', 'min:6'],
'mail' => ['required', 'email', 'max:255'],
'look' => ['nullable', 'string'],
'motto' => ['nullable', 'string', 'max:100'],
]);
$user = $createNewUser->create($request->validated());
$user = $createNewUser->create($validated);
$token = $user->createToken('auth-token')->plainTextToken;
return response()->json([
'user' => [
'id' => (string) $user->id,
'email' => $user->mail,
'username' => $user->username,
'look' => $user->look,
],
'token' => $token,
], 201);
} catch (ValidationException $e) {
return response()->json([
'errors' => $e->errors(),
], 422);
}
}
public function user(Request $request): JsonResponse
{
$user = $request->user();
$token = $user->createToken('auth-token')->plainTextToken;
return response()->json([
'id' => (string) $user->id,
'email' => $user->mail,
'username' => $user->username,
'look' => $user->look,
'motto' => $user->motto ?? '',
'credits' => $user->credits ?? 0,
'pixels' => $user->pixels ?? 0,
'diamonds' => $user->diamonds ?? 0,
]);
'user' => new UserApiResource($user),
'token' => $token,
], 201);
}
public function logout(Request $request): JsonResponse
public function user(\Illuminate\Http\Request $request): JsonResponse
{
return response()->json(new UserApiResource($request->user()));
}
public function logout(\Illuminate\Http\Request $request): JsonResponse
{
$request->user()->currentAccessToken()->delete();
@@ -118,69 +87,45 @@ class AuthController extends Controller
$articles = WebsiteArticle::with(['user:id,username,look'])
->latest('id')
->take(4)
->get()
->map(fn ($article) => [
'id' => $article->id,
'title' => $article->title,
'slug' => $article->slug,
'image' => $article->image,
'excerpt' => $article->excerpt,
'user' => $article->user,
'created_at' => $article->created_at,
]);
->get();
$photos = CameraWeb::query()
->latest('id')
->take(4)
->where('visible', true)
->with('user:id,username,look')
->get()
->map(fn ($photo) => [
'id' => $photo->id,
'image' => $photo->image,
'user' => $photo->user,
]);
->get();
return response()->json([
'articles' => $articles,
'photos' => $photos,
'articles' => ArticleResource::collection($articles),
'photos' => PhotoResource::collection($photos),
]);
}
public function updateUser(Request $request): JsonResponse
public function updateUser(UpdateUserRequest $request): JsonResponse
{
$user = $request->user();
$user->update($request->validated());
$validated = $request->validate([
'motto' => ['nullable', 'string', 'max:100'],
'look' => ['nullable', 'string'],
]);
$user->update($validated);
return response()->json([
'id' => (string) $user->id,
'email' => $user->mail,
'username' => $user->username,
'look' => $user->look,
'motto' => $user->motto,
'credits' => $user->credits,
'pixels' => $user->pixels,
'diamonds' => $user->diamonds,
]);
return response()->json(new UserApiResource($user));
}
public function articleComment(Request $request, string $slug): JsonResponse
public function updatePassword(UpdatePasswordRequest $request): JsonResponse
{
$request->user()->update([
'password' => Hash::make($request->input('password')),
]);
return response()->json(['message' => 'Password updated successfully']);
}
public function articleComment(ArticleCommentRequest $request, string $slug): JsonResponse
{
$article = WebsiteArticle::where('slug', $slug)->firstOrFail();
$validated = $request->validate([
'comment' => ['required', 'string', 'max:1000'],
]);
$comment = $article->comments()->create([
'user_id' => $request->user()->id,
'comment' => strip_tags((string) $validated['comment']),
'comment' => strip_tags((string) $request->input('comment')),
]);
return response()->json([