feat: Implement JWT refresh token endpoint and update OAuth routing

This commit is contained in:
mohamad 2025-06-09 13:06:01 +02:00
parent 3ec2ff1f89
commit dccd7bb300
5 changed files with 44 additions and 6 deletions

View File

@ -1,11 +1,12 @@
from fastapi import APIRouter, Depends, Request
from fastapi.responses import RedirectResponse
from fastapi import APIRouter, Depends, Request, HTTPException, status
from fastapi.responses import RedirectResponse, JSONResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.database import get_transactional_session
from app.models import User
from app.auth import oauth, fastapi_users, auth_backend, get_jwt_strategy, get_refresh_jwt_strategy
from app.config import settings
from fastapi.security import OAuth2PasswordRequestForm
router = APIRouter()
@ -92,4 +93,30 @@ async def apple_callback(request: Request, db: AsyncSession = Depends(get_transa
# Redirect to frontend with tokens
redirect_url = f"{settings.FRONTEND_URL}/auth/callback?access_token={access_token}&refresh_token={refresh_token}"
return RedirectResponse(url=redirect_url)
return RedirectResponse(url=redirect_url)
@router.post('/jwt/refresh')
async def refresh_jwt_token(request: Request):
data = await request.json()
refresh_token = data.get('refresh_token')
if not refresh_token:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Missing refresh token")
refresh_strategy = get_refresh_jwt_strategy()
try:
user = await refresh_strategy.read_token(refresh_token, None)
except Exception:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token")
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token")
access_strategy = get_jwt_strategy()
access_token = await access_strategy.write_token(user)
# Optionally, issue a new refresh token (rotation)
new_refresh_token = await refresh_strategy.write_token(user)
return JSONResponse({
"access_token": access_token,
"refresh_token": new_refresh_token,
"token_type": "bearer"
})

View File

@ -9,6 +9,7 @@ from app.api.v1.endpoints import ocr
from app.api.v1.endpoints import costs
from app.api.v1.endpoints import financials
from app.api.v1.endpoints import chores
from app.api.auth import oauth
api_router_v1 = APIRouter()
@ -21,5 +22,6 @@ api_router_v1.include_router(ocr.router, prefix="/ocr", tags=["OCR"])
api_router_v1.include_router(costs.router, prefix="/costs", tags=["Costs"])
api_router_v1.include_router(financials.router, prefix="/financials", tags=["Financials"])
api_router_v1.include_router(chores.router, prefix="/chores", tags=["Chores"])
api_router_v1.include_router(oauth.router, prefix="/auth", tags=["Auth"])
# Add other v1 endpoint routers here later
# e.g., api_router_v1.include_router(users.router, prefix="/users", tags=["Users"])

8
fe/package-lock.json generated
View File

@ -34,6 +34,7 @@
"@types/date-fns": "^2.5.3",
"@types/jsdom": "^21.1.7",
"@types/node": "^22.15.17",
"@types/qs": "^6.14.0",
"@vitejs/plugin-vue": "^5.2.3",
"@vitest/eslint-plugin": "^1.1.39",
"@vue/eslint-config-prettier": "^10.2.0",
@ -4291,6 +4292,13 @@
"integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==",
"license": "MIT"
},
"node_modules/@types/qs": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "19.1.6",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz",

View File

@ -45,6 +45,7 @@
"@types/date-fns": "^2.5.3",
"@types/jsdom": "^21.1.7",
"@types/node": "^22.15.17",
"@types/qs": "^6.14.0",
"@vitejs/plugin-vue": "^5.2.3",
"@vitest/eslint-plugin": "^1.1.39",
"@vue/eslint-config-prettier": "^10.2.0",

View File

@ -58,10 +58,10 @@ api.interceptors.response.use(
return Promise.reject(error)
}
// Send refresh token in Authorization header as expected by backend
const response = await api.post(API_ENDPOINTS.AUTH.REFRESH, {}, {
// Send refresh token in request body as expected by backend
const response = await api.post(API_ENDPOINTS.AUTH.REFRESH, { refresh_token: refreshTokenValue }, {
headers: {
Authorization: `Bearer ${refreshTokenValue}`
'Content-Type': 'application/json',
}
})