From 4b1ed6ade3ef4c89a7205f3d74c6df3ff48154fa Mon Sep 17 00:00:00 2001 From: Jethro Lin Date: Wed, 4 Dec 2024 13:32:14 +0800 Subject: [PATCH] token system fix --- .../Api/Admin/ClientController.php | 104 +++++++++++++++--- app/Models/Client.php | 6 - app/Services/Auth/TokenService.php | 44 +++++--- 3 files changed, 120 insertions(+), 34 deletions(-) diff --git a/app/Http/Controllers/Api/Admin/ClientController.php b/app/Http/Controllers/Api/Admin/ClientController.php index b9e0894..516cfb2 100644 --- a/app/Http/Controllers/Api/Admin/ClientController.php +++ b/app/Http/Controllers/Api/Admin/ClientController.php @@ -15,6 +15,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; use Illuminate\Validation\ValidationException; +use Illuminate\Support\Facades\Redis; class ClientController extends Controller { @@ -273,35 +274,109 @@ public function update(Request $request, int $id): JsonResponse } /** - * 删除客户 - * - * @param Request $request - * @param int $id - * @return JsonResponse + * 檢查客戶是否有活躍的令牌 */ - public function destroy(Request $request, int $id): JsonResponse + private function hasActiveTokens(Client $client): bool + { + $pattern = 'auth_token:*'; + $keys = Redis::keys($pattern); + + foreach ($keys as $key) { + $data = Redis::get($key); + if ($data) { + $tokenData = json_decode($data, true); + if ($tokenData && + $tokenData['client_id'] === $client->id && + now()->isBefore($tokenData['expires_at'])) { + return true; + } + } + } + + return false; + } + + /** + * 停用客戶 + */ + public function deactivate(int $id): JsonResponse { try { $client = Client::findOrFail($id); - // 检查是否有活跃的认证令牌 - if ($client->authTokens()->where('status', 'active')->exists()) { + if (!$this->admin->canManageClient($client->id)) { return $this->error( - ErrorCode::CLIENT_HAS_ACTIVE_TOKENS, - '该客户有活跃的认证令牌,请先停用或删除令牌。' + ErrorCode::FORBIDDEN, + '您無權管理該客戶。' + ); + } + + if ($this->hasActiveTokens($client)) { + return $this->error( + ErrorCode::CLIENT_HAS_ACTIVE_TOKENS, + '該客戶有活躍的認證令牌,無法停用。' + ); + } + + $client->update(['status' => Client::STATUS_INACTIVE]); + + $this->logService->logOperation( + 'admin', + $this->admin->id, + "Deactivated client: {$client->name}" + ); + + return $this->success(); + + } catch (ModelNotFoundException $e) { + return $this->error( + ErrorCode::CLIENT_NOT_FOUND, + ErrorCode::getMessage(ErrorCode::CLIENT_NOT_FOUND) + ); + } catch (\Exception $e) { + Log::error('Error deactivating client', [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + + return $this->error( + ErrorCode::SERVER_ERROR, + ErrorCode::getMessage(ErrorCode::SERVER_ERROR) + ); + } + } + + /** + * 刪除客戶 + */ + public function destroy(int $id): JsonResponse + { + try { + $client = Client::findOrFail($id); + + if (!$this->admin->canManageClient($client->id)) { + return $this->error( + ErrorCode::FORBIDDEN, + '您無權管理該客戶。' + ); + } + + if ($this->hasActiveTokens($client)) { + return $this->error( + ErrorCode::CLIENT_HAS_ACTIVE_TOKENS, + '該客戶有活躍的認證令牌,無法刪除。' ); } - $clientName = $client->name; $client->delete(); $this->logService->logOperation( 'admin', - $request->admin->id, - "Deleted client: {$clientName}" + $this->admin->id, + "Deleted client: {$client->name}" ); - return $this->success(null, '客户已删除。'); + return $this->success(); } catch (ModelNotFoundException $e) { return $this->error( @@ -312,7 +387,6 @@ public function destroy(Request $request, int $id): JsonResponse Log::error('Error deleting client', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), - 'client_id' => $id, ]); return $this->error( diff --git a/app/Models/Client.php b/app/Models/Client.php index c820a32..47a7349 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -7,7 +7,6 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; -use Illuminate\Database\Eloquent\Relations\HasMany; class Client extends Model { @@ -52,11 +51,6 @@ public function admins(): BelongsToMany ->withTimestamps(); } - public function authTokens(): HasMany - { - return $this->hasMany(AuthToken::class); - } - /** * 检查客户是否处于活跃状态 */ diff --git a/app/Services/Auth/TokenService.php b/app/Services/Auth/TokenService.php index 59c54ce..0e15793 100644 --- a/app/Services/Auth/TokenService.php +++ b/app/Services/Auth/TokenService.php @@ -4,7 +4,6 @@ namespace App\Services\Auth; -use App\Models\AuthToken; use App\Models\Client; use App\Services\LogService; use Illuminate\Support\Facades\Redis; @@ -22,7 +21,12 @@ public function __construct( private readonly LogService $logService ) {} - public function generateAuthToken(Client $client, ?int $expiresInHours = null): AuthToken + /** + * 生成認證令牌 + * + * @return array{token: string, expires_at: string} + */ + public function generateAuthToken(Client $client, ?int $expiresInHours = null): array { // 確保不超過最大有效期 $effectiveHours = min($expiresInHours ?? self::MAX_AUTH_TOKEN_HOURS, self::MAX_AUTH_TOKEN_HOURS); @@ -38,6 +42,7 @@ public function generateAuthToken(Client $client, ?int $expiresInHours = null): $effectiveSeconds, json_encode([ 'client_id' => $client->id, + 'token' => $token, 'created_at' => now()->toIso8601String(), 'expires_at' => $expiresAt->toIso8601String(), ]) @@ -50,14 +55,17 @@ public function generateAuthToken(Client $client, ?int $expiresInHours = null): 'Generated new auth token' ); - // 返回令牌對象(僅用於 API 響應) - return new AuthToken([ - 'client_id' => $client->id, + return [ 'token' => $token, - 'expires_at' => $expiresAt, - ]); + 'expires_at' => $expiresAt->toIso8601String(), + ]; } + /** + * 驗證認證令牌 + * + * @return array{client_id: int, token: string, created_at: string, expires_at: string}|null + */ public function validateAuthToken(string $token): ?array { // 檢查黑名單 @@ -80,6 +88,11 @@ public function validateAuthToken(string $token): ?array return $data; } + /** + * 生成訪問令牌 + * + * @return array{access_token: string, expires_in: int, token_type: string} + */ public function generateAccessToken(array $authTokenData): array { $accessToken = Str::random(64); @@ -109,6 +122,11 @@ public function generateAccessToken(array $authTokenData): array ]; } + /** + * 驗證訪問令牌 + * + * @return array{client_id: int, auth_token: string, created_at: string}|null + */ public function validateAccessToken(string $token): ?array { $tokenData = Redis::get(self::ACCESS_TOKEN_PREFIX . $token); @@ -119,6 +137,9 @@ public function validateAccessToken(string $token): ?array return json_decode($tokenData, true); } + /** + * 撤銷訪問令牌 + */ public function revokeAccessToken(string $token): void { $tokenData = Redis::get(self::ACCESS_TOKEN_PREFIX . $token); @@ -137,6 +158,9 @@ public function revokeAccessToken(string $token): void } } + /** + * 撤銷認證令牌 + */ public function revokeAuthToken(string $token): void { $tokenData = Redis::get(self::AUTH_TOKEN_PREFIX . $token); @@ -165,10 +189,4 @@ public function revokeAuthToken(string $token): void Redis::del(self::AUTH_TOKEN_PREFIX . $token); } } - - public function cleanupExpiredTokens(): void - { - // Redis 會自動清理過期的令牌,無需手動清理 - // 這個方法保留用於日誌記錄或其他清理工作 - } }