felhasználói felületre pü

This commit is contained in:
Roo
2026-07-27 08:39:18 +00:00
parent c9118bf52f
commit 6f28d3e70d
72 changed files with 6311 additions and 749 deletions

View File

@@ -0,0 +1,156 @@
# Logic Spec: Fix 401 Unauthorized on Admin Ledger Endpoint
**Date:** 2026-07-25
**Severity:** P0 (Blocker — admin users cannot access Financial Ledger page)
**Related Issue:** Frontend redirects to login screen on `/finance/ledger`
---
## 1. Root Cause Analysis
### 1.1 Authentication Architecture
The FastAPI backend uses `OAuth2PasswordBearer` ([`deps.py:27-29`](backend/app/api/deps.py:27)) which extracts the JWT token **exclusively** from the `Authorization: Bearer <token>` HTTP header. It does **NOT** read tokens from cookies.
```python
# backend/app/api/deps.py:27-29
reusable_oauth2 = OAuth2PasswordBearer(
tokenUrl=f"{settings.API_V1_STR}/auth/login"
)
```
The auth dependency chain for the ledger endpoint:
```
GET /admin/finance/ledger
→ RequirePermission("finance:view") [deps.py:283]
→ get_current_active_user [deps.py:94]
→ get_current_user [deps.py:56]
→ get_current_token_payload [deps.py:31]
→ reusable_oauth2 (OAuth2PasswordBearer) [deps.py:27]
→ extracts token from Authorization: Bearer header
```
### 1.2 The Bug
The [`ledger.vue:348`](frontend_admin/pages/finance/ledger.vue:348) uses **native `fetch()`**:
```typescript
const res = await fetch(`/api/v1/admin/finance/ledger?${params.toString()}`, {
credentials: 'include',
})
```
This sends cookies (including `access_token` cookie) but does **NOT** send the `Authorization: Bearer` header. The `OAuth2PasswordBearer` dependency never sees a token → 401 Unauthorized.
### 1.3 Why Other Admin Pages Work
Every other admin page uses `$fetch()` (Nuxt's ofetch wrapper) with a `getHeaders()` helper that explicitly adds the `Authorization` header:
```typescript
function getHeaders(): Record<string, string> {
const tokenCookie = useCookie('access_token')
return tokenCookie.value
? { Authorization: `Bearer ${tokenCookie.value}` }
: {}
}
```
Examples:
- [`commission-rules.vue:694`](frontend_admin/pages/finance/commission-rules.vue:694) — has `getHeaders()`
- [`providers/index.vue:288`](frontend_admin/pages/providers/index.vue:288) — has `getHeaders()`
- [`gamification/ledger.vue:244`](frontend_admin/pages/gamification/ledger.vue:244) — has `getHeaders()`
The `ledger.vue` page **omitted** both `$fetch` and the `getHeaders()` helper — it's the only admin page with this pattern.
### 1.4 What the 401 Interceptor Does
The [`api.ts` plugin](frontend_admin/plugins/api.ts:5) overrides `globalThis.fetch` to detect 401 responses. When a 401 is detected on any non-login URL, it calls `authStore.logout()` and redirects to `/login`. This is why the user sees a redirect — the interceptor fires, clears the token, and forces a redirect.
**The interceptor is working correctly.** The problem is the request never had a token attached in the first place.
---
## 2. Fix Plan
### 2.1 Primary Fix: Frontend — [`ledger.vue`](frontend_admin/pages/finance/ledger.vue)
**File to modify:** `frontend_admin/pages/finance/ledger.vue`
**Changes:**
1. Add `getHeaders()` helper function in the `<script setup>` block:
```typescript
function getHeaders(): Record<string, string> {
const tokenCookie = useCookie('access_token')
return tokenCookie.value
? { Authorization: `Bearer ${tokenCookie.value}` }
: {}
}
```
2. Add `useCookie` import at the top:
```typescript
import { useCookie } from '#app'
```
3. Replace the native `fetch()` call with `$fetch()` and pass headers:
```typescript
// BEFORE (broken):
const res = await fetch(`/api/v1/admin/finance/ledger?${params.toString()}`, {
credentials: 'include',
})
// AFTER (fixed):
const data: LedgerResponse = await $fetch('/api/v1/admin/finance/ledger', {
headers: getHeaders(),
params: params, // $fetch handles query params natively
})
```
4. Adjust error handling — `$fetch` throws on non-2xx responses.
### 2.2 No Backend Changes Required
The [`finance_admin.py`](backend/app/api/v1/endpoints/finance_admin.py:81-96) security dependencies are **correct**:
```python
@router.get("/ledger", response_model=FinancialLedgerListResponse)
async def list_financial_ledger(
...
current_user: User = Depends(deps.get_current_active_user),
_ = Depends(deps.RequirePermission("finance:view")),
):
```
This is consistent with working endpoints like:
- [`admin_commission.py:72`](backend/app/api/v1/endpoints/admin_commission.py:72): `current_user: User = Depends(get_current_staff)`
- [`finance_admin.py:30-31`](backend/app/api/v1/endpoints/finance_admin.py:30): `list_issuers` uses identical pattern and works
The 401 is purely a **frontend auth header omission** — the backend auth chain and permission check are correct.
---
## 3. Verification Steps
1. After applying the fix, the frontend dev server should be running.
2. Log in as admin (`admin@profibot.hu` / `Admin123!`).
3. Navigate to `/finance/ledger`.
4. Verify:
- No 401 error in browser DevTools Network tab
- `Authorization: Bearer <token>` header is present in the request
- Ledger data loads with pagination, filtering
- No redirect to `/login`
---
## 4. Impact Assessment
| Component | Change | Risk |
|-----------|--------|------|
| `ledger.vue` | Add `getHeaders()`, `useCookie` import, switch to `$fetch` | Low — exact same pattern used by 20+ other admin pages |
| Backend | None | N/A |
| Other pages | None | N/A |