Browse Source

feat: add refresh token expiration configuration

- Added a new configuration option 'rt_expiration' to sanctum.php to set the expiration time for refresh token cookies.

refactor: simplify PermissionSeeder constructor

- Refactored the constructor of PermissionSeeder for brevity.
- Updated createPermissionsAndChildren method to accept a parent_id parameter for hierarchical permission creation.

refactor: enhance UserTypePermissionSeeder for better permission handling

- Improved the UserTypePermissionSeeder to use a dedicated method for seeding user type permissions.
- Streamlined permission retrieval and saving logic for different user types.

fix: update authentication failure message

- Changed the authentication failure message to a more user-friendly format.
Denis 9 tháng trước cách đây
mục cha
commit
27702a7e99

+ 1 - 1
app/Commands/RefreshPermissions.php

@@ -57,7 +57,7 @@ public function handle()
             return Command::SUCCESS;
         } catch (\Exception $e) {
             $this->error('An error occurred while refreshing permissions:');
-            $this->error($e->getMessage());
+            $this->error($e);
             return Command::FAILURE;
         }
     }

+ 5 - 3
app/DTO/RefreshTokenDTO.php

@@ -3,16 +3,18 @@
 namespace App\DTO;
 
 use App\Http\Requests\RefreshTokenRequest;
+use Illuminate\Support\Facades\Log;
 
 readonly class RefreshTokenDTO extends BaseDTO
 {
     public function __construct(
         public ?string $refresh_token = null,
-    ) {
-    }
+    ) {}
 
     public static function fromRequest(RefreshTokenRequest $request): self
     {
-        return new self(...$request->validated());
+        return new self(
+            refresh_token: $request->refresh_token
+        );
     }
 }

+ 1 - 0
app/Enums/UserTypeEnum.php

@@ -9,5 +9,6 @@ enum UserTypeEnum: string
     use EnumHelper;
 
     case ADMIN = 'ADMIN';
+    case USER = 'USER';
     case GUEST = 'GUEST';
 }

+ 39 - 11
app/Http/Controllers/AuthController.php

@@ -9,10 +9,8 @@
 use App\Http\Resources\AuthResource;
 use App\Services\AuthService;
 use App\DTO\RefreshTokenDTO;
-use Illuminate\Support\Facades\Log;
 
 class AuthController extends Controller
