Skip to content

Commit 7483eea

Browse files
committed
fix: empêcher le bypass 2FA sur l'API client via token pending
Le token 2fa:pending pouvait accéder à toutes les routes protégées. Ajout du middleware abilities:* sur les routes protégées et abilities:2fa:pending uniquement sur la route de vérification 2FA.
1 parent 58183cd commit 7483eea

2 files changed

Lines changed: 61 additions & 44 deletions

File tree

routes/api-client.php

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -48,54 +48,58 @@
4848
Route::middleware('auth:sanctum')->group(function () {
4949
// Auth
5050
Route::post('/auth/logout', [AuthController::class, 'logout'])->name('auth.logout');
51-
Route::post('/auth/2fa/verify', [AuthController::class, 'verify2fa'])->name('auth.2fa.verify');
51+
Route::middleware('abilities:2fa:pending')
52+
->post('/auth/2fa/verify', [AuthController::class, 'verify2fa'])
53+
->name('auth.2fa.verify');
5254

53-
// Profile
54-
Route::prefix('profile')->name('profile.')->group(function () {
55-
Route::get('/', [ProfileController::class, 'show'])->name('show');
56-
Route::put('/', [ProfileController::class, 'update'])->name('update');
57-
Route::put('/password', [ProfileController::class, 'password'])->name('password');
58-
Route::get('/2fa/setup', [ProfileController::class, 'setup2fa'])->name('2fa.setup');
59-
Route::post('/2fa', [ProfileController::class, 'toggle2fa'])->name('2fa');
60-
Route::get('/2fa/recovery-codes', [ProfileController::class, 'recoveryCodes'])->name('2fa.recovery-codes');
61-
Route::post('/security-question', [ProfileController::class, 'saveSecurityQuestion'])->name('security-question');
62-
Route::delete('/', [ProfileController::class, 'deleteAccount'])->name('delete');
63-
});
55+
Route::middleware('abilities:*')->group(function () {
56+
// Profile
57+
Route::prefix('profile')->name('profile.')->group(function () {
58+
Route::get('/', [ProfileController::class, 'show'])->name('show');
59+
Route::put('/', [ProfileController::class, 'update'])->name('update');
60+
Route::put('/password', [ProfileController::class, 'password'])->name('password');
61+
Route::get('/2fa/setup', [ProfileController::class, 'setup2fa'])->name('2fa.setup');
62+
Route::post('/2fa', [ProfileController::class, 'toggle2fa'])->name('2fa');
63+
Route::get('/2fa/recovery-codes', [ProfileController::class, 'recoveryCodes'])->name('2fa.recovery-codes');
64+
Route::post('/security-question', [ProfileController::class, 'saveSecurityQuestion'])->name('security-question');
65+
Route::delete('/', [ProfileController::class, 'deleteAccount'])->name('delete');
66+
});
6467

65-
// Tickets
66-
Route::prefix('tickets')->name('tickets.')->group(function () {
67-
Route::get('/', [TicketController::class, 'index'])->name('index');
68-
Route::post('/', [TicketController::class, 'store'])->name('store');
69-
Route::get('/departments', [TicketController::class, 'departments'])->name('departments');
70-
Route::get('/{ticket}', [TicketController::class, 'show'])->name('show');
71-
Route::post('/{ticket}/reply', [TicketController::class, 'reply'])->name('reply');
72-
Route::post('/{ticket}/close', [TicketController::class, 'close'])->name('close');
73-
Route::post('/{ticket}/reopen', [TicketController::class, 'reopen'])->name('reopen');
74-
Route::get('/{ticket}/attachments/{attachment}', [TicketController::class, 'downloadAttachment'])->name('attachment');
75-
});
68+
// Tickets
69+
Route::prefix('tickets')->name('tickets.')->group(function () {
70+
Route::get('/', [TicketController::class, 'index'])->name('index');
71+
Route::post('/', [TicketController::class, 'store'])->name('store');
72+
Route::get('/departments', [TicketController::class, 'departments'])->name('departments');
73+
Route::get('/{ticket}', [TicketController::class, 'show'])->name('show');
74+
Route::post('/{ticket}/reply', [TicketController::class, 'reply'])->name('reply');
75+
Route::post('/{ticket}/close', [TicketController::class, 'close'])->name('close');
76+
Route::post('/{ticket}/reopen', [TicketController::class, 'reopen'])->name('reopen');
77+
Route::get('/{ticket}/attachments/{attachment}', [TicketController::class, 'downloadAttachment'])->name('attachment');
78+
});
7679

77-
// Invoices
78-
Route::prefix('invoices')->name('invoices.')->group(function () {
79-
Route::get('/', [InvoiceController::class, 'index'])->name('index');
80-
Route::get('/{invoice}', [InvoiceController::class, 'show'])->name('show');
81-
Route::get('/{invoice}/pdf', [InvoiceController::class, 'pdf'])->name('pdf');
82-
Route::get('/{invoice}/download', [InvoiceController::class, 'download'])->name('download');
83-
Route::post('/{invoice}/pay/{gateway}', [InvoiceController::class, 'pay'])->name('pay');
84-
Route::post('/{invoice}/balance', [InvoiceController::class, 'balance'])->name('balance');
85-
});
80+
// Invoices
81+
Route::prefix('invoices')->name('invoices.')->group(function () {
82+
Route::get('/', [InvoiceController::class, 'index'])->name('index');
83+
Route::get('/{invoice}', [InvoiceController::class, 'show'])->name('show');
84+
Route::get('/{invoice}/pdf', [InvoiceController::class, 'pdf'])->name('pdf');
85+
Route::get('/{invoice}/download', [InvoiceController::class, 'download'])->name('download');
86+
Route::post('/{invoice}/pay/{gateway}', [InvoiceController::class, 'pay'])->name('pay');
87+
Route::post('/{invoice}/balance', [InvoiceController::class, 'balance'])->name('balance');
88+
});
8689

87-
// Services
88-
Route::prefix('services')->name('services.')->group(function () {
89-
Route::get('/', [ServiceController::class, 'index'])->name('index');
90-
Route::get('/{service}', [ServiceController::class, 'show'])->name('show');
91-
});
90+
// Services
91+
Route::prefix('services')->name('services.')->group(function () {
92+
Route::get('/', [ServiceController::class, 'index'])->name('index');
93+
Route::get('/{service}', [ServiceController::class, 'show'])->name('show');
94+
});
9295

93-
// Payment Methods
94-
Route::prefix('payment-methods')->name('payment-methods.')->group(function () {
95-
Route::get('/', [PaymentMethodController::class, 'index'])->name('index');
96-
Route::get('/gateways', [PaymentMethodController::class, 'gateways'])->name('gateways');
97-
Route::post('/{gateway}', [PaymentMethodController::class, 'add'])->name('add');
98-
Route::put('/{source}/default', [PaymentMethodController::class, 'setDefault'])->name('default');
99-
Route::delete('/{source}', [PaymentMethodController::class, 'delete'])->name('delete');
96+
// Payment Methods
97+
Route::prefix('payment-methods')->name('payment-methods.')->group(function () {
98+
Route::get('/', [PaymentMethodController::class, 'index'])->name('index');
99+
Route::get('/gateways', [PaymentMethodController::class, 'gateways'])->name('gateways');
100+
Route::post('/{gateway}', [PaymentMethodController::class, 'add'])->name('add');
101+
Route::put('/{source}/default', [PaymentMethodController::class, 'setDefault'])->name('default');
102+
Route::delete('/{source}', [PaymentMethodController::class, 'delete'])->name('delete');
103+
});
100104
});
101105
});

tests/Feature/Api/Client/AuthControllerTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,4 +239,17 @@ public function test_customer_can_logout(): void
239239
'id' => $token->accessToken->id,
240240
]);
241241
}
242+
243+
public function test_pending_2fa_token_cannot_access_protected_client_resources(): void
244+
{
245+
$customer = Customer::factory()->create();
246+
$pendingToken = $customer->createToken('2fa-pending', ['2fa:pending']);
247+
248+
$response = $this->withHeaders([
249+
'Authorization' => 'Bearer '.$pendingToken->plainTextToken,
250+
'Accept' => 'application/json',
251+
])->getJson('/api/client/profile');
252+
253+
$response->assertForbidden();
254+
}
242255
}

0 commit comments

Comments
 (0)