feat: Implement JWT refresh token endpoint and update OAuth routing
This commit is contained in:
parent
3ec2ff1f89
commit
dccd7bb300
@ -1,11 +1,12 @@
|
|||||||
from fastapi import APIRouter, Depends, Request
|
from fastapi import APIRouter, Depends, Request, HTTPException, status
|
||||||
from fastapi.responses import RedirectResponse
|
from fastapi.responses import RedirectResponse, JSONResponse
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from app.database import get_transactional_session
|
from app.database import get_transactional_session
|
||||||
from app.models import User
|
from app.models import User
|
||||||
from app.auth import oauth, fastapi_users, auth_backend, get_jwt_strategy, get_refresh_jwt_strategy
|
from app.auth import oauth, fastapi_users, auth_backend, get_jwt_strategy, get_refresh_jwt_strategy
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
@ -93,3 +94,29 @@ async def apple_callback(request: Request, db: AsyncSession = Depends(get_transa
|
|||||||
redirect_url = f"{settings.FRONTEND_URL}/auth/callback?access_token={access_token}&refresh_token={refresh_token}"
|
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"
|
||||||
|
})
|
@ -9,6 +9,7 @@ from app.api.v1.endpoints import ocr
|
|||||||
from app.api.v1.endpoints import costs
|
from app.api.v1.endpoints import costs
|
||||||
from app.api.v1.endpoints import financials
|
from app.api.v1.endpoints import financials
|
||||||
from app.api.v1.endpoints import chores
|
from app.api.v1.endpoints import chores
|
||||||
|
from app.api.auth import oauth
|
||||||
|
|
||||||
api_router_v1 = APIRouter()
|
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(costs.router, prefix="/costs", tags=["Costs"])
|
||||||
api_router_v1.include_router(financials.router, prefix="/financials", tags=["Financials"])
|
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(chores.router, prefix="/chores", tags=["Chores"])
|
||||||
|
api_router_v1.include_router(oauth.router, prefix="/auth", tags=["Auth"])
|
||||||
# Add other v1 endpoint routers here later
|
# Add other v1 endpoint routers here later
|
||||||
# e.g., api_router_v1.include_router(users.router, prefix="/users", tags=["Users"])
|
# e.g., api_router_v1.include_router(users.router, prefix="/users", tags=["Users"])
|
8
fe/package-lock.json
generated
8
fe/package-lock.json
generated
@ -34,6 +34,7 @@
|
|||||||
"@types/date-fns": "^2.5.3",
|
"@types/date-fns": "^2.5.3",
|
||||||
"@types/jsdom": "^21.1.7",
|
"@types/jsdom": "^21.1.7",
|
||||||
"@types/node": "^22.15.17",
|
"@types/node": "^22.15.17",
|
||||||
|
"@types/qs": "^6.14.0",
|
||||||
"@vitejs/plugin-vue": "^5.2.3",
|
"@vitejs/plugin-vue": "^5.2.3",
|
||||||
"@vitest/eslint-plugin": "^1.1.39",
|
"@vitest/eslint-plugin": "^1.1.39",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
@ -4291,6 +4292,13 @@
|
|||||||
"integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==",
|
"integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.1.6",
|
"version": "19.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz",
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
"@types/date-fns": "^2.5.3",
|
"@types/date-fns": "^2.5.3",
|
||||||
"@types/jsdom": "^21.1.7",
|
"@types/jsdom": "^21.1.7",
|
||||||
"@types/node": "^22.15.17",
|
"@types/node": "^22.15.17",
|
||||||
|
"@types/qs": "^6.14.0",
|
||||||
"@vitejs/plugin-vue": "^5.2.3",
|
"@vitejs/plugin-vue": "^5.2.3",
|
||||||
"@vitest/eslint-plugin": "^1.1.39",
|
"@vitest/eslint-plugin": "^1.1.39",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
|
@ -58,10 +58,10 @@ api.interceptors.response.use(
|
|||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send refresh token in Authorization header as expected by backend
|
// Send refresh token in request body as expected by backend
|
||||||
const response = await api.post(API_ENDPOINTS.AUTH.REFRESH, {}, {
|
const response = await api.post(API_ENDPOINTS.AUTH.REFRESH, { refresh_token: refreshTokenValue }, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${refreshTokenValue}`
|
'Content-Type': 'application/json',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user