-
 {
     public function __construct(
         protected AuthService $authService,
@@ -20,27 +18,57 @@ public function __construct(
 
     public function login(AuthRequest $request): JsonResponse
     {
-        $tokens = $this->authService->login(credentials: AuthDTO::fromRequest(request: $request));
+        $result = $this->authService->login(credentials: AuthDTO::fromRequest(request: $request));
 
-        if (!$tokens) {
-            return $this->errorResponse(message: __(key: 'auth.failed'), code: 401);
+        if (!$result) {
+            return $this->errorResponse(message: __('auth.failed'), code: 401);
         }
 
-        return $this->successResponse(payload: new AuthResource(resource: $tokens), message: __(key: 'auth.logged_in'));
+        return $this->successResponse(payload: new AuthResource($result['payload']), message: __('auth.logged_in'))
+            ->withCookie(
+                cookie(
+                    'refresh_token',
+                    $result['refreshToken'],
+                    config('sanctum.rt_expiration') * 60,
+                    '/',
+                    config('session.domain'),
+                    config('session.secure'),
+                    true,
+                    false,
+                    'Lax'
+                )
+            );
     }
 
     public function logout(): JsonResponse
     {
         $this->authService->logout();
-        return $this->successResponse(message: __(key: 'auth.logout'));
+
+        return $this->successResponse(message: __('auth.logout'))
+            ->withoutCookie('refresh_token');
     }
 
     public function refresh(RefreshTokenRequest $request): JsonResponse
     {
-        $tokens = $this->authService->refresh(refreshToken: RefreshTokenDTO::fromRequest(request: $request));
-        if (is_null(value: $tokens)) {
-            return $this->errorResponse(message: __(key: 'auth.unauthorized'), code: 403);
+        $result = $this->authService->refresh(RefreshTokenDTO::fromRequest(request: $request));
+        if (is_null($result)) {
+            return $this->errorResponse(message: __('auth.unauthorized'), code: 403)
+                ->withoutCookie('refresh_token');
         }
-        return $this->successResponse(payload: new AuthResource(resource: $tokens));
+
+        return $this->successResponse(payload: new AuthResource($result['payload']))
+            ->withCookie(
+                cookie(
+                    'refresh_token',
+                    $result['refreshToken'],
+                    config('sanctum.rt_expiration') * 60,
+                    '/',
+                    config('session.domain'),
+                    config('session.secure'),
+                    true,
+                    true,
+                    'Lax'
+                )
+            );
     }
 }

+ 1 - 1
app/Http/Requests/AuthRequest.php

@@ -9,7 +9,7 @@ class AuthRequest extends FormRequest
     public function rules(): array
     {
         return [
-            'email' => 'required|string',
+            'email' => 'required|string|email',
             'password' => 'required|string',
         ];
     }

+ 14 - 3
app/Http/Requests/RefreshTokenRequest.php

@@ -6,10 +6,21 @@
 
 class RefreshTokenRequest extends FormRequest
 {
+    public function authorize(): bool
+    {
+        return true;
+    }
+
     public function rules(): array
     {
-        return [
-            'refresh_token' => 'string|nullable',
-        ];
+        return [];
+    }
+
+    //This adds the cookie value to the request data.
+    protected function passedValidation(): void
+    {
+        $this->merge([
+            'refresh_token' => $this->cookie('refresh_token'),
+        ]);
     }
 }

+ 1 - 3
app/Http/Resources/AuthResource.php

@@ -17,10 +17,8 @@ public function toArray(Request $request): array
     {
         return [
             'access_token' => $this['access_token'],
-            'refresh_token' => $this['refresh_token'],
             'token_type' => 'Bearer',
-            'expires_in' => 900, // 15 minutes
-            'refresh_token_expires_in' => 2592000, // 30 days
+            'expires_in' => 900,
             'user' => new UserResource($this['user']),
         ];
     }

+ 1 - 3
app/Http/Resources/UserTypePermissionResource.php

@@ -20,12 +20,10 @@ public function toArray(Request $request): array
         return [
             'id' => $this->id,
             'scope' => $this->permission->scope,
-            'permission_id' => $this->permission_id,
+            'user_permission_id' => $this->permission_id,
             'description' => $this->permission->description,
             'parent_id' => $this->permission->parent_id,
             'bits' => $this->bits,
-            'created_at' => Carbon::parse($this->created_at)->format('Y-m-d H:i:s'),
-            'updated_at' => Carbon::parse($this->updated_at)->format('Y-m-d H:i:s'),
         ];
     }
 

+ 4 - 4
app/Repositories/PermissionRepository.php

@@ -25,17 +25,17 @@ public function allNoTree(): ?Collection
 
     public function find(int $id): ?Permission
     {
-        return $this->model->find(id: $id);
+        return $this->model->find($id);
     }
 
     public function findByScope(string $scope): ?Permission
     {
-        return $this->model->where(column: 'scope', operator: $scope)->first();
+        return $this->model->where('scope', $scope)->first();
     }
 
     public function update(int $id, PermissionDTO $dto, array $fieldsToUpdate): ?Permission
     {
-        $permission = $this->model->find(id: $id);
+        $permission = $this->model->find($id);
 
         $updateFields = array_intersect_key(
             $dto->toArray(),
@@ -67,6 +67,6 @@ public function store(PermissionDTO $permissionDTO): Permission
 
     public function delete(int $id): bool
     {
-        return $this->model->destroy(ids: $id) > 0;
+        return $this->model->destroy($id) > 0;
     }
 }

+ 21 - 15
app/Services/AuthService.php

@@ -27,22 +27,23 @@ public function login(AuthDTO $credentials): ?array
         $refreshToken = $this->authRepository->createRefreshToken(user: $user, deviceId: $deviceId);
 
         return [
-            'access_token' => $accessToken,
-            'refresh_token' => $refreshToken,
-            'user' => $user,
-            'device_id' => $deviceId,
+            'payload' => [
+                'access_token' => $accessToken,
+                'user' => $user,
+            ],
+            'refreshToken' => $refreshToken
         ];
     }
 
-    public function refresh(RefreshTokenDTO $refreshToken): ?array
+    public function refresh(RefreshTokenDTO $refreshTokenDto): ?array
     {
-        if (!$refreshToken->refresh_token) {
+        if (!$refreshTokenDto->refresh_token) {
             return null;
         }
 
-        $tokenModel = $this->authRepository->findToken($refreshToken->refresh_token);
+        $tokenModel = $this->authRepository->findToken($refreshTokenDto->refresh_token);
 
-        if (!$tokenModel || !in_array(needle: 'refresh', haystack: $tokenModel->abilities) || $tokenModel->expires_at < now()) {
+        if (!$tokenModel || !in_array('refresh', $tokenModel->abilities) || $tokenModel->expires_at < now()) {
             return null;
         }
 
@@ -51,25 +52,30 @@ public function refresh(RefreshTokenDTO $refreshToken): ?array
             return null;
         }
 
-        $deviceId = Str::afterLast(subject: $tokenModel->name, search: '_');
+        $deviceId = Str::afterLast($tokenModel->name, '_');
 
         $tokens = $this->authRepository->refreshToken($tokenModel, $user, $deviceId);
 
-        return array_merge($tokens, [
-            'user' => $user,
-            'device_id' => $deviceId,
-        ]);
+        return [
+            'payload' => [
+                'access_token' => $tokens['access_token'],
+                'user' => $user,
+            ],
+            'refreshToken' => $tokens['refresh_token']
+        ];
     }
 
+
     public function logout(): void
     {
         $user = Auth::user();
-        $tokenName = $user->currentAccessToken()->name;
-        $deviceId = Str::afterLast($tokenName, '_');
         if (!$user) {
             return;
         }
 
+        $tokenName = $user->currentAccessToken()->name;
+        $deviceId = Str::afterLast($tokenName, '_');
+
         $this->authRepository->deleteUserTokensByDevice(user: $user, deviceId: $deviceId);
     }
 }

+ 5 - 4
bootstrap/app.php

@@ -6,20 +6,21 @@
 use Illuminate\Console\Scheduling\Schedule;
 use App\Http\Middleware\SetUserLanguage;
 use App\Http\Middleware\CheckPermission;
+use App\Http\Middleware\PerformanceMonitor;
 use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;
 use App\Tasks\DeleteExpiredTokens;
 
 
 return Application::configure(basePath: dirname(__DIR__))
     ->withRouting(
-        web: __DIR__.'/../routes/web.php',
-        api: __DIR__.'/../routes/api.php',
-        commands: __DIR__.'/../routes/console.php',
+        web: __DIR__ . '/../routes/web.php',
+        api: __DIR__ . '/../routes/api.php',
+        commands: __DIR__ . '/../routes/console.php',
         health: '/up',
     )
     ->withMiddleware(function (Middleware $middleware) {
         // $middleware->statefulApi();
-        $middleware->append(SetUserLanguage::class);
+        $middleware->append([SetUserLanguage::class, PerformanceMonitor::class]);
         $middleware->alias([
             'permission' => CheckPermission::class,
             'ability' => CheckForAnyAbility::class,

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 185 - 196
composer.lock


+ 12 - 0
config/sanctum.php

@@ -48,6 +48,18 @@
 
     'expiration' => null,
 
+    /*
+    |--------------------------------------------------------------------------
+    | Refresh Token Expiration Minutes
+    |--------------------------------------------------------------------------
+    |
+    | This value controls the number of minutes until an issued refresh
+    | token cookie will be considered expired.
+    |
+    */
+
+    'rt_expiration' => 129600, // 90 days in minutes (90 * 24 * 60)
+
     /*
     |--------------------------------------------------------------------------
     | Token Prefix

+ 9 - 6
database/seeders/PermissionSeeder.php

@@ -10,8 +10,7 @@ class PermissionSeeder extends Seeder
 {
     public function __construct(
         protected PermissionService $permissionService,
-    ) {
-    }
+    ) {}
 
     public function run(): void
     {
@@ -78,13 +77,17 @@ public function run(): void
         $this->createPermissionsAndChildren(permissions: $permissions);
     }
 
-    private function createPermissionsAndChildren(array $permissions): void
+    private function createPermissionsAndChildren(array $permissions, ?int $parent_id = null): void
     {
         foreach ($permissions as $permission) {
-            $this->permissionService->store(permissionDTO: PermissionDTO::fromArray(data: (array) $permission));
-
+            if ($parent_id != null) {
+                array_merge($permission, [
+                    'parent_id' => $parent_id,
+                ]);
+            }
+            $permissionDb = $this->permissionService->store(permissionDTO: PermissionDTO::fromArray(data: (array) $permission));
             if (!empty($permission['children'])) {
-                $this->createPermissionsAndChildren(permissions: $permission['children']);
+                $this->createPermissionsAndChildren(permissions: $permission['children'], parent_id: $permissionDb->id);
             }
         }
     }

+ 37 - 14
database/seeders/UserTypePermissionSeeder.php

@@ -18,25 +18,48 @@ public function run(): void
         foreach (UserTypeEnum::cases() as $userType) {
             switch ($userType) {
                 case UserTypeEnum::ADMIN:
-                    $permissions = Permission::get();
-                    foreach ($permissions as $permission) {
-                        $userTypePermission = UserTypePermission::firstOrNew(attributes: [
-                            'user_type' => UserTypeEnum::ADMIN->value,
-                            'permission_id' => $permission->id,
+                    $permissions = Permission::get()->map(function ($permission) {
+                        return [
+                            'scope' => $permission->scope,
                             'bits' => $permission->bits,
-                        ]);
-                        $userTypePermission->save();
-                    }
+                        ];
+                    })->toArray();
+                    $this->seedUserTypePermissions($permissions, UserTypeEnum::ADMIN->value);
                     break;
+
+                case UserTypeEnum::USER:
+                    $userPermissions = [
+                        ['scope' => 'dashboard', 'bits' => 1],
+                        ['scope' => 'config.user', 'bits' => 5],
+                        ['scope' => 'config.city', 'bits' => 1],
+                        ['scope' => 'config.country', 'bits' => 1],
+                        ['scope' => 'config.state', 'bits' => 1],
+                    ];
+                    $this->seedUserTypePermissions($userPermissions, UserTypeEnum::USER->value);
+                    break;
+
                 case UserTypeEnum::GUEST:
-                    $userTypePermission = UserTypePermission::firstOrNew([
-                        'user_type' => UserTypeEnum::GUEST->value,
-                        'permission_id' => Permission::where('scope', 'config.user')->first()->id,
-                        'bits' => 5,
-                    ]);
-                    $userTypePermission->save();
+                    $guestPermissions = [
+                        ['scope' => 'config.user', 'bits' => 1],
+                    ];
+                    $this->seedUserTypePermissions($guestPermissions, UserTypeEnum::GUEST->value);
                     break;
             }
         }
     }
+
+    private function seedUserTypePermissions(array $permissions, string $userType): void
+    {
+        foreach ($permissions as $permissionData) {
+            $permission = Permission::where('scope', $permissionData['scope'])->first();
+            if ($permission) {
+                $userTypePermission = UserTypePermission::firstOrNew([
+                    'user_type' => $userType,
+                    'permission_id' => $permission->id,
+                    'bits' => $permissionData['bits'],
+                ]);
+                $userTypePermission->save();
+            }
+        }
+    }
 }

+ 1 - 1
lang/en/auth.php

@@ -13,7 +13,7 @@
     |
     */
 
-    'failed' => 'These credentials do not match our records.',
+    'failed' => 'Invalid credentials',
     'password' => 'The provided password is incorrect.',
     'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
     'logout' => 'Logged out successfully',

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác