-
Notifications
You must be signed in to change notification settings - Fork 13
[SECURITY] Token API Harus Expire & Support Rotasi/Revocation #983
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: rilis-dev
Are you sure you want to change the base?
Changes from all commits
9d9892b
9bd3153
3aaffca
f9500b7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| <?php | ||
|
|
||
| namespace App\Http\Controllers\Api; | ||
|
|
||
| use App\Http\Controllers\Controller; | ||
| use App\Http\Requests\RefreshTokenRequest; | ||
| use App\Models\RefreshToken; | ||
| use App\Models\User; | ||
| use Illuminate\Http\JsonResponse; | ||
| use Illuminate\Http\Request; | ||
| use Illuminate\Support\Facades\Log; | ||
| use Illuminate\Support\Str; | ||
| use Laravel\Sanctum\PersonalAccessToken; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [HIGH] โก Performance: Query Tanpa Index pada Token Hash Masalah: Query Kode: $refreshToken = RefreshToken::where('token', $hashedToken)
->where('expires_at', '>', now())
->whereNull('revoked_at')
->first();Dampak: Tanpa index, full table scan pada tabel dengan 100K+ refresh tokens = 50-200ms per query. Dengan index: <5ms. Fix: // Tambahkan di migration: database/migrations/2026_03_12_100843_create_refresh_tokens_table.php
Schema::create('refresh_tokens', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('token', 64)->unique(); // PENTING: unique() membuat index
$table->string('ip_address')->nullable();
$table->text('user_agent')->nullable();
$table->timestamp('expires_at');
$table->timestamp('revoked_at')->nullable();
$table->timestamps();
// Composite index untuk query optimization
$table->index(['token', 'expires_at', 'revoked_at']);
});There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [HIGH] ๐ Code Quality: Missing Return Type Hints Kategori: PHP Quality Masalah: Method Kode: Fix: public function refresh(RefreshTokenRequest $request): \Illuminate\Http\JsonResponse
{
// ... existing code
}There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [HIGH] ๐ Code Quality: Fat Controller - Complex Business Logic Kategori: Architecture Masalah: Method Kode: public function refresh(RefreshTokenRequest $request)
{
$hashedToken = hash('sha256', $request->refresh_token);
$refreshToken = RefreshToken::where('token', $hashedToken)
->where('expires_at', '>', now())
->where('revoked', false)
->first();
if (!$refreshToken) {
return response()->json([
'message' => 'Invalid or expired refresh token',
], 401);
}
// Check if IP or User Agent changed (anomaly detection)
$ipChanged = $refreshToken->ip_address !== $request->ip();
$userAgentChanged = $refreshToken->user_agent !== $request->userAgent();
if ($ipChanged || $userAgentChanged) {
// Log anomaly
activity()
->causedBy($refreshToken->user)
->withProperties([...])
->log('Token anomaly detected during refresh');
}
// ... 40+ more lines
}Fix: // Create app/Services/RefreshTokenService.php
namespace App\Services;
class RefreshTokenService
{
public function validateAndRefresh(string $refreshToken, Request $request): array
{
$hashedToken = hash('sha256', $refreshToken);
$token = RefreshToken::where('token', $hashedToken)
->where('expires_at', '>', now())
->where('revoked', false)
->first();
if (!$token) {
throw new \Exception('Invalid or expired refresh token');
}
$this->detectAndLogAnomaly($token, $request);
$user = $token->user;
// Revoke used token
$token->update(['revoked' => true]);
return $this->createNewTokens($user, $request);
}
private function detectAndLogAnomaly(RefreshToken $token, Request $request): void
{
$ipChanged = $token->ip_address !== $request->ip();
$userAgentChanged = $token->user_agent !== $request->userAgent();
if ($ipChanged || $userAgentChanged) {
activity()
->causedBy($token->user)
->withProperties([
'old_ip' => $token->ip_address,
'new_ip' => $request->ip(),
'old_user_agent' => $token->user_agent,
'new_user_agent' => $request->userAgent(),
'token_id' => $token->id,
])
->log('Token anomaly detected during refresh');
}
}
private function createNewTokens(User $user, Request $request): array
{
// Token creation logic
}
}
// In Controller
public function refresh(RefreshTokenRequest $request, RefreshTokenService $service): JsonResponse
{
try {
$tokens = $service->validateAndRefresh($request->refresh_token, $request);
return response()->json($tokens);
} catch (\Exception $e) {
return response()->json(['message' => $e->getMessage()], 401);
}
} |
||
|
|
||
| class RefreshTokenController extends Controller | ||
| { | ||
| /** | ||
| * Refresh access token using refresh token. | ||
| * | ||
| * @param RefreshTokenRequest $request | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [HIGH] ๐ Security: Race Condition in Single-Use Refresh Token Masalah: Refresh token validation dan revocation tidak atomic. Attacker bisa exploit race condition dengan mengirim multiple concurrent requests menggunakan refresh token yang sama sebelum di-revoke. Kode: $refreshToken = RefreshToken::where('token', $hashedToken)
->where('expires_at', '>', now())
->whereNull('revoked_at')
->first();
if (! $refreshToken) {
return response()->json([
'message' => 'Invalid or expired refresh token',
], 401);
}
// ... validasi anomali ...
// Revoke refresh token lama (single-use)
$refreshToken->update(['revoked_at' => now()]);Risiko: Dalam window waktu antara check Cara Reproduksi: import requests
import threading
url = "https://target.com/api/auth/refresh"
refresh_token = "stolen_refresh_token_xyz123"
results = []
def exploit_race_condition():
response = requests.post(url, json={"refresh_token": refresh_token})
results.append(response.json())
# Launch 10 concurrent requests
threads = []
for i in range(10):
t = threading.Thread(target=exploit_race_condition)
threads.append(t)
t.start()
# Wait for all threads
for t in threads:
t.join()
# Check results
valid_tokens = [r for r in results if 'access_token' in r]
print(f"Got {len(valid_tokens)} valid access tokens from single refresh token!")
# Expected: 1 token (single-use)
# Actual: Multiple tokens (race condition exploited)Fix: public function refresh(RefreshTokenRequest $request)
{
$hashedToken = hash('sha256', $request->refresh_token);
// Gunakan database transaction dengan pessimistic locking
DB::beginTransaction();
try {
// Lock row untuk prevent race condition
$refreshToken = RefreshToken::where('token', $hashedToken)
->where('expires_at', '>', now())
->whereNull('revoked_at')
->lockForUpdate() // CRITICAL: Pessimistic lock
->first();
if (! $refreshToken) {
DB::rollBack();
return response()->json([
'message' => 'Invalid or expired refresh token',
], 401);
}
// Validasi IP dan User Agent untuk deteksi anomali
if ($refreshToken->ip_address !== $request->ip() ||
$refreshToken->user_agent !== $request->userAgent()) {
activity()
->causedBy($refreshToken->user)
->withProperties([
'original_ip' => $refreshToken->ip_address,
'current_ip' => $request->ip(),
'original_user_agent' => $refreshToken->user_agent,
'current_user_agent' => $request->userAgent(),
])
->log('Token anomaly detected during refresh');
}
$user = $refreshToken->user;
// Revoke refresh token lama IMMEDIATELY (single-use)
$refreshToken->update(['revoked_at' => now()]);
// Buat access token baru
$token = $user->createToken('api-token', ['*'], now()->addMinutes(config('sanctum.expiration')));
$token->accessToken->update([
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent(),
]);
// Buat refresh token baru
$newRefreshToken = RefreshToken::create([
'user_id' => $user->id,
'token' => hash('sha256', $plainTextRefreshToken = \Str::random(64)),
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent(),
'expires_at' => now()->addDays(config('auth.refresh_token_lifetime', 30)),
]);
DB::commit();
return response()->json([
'access_token' => $token->plainTextToken,
'refresh_token' => $plainTextRefreshToken,
'token_type' => 'Bearer',
'expires_in' => config('sanctum.expiration') * 60,
]);
} catch (\Exception $e) {
DB::rollBack();
// Log error untuk investigation
\Log::error('Refresh token error', [
'error' => $e->getMessage(),
'ip' => $request->ip(),
]);
return response()->json([
'message' => 'An error occurred during token refresh',
], 500);
}
} |
||
| * @return JsonResponse | ||
| */ | ||
| public function refresh(RefreshTokenRequest $request): JsonResponse | ||
| { | ||
| try { | ||
| $refreshTokenString = $request->validated('refresh_token'); | ||
|
|
||
| // Find the refresh token | ||
| $refreshToken = RefreshToken::where('refresh_token', $refreshTokenString)->first(); | ||
|
|
||
| if (! $refreshToken) { | ||
| return response()->json([ | ||
| 'message' => 'Refresh token tidak ditemukan', | ||
| ], JsonResponse::HTTP_NOT_FOUND); | ||
| } | ||
|
|
||
| // Check if refresh token is valid | ||
| if (! $refreshToken->isValid()) { | ||
| if ($refreshToken->is_revoked) { | ||
| return response()->json([ | ||
| 'message' => 'Refresh token telah dicabut. Silakan login ulang.', | ||
| ], JsonResponse::HTTP_FORBIDDEN); | ||
| } | ||
|
|
||
| if ($refreshToken->isExpired()) { | ||
| return response()->json([ | ||
| 'message' => 'Refresh token telah expired. Silakan login ulang.', | ||
| ], JsonResponse::HTTP_FORBIDDEN); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [CRITICAL] โก Performance: N+1 Query - Missing Eager Loading Masalah: Query Kode: $user = User::find($refreshToken->user_id);Dampak: +1 query per refresh request. Dengan 100K refresh/hari = 100K query tidak perlu. Estimasi: 0.5-2ms overhead per request. Fix: // Gunakan relasi yang sudah ada
$user = $refreshToken->user;
// Atau eager load sejak awal (line 13-16):
$refreshToken = RefreshToken::with('user')
->where('token', $hashedToken)
->where('expires_at', '>', now())
->whereNull('revoked_at')
->first(); |
||
| } | ||
| } | ||
|
|
||
| // Get the user | ||
| $user = $refreshToken->user; | ||
|
|
||
| if (! $user || ! $user->active) { | ||
| return response()->json([ | ||
| 'message' => 'User tidak ditemukan atau tidak aktif', | ||
| ], JsonResponse::HTTP_FORBIDDEN); | ||
| } | ||
|
|
||
| // Revoke old refresh token (single use) | ||
| $refreshToken->revoke('token_refresh'); | ||
|
|
||
| // Revoke old access token if exists | ||
| if ($refreshToken->access_token_id) { | ||
| PersonalAccessToken::find($refreshToken->access_token_id)?->delete(); | ||
| } | ||
|
|
||
| // Create new access token | ||
| $newAccessToken = $user->createToken('auth_token'); | ||
|
|
||
| // Update access token with metadata | ||
| $newAccessTokenModel = PersonalAccessToken::find($newAccessToken->accessToken->id); | ||
| $newAccessTokenModel->forceFill([ | ||
| 'ip_address' => $request->ip(), | ||
| 'user_agent' => $request->userAgent(), | ||
| 'expires_at' => now()->addMinutes(config('sanctum.expiration')), | ||
| ]); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [CRITICAL] ๐ Bug: Null Dereference - Fatal Error Saat Token Tidak Ditemukan Kode: $refreshToken = RefreshToken::where('token', $hashedToken)->first();
if ($refreshToken->isExpired()) {
return response()->json(['error' => 'Refresh token expired'], 401);
}Skenario: Jika token tidak ditemukan di database (token invalid, sudah dihapus, atau typo), Dampak: Endpoint crash dengan 500 error setiap kali user kirim invalid refresh token. Attacker bisa DoS endpoint ini dengan spam invalid tokens. Fix: $refreshToken = RefreshToken::where('token', $hashedToken)->first();
if (!$refreshToken) {
return response()->json(['error' => 'Invalid refresh token'], 401);
}
if ($refreshToken->isExpired()) {
return response()->json(['error' => 'Refresh token expired'], 401);
} |
||
| $newAccessTokenModel->save(); | ||
|
|
||
| // Create new refresh token | ||
| $newRefreshToken = $this->createRefreshToken($user, $newAccessTokenModel->id, $request); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [HIGH] ๐ Code Quality: Missing Return Type Hints Kategori: PHP Quality Masalah: Method Kode: Fix: public function revokeAll(Request $request): \Illuminate\Http\JsonResponse
{
// ... existing code
} |
||
|
|
||
| return response()->json([ | ||
| 'message' => 'Token berhasil di-refresh', | ||
| 'data' => [ | ||
| 'access_token' => $newAccessToken->plainTextToken, | ||
| 'refresh_token' => $newRefreshToken->refresh_token, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [HIGH] ๐ Bug: Null Dereference - Orphaned Refresh Token Kode: $user = $refreshToken->user;
// ... langsung digunakan tanpa null check ...
$newToken = $user->createToken(...);Skenario: Jika user dihapus dari database tapi refresh token masih ada (orphaned record karena tidak ada cascade delete), Dampak: Endpoint crash untuk orphaned tokens. User yang sudah dihapus tapi refresh token masih beredar bisa crash sistem. Fix: $user = $refreshToken->user;
if (!$user) {
$refreshToken->delete(); // cleanup orphaned token
return response()->json(['error' => 'User not found'], 401);
}
$newToken = $user->createToken(...); |
||
| 'token_type' => 'Bearer', | ||
| 'expires_in' => config('sanctum.expiration') * 60, // in seconds | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [HIGH] ๐ Code Quality: Missing FormRequest Validation Kategori: PHP Quality Masalah: Method Kode: public function revoke(Request $request)
{
$request->validate([
'refresh_token' => 'required|string',
]);Fix: // Create app/Http/Requests/RevokeRefreshTokenRequest.php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class RevokeRefreshTokenRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'refresh_token' => 'required|string|size:100',
];
}
}
// Update controller
public function revoke(RevokeRefreshTokenRequest $request): JsonResponse
{
$refreshToken = RefreshToken::where('refresh_token', $request->validated()['refresh_token'])
->where('user_id', $request->user()->id)
->first();
// ... rest of code
} |
||
| 'refresh_expires_in' => config('auth.refresh_token_lifetime', 2592000), // 30 days in seconds | ||
| ], | ||
| ]); | ||
| } catch (\Exception $e) { | ||
| Log::error('Refresh token error: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine()); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [HIGH] ๐ Bug: Race Condition - Token Rotation Tanpa Transaction Kode: $refreshToken->delete();
$oldToken->delete();
$newToken = $user->createToken(...);
RefreshToken::create(...);Skenario: Jika salah satu operasi gagal di tengah-tengah (misal Dampak: User terkunci dari sistem, harus login ulang. Dalam production dengan high traffic, ini bisa terjadi sering karena DB connection timeout atau deadlock. Fix: DB::transaction(function () use ($refreshToken, $oldToken, $user, $request) {
$refreshToken->delete();
$oldToken->delete();
$newToken = $user->createToken(
'auth_token',
['*'],
now()->addMinutes(config('sanctum.expiration') ?? 1440)
);
$newToken->accessToken->update([
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent(),
]);
$newRefreshToken = RefreshToken::create([...]);
return [...];
}); |
||
| Log::error('Stack trace: ' . $e->getTraceAsString()); | ||
|
|
||
| return response()->json([ | ||
| 'message' => 'Server Error: ' . $e->getMessage(), | ||
| 'file' => $e->getFile(), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [HIGH] โก Performance: Missing Pagination Masalah: Method Kode: $refreshTokens = RefreshToken::where('user_id', $request->user()->id)
->whereNull('revoked_at')
->where('expires_at', '>', now())
->orderBy('created_at', 'desc')
->get();Dampak: User dengan 50+ tokens = 50+ rows di-load sekaligus. Memory spike dan response time lambat. Estimasi: 10-50ms overhead untuk 50 tokens. Fix: $refreshTokens = RefreshToken::where('user_id', $request->user()->id)
->whereNull('revoked_at')
->where('expires_at', '>', now())
->orderBy('created_at', 'desc')
->paginate(20); // atau simplePaginate(20)
return response()->json($refreshTokens); |
||
| 'line' => $e->getLine(), | ||
| ], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [CRITICAL] ๐ Bug: Null Config Value di Token Creation Kode: Skenario: Sama seperti AuthController - jika config null, Carbon throw TypeError Dampak: Refresh token endpoint crash, user tidak bisa perpanjang session Fix: now()->addMinutes(config('sanctum.expiration') ?? 1440) |
||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [HIGH] ๐ Code Quality: Missing Return Type Hints Kategori: PHP Quality Masalah: Method Kode: Fix: public function index(Request $request): \Illuminate\Http\JsonResponse
{
// ... existing code
} |
||
| } | ||
|
|
||
| /** | ||
| * Create a new refresh token. | ||
| * | ||
| * @param User $user | ||
| * @param int $accessTokenId | ||
| * @param Request $request | ||
| * @return RefreshToken | ||
| */ | ||
| private function createRefreshToken(User $user, int $accessTokenId, Request $request): RefreshToken | ||
| { | ||
| return RefreshToken::create([ | ||
| 'user_id' => $user->id, | ||
| 'refresh_token' => Str::random(100), | ||
| 'access_token_id' => $accessTokenId, | ||
| 'ip_address' => $request->ip() ?? '0.0.0.0', | ||
| 'user_agent' => $request->userAgent() ?? 'Unknown', | ||
| 'expires_at' => now()->addDays(config('auth.refresh_token_lifetime_days', 30)), | ||
| 'is_revoked' => false, | ||
| ]); | ||
| } | ||
|
|
||
| /** | ||
| * Revoke refresh token (logout). | ||
| * | ||
| * @param RefreshTokenRequest $request | ||
| * @return JsonResponse | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [HIGH] ๐ Code Quality: Missing Return Type Hints Kategori: PHP Quality Masalah: Method Kode: Fix: public function revoke(Request $request, int $id): \Illuminate\Http\JsonResponse
{
// ... existing code
}There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [CRITICAL] ๐ Code Quality: Missing Input Validation Kategori: PHP Quality Masalah: Method Kode: Fix: // Create app/Http/Requests/RefreshToken/RevokeRefreshTokenRequest.php
namespace App\Http\Requests\RefreshToken;
use Illuminate\Foundation\Http\FormRequest;
class RevokeRefreshTokenRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'id' => 'required|integer|exists:refresh_tokens,id',
];
}
protected function prepareForValidation(): void
{
$this->merge([
'id' => $this->route('id'),
]);
}
}
// In Controller
public function revoke(RevokeRefreshTokenRequest $request): JsonResponse
{
$user = $request->user();
$id = $request->validated()['id'];
$refreshToken = RefreshToken::where('id', $id)
->where('user_id', $user->id)
->where('revoked', false)
->first();
if (!$refreshToken) {
return response()->json([
'message' => 'Refresh token not found',
], 404);
}
$refreshToken->update(['revoked' => true]);
return response()->json([
'message' => 'Refresh token revoked successfully',
]);
} |
||
| */ | ||
| public function revoke(RefreshTokenRequest $request): JsonResponse | ||
| { | ||
| try { | ||
| $refreshTokenString = $request->validated('refresh_token'); | ||
| $refreshToken = RefreshToken::where('refresh_token', $refreshTokenString)->first(); | ||
|
|
||
| if (! $refreshToken) { | ||
| return response()->json([ | ||
| 'message' => 'Refresh token tidak ditemukan', | ||
| ], JsonResponse::HTTP_NOT_FOUND); | ||
| } | ||
|
|
||
| // Revoke the refresh token | ||
| $refreshToken->revoke('logout'); | ||
|
|
||
| // Revoke associated access token | ||
| if ($refreshToken->access_token_id) { | ||
| PersonalAccessToken::find($refreshToken->access_token_id)?->delete(); | ||
| } | ||
|
|
||
| return response()->json([ | ||
| 'message' => 'Berhasil logout', | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [HIGH] ๐ Code Quality: Missing Return Type Hints Kategori: PHP Quality Masalah: Method Kode: Fix: public function show(Request $request, int $id): \Illuminate\Http\JsonResponse
{
// ... existing code
}There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [CRITICAL] ๐ Code Quality: Missing Input Validation Kategori: PHP Quality Masalah: Method Kode: Fix: // Use FormRequest dengan route parameter validation
public function show(ShowRefreshTokenRequest $request): JsonResponse
{
$user = $request->user();
$id = $request->validated()['id'];
$refreshToken = RefreshToken::where('id', $id)
->where('user_id', $user->id)
->first();
if (!$refreshToken) {
return response()->json([
'message' => 'Refresh token not found',
], 404);
}
return response()->json([
'token' => [
'id' => $refreshToken->id,
'ip_address' => $refreshToken->ip_address,
'user_agent' => $refreshToken->user_agent,
'created_at' => $refreshToken->created_at,
'expires_at' => $refreshToken->expires_at,
'revoked' => $refreshToken->revoked,
'is_current' => $refreshToken->ip_address === $request->ip() &&
$refreshToken->user_agent === $request->userAgent(),
],
]);
} |
||
| ]); | ||
| } catch (\Exception $e) { | ||
| Log::error('Revoke refresh token error: ' . $e->getMessage()); | ||
|
|
||
| return response()->json([ | ||
| 'message' => 'Server Error: ' . $e->getMessage(), | ||
| ], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Revoke all refresh tokens for user (logout from all devices). | ||
| * | ||
| * @param Request $request | ||
| * @return JsonResponse | ||
| */ | ||
| public function revokeAll(Request $request): JsonResponse | ||
| { | ||
| $user = $request->user(); | ||
|
|
||
| if (! $user) { | ||
| return response()->json([ | ||
| 'message' => 'User tidak terautentikasi', | ||
| ], JsonResponse::HTTP_UNAUTHORIZED); | ||
| } | ||
|
|
||
| // Revoke all refresh tokens | ||
| $count = $user->refreshTokens()->update([ | ||
| 'is_revoked' => true, | ||
| 'revoked_at' => now(), | ||
| 'revoked_reason' => 'logout_all_devices', | ||
| ]); | ||
|
|
||
| // Revoke all access tokens | ||
| $user->tokens()->delete(); | ||
|
|
||
| // Log activity | ||
| activity('token') | ||
| ->causedBy($user) | ||
| ->withProperties([ | ||
| 'revoked_count' => $count, | ||
| 'action' => 'revoke_all_refresh_tokens', | ||
| ]) | ||
| ->log('Semua refresh token dicabut (logout dari semua perangkat)'); | ||
|
|
||
| return response()->json([ | ||
| 'message' => "Berhasil logout dari {$count} perangkat", | ||
| ]); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[CRITICAL] ๐ Code Quality: Missing Error Handling
Kategori: PHP Quality
Masalah: Method
refresh()tidak memiliki try-catch untuk operasi database yang bisa gagal (create token, update, delete). Database error akan expose stack trace.Kode: Semua operasi database di method
refresh(),revoke(),revokeAll()Fix: