Feature/AUTH

This commit is contained in:
Nyavokevin 2025-10-03 14:58:46 +03:00
parent 48ac6365a5
commit 6fa1e4797b
6 changed files with 324 additions and 0 deletions

View File

@ -0,0 +1,128 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
use App\Http\Controllers\Api\BaseController as BaseController;
use Illuminate\Validation\ValidationException;
class AuthController extends BaseController
{
public function register(Request $request): JsonResponse
{
try {
$data = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email'],
'password' => ['required', Password::min(8)],
]);
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => $data['password'], // hashed via User model cast
]);
$token = $user->createToken('api')->plainTextToken;
return $this->sendResponse([
'user' => $user,
'token' => $token,
], 'User registered successfully.');
} catch (ValidationException $e) {
return $this->sendError('Validation Error.', $e->errors(), 422);
} catch (\Exception $e) {
return $this->sendError('Registration failed.', ['error' => $e->getMessage()], 500);
}
}
public function login(Request $request): JsonResponse
{
try {
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required', 'string'],
]);
/** @var User|null $user */
$user = User::where('email', $credentials['email'])->first();
if (! $user || ! Hash::check($credentials['password'], $user->password)) {
return $this->sendError('Invalid credentials.', ['email' => ['The provided credentials are incorrect.']], 401);
}
$token = $user->createToken('api')->plainTextToken;
return $this->sendResponse([
'user' => $user,
'token' => $token,
], 'Login successful.');
} catch (ValidationException $e) {
return $this->sendError('Validation Error.', $e->errors(), 422);
} catch (\Exception $e) {
return $this->sendError('Login failed.', ['error' => $e->getMessage()], 500);
}
}
public function me(Request $request): JsonResponse
{
try {
$user = $request->user();
if (!$user) {
return $this->sendError('Unauthenticated.', [], 401);
}
return $this->sendResponse($user, 'User retrieved successfully.');
} catch (\Exception $e) {
return $this->sendError('Failed to retrieve user.', ['error' => $e->getMessage()], 500);
}
}
public function logout(Request $request): JsonResponse
{
try {
$user = $request->user();
if (!$user) {
return $this->sendError('Unauthenticated.', [], 401);
}
if ($user->currentAccessToken()) {
$user->currentAccessToken()->delete();
}
return $this->sendResponse([], 'Logged out successfully.');
} catch (\Exception $e) {
return $this->sendError('Logout failed.', ['error' => $e->getMessage()], 500);
}
}
public function logoutAll(Request $request): JsonResponse
{
try {
$user = $request->user();
if (!$user) {
return $this->sendError('Unauthenticated.', [], 401);
}
$user->tokens()->delete();
return $this->sendResponse([], 'Logged out from all devices successfully.');
} catch (\Exception $e) {
return $this->sendError('Logout failed.', ['error' => $e->getMessage()], 500);
}
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers\API;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller as Controller;
class BaseController extends Controller
{
/**
* success response method.
*
* @return \Illuminate\Http\Response
*/
public function sendResponse($result, $message)
{
$response = [
'success' => true,
'data' => $result,
'message' => $message,
];
return response()->json($response, 200);
}
/**
* return error response.
*
* @return \Illuminate\Http\Response
*/
public function sendError($error, $errorMessages = [], $code = 404)
{
$response = [
'success' => false,
'message' => $error,
];
if(!empty($errorMessages)){
$response['data'] = $errorMessages;
}
return response()->json($response, $code);
}
}

View File

@ -0,0 +1,84 @@
<?php
use Laravel\Sanctum\Sanctum;
return [
/*
|--------------------------------------------------------------------------
| Stateful Domains
|--------------------------------------------------------------------------
|
| Requests from the following domains / hosts will receive stateful API
| authentication cookies. Typically, these should include your local
| and production domains which access your API via a frontend SPA.
|
*/
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
Sanctum::currentApplicationUrlWithPort(),
// Sanctum::currentRequestHost(),
))),
/*
|--------------------------------------------------------------------------
| Sanctum Guards
|--------------------------------------------------------------------------
|
| This array contains the authentication guards that will be checked when
| Sanctum is trying to authenticate a request. If none of these guards
| are able to authenticate the request, Sanctum will use the bearer
| token that's present on an incoming request for authentication.
|
*/
'guard' => ['web'],
/*
|--------------------------------------------------------------------------
| Expiration Minutes
|--------------------------------------------------------------------------
|
| This value controls the number of minutes until an issued token will be
| considered expired. This will override any values set in the token's
| "expires_at" attribute, but first-party sessions are not affected.
|
*/
'expiration' => null,
/*
|--------------------------------------------------------------------------
| Token Prefix
|--------------------------------------------------------------------------
|
| Sanctum can prefix new tokens in order to take advantage of numerous
| security scanning initiatives maintained by open source platforms
| that notify developers if they commit tokens into repositories.
|
| See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
|
*/
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
/*
|--------------------------------------------------------------------------
| Sanctum Middleware
|--------------------------------------------------------------------------
|
| When authenticating your first-party SPA with Sanctum you may need to
| customize some of the middleware Sanctum uses while processing the
| request. You may change the middleware listed below as required.
|
*/
'middleware' => [
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
],
];

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if (! Schema::hasTable('personal_access_tokens')) {
Schema::create('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->text('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable()->index();
$table->timestamps();
});
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('personal_access_tokens');
}
};

View File

@ -0,0 +1,25 @@
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\AuthController;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| These routes are loaded with the "api" middleware group and are prefixed
| with /api automatically via bootstrap/app.php.
|
*/
Route::prefix('auth')->group(function () {
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
Route::middleware('auth:sanctum')->group(function () {
Route::get('/me', [AuthController::class, 'me']);
Route::post('/logout', [AuthController::class, 'logout']);
Route::post('/logout-all', [AuthController::class, 'logoutAll']);
});
});

View File

@ -0,0 +1,8 @@
export interface User {
id: number;
name: string;
email: string;
email_verified_at?: string | null;
created_at?: string;
updated_at?: string;
}