
- Added support for refresh tokens in the authentication backend, allowing users to obtain new access tokens using valid refresh tokens. - Created a new `BearerResponseWithRefresh` model to structure responses containing both access and refresh tokens. - Updated the `AuthenticationBackend` to handle login and logout processes with refresh token support. - Introduced a new `/auth/jwt/refresh` endpoint to facilitate token refreshing, validating the refresh token and generating new tokens as needed. - Modified OAuth callback logic to generate and return both access and refresh tokens upon successful authentication. - Updated frontend API service to send the refresh token in the Authorization header for token refresh requests.
95 lines
4.0 KiB
Python
95 lines
4.0 KiB
Python
from fastapi import APIRouter, Depends, Request
|
|
from fastapi.responses import RedirectResponse
|
|
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
|
|
|
|
router = APIRouter()
|
|
|
|
@router.get('/google/login')
|
|
async def google_login(request: Request):
|
|
return await oauth.google.authorize_redirect(request, settings.GOOGLE_REDIRECT_URI)
|
|
|
|
@router.get('/google/callback')
|
|
async def google_callback(request: Request, db: AsyncSession = Depends(get_transactional_session)):
|
|
token_data = await oauth.google.authorize_access_token(request)
|
|
user_info = await oauth.google.parse_id_token(request, token_data)
|
|
|
|
# Check if user exists
|
|
existing_user = (await db.execute(select(User).where(User.email == user_info['email']))).scalar_one_or_none()
|
|
|
|
user_to_login = existing_user
|
|
if not existing_user:
|
|
# Create new user
|
|
new_user = User(
|
|
email=user_info['email'],
|
|
name=user_info.get('name', user_info.get('email')),
|
|
is_verified=True, # Email is verified by Google
|
|
is_active=True
|
|
)
|
|
db.add(new_user)
|
|
await db.flush() # Use flush instead of commit since we're in a transaction
|
|
user_to_login = new_user
|
|
|
|
# Generate JWT tokens using the new backend
|
|
access_strategy = get_jwt_strategy()
|
|
refresh_strategy = get_refresh_jwt_strategy()
|
|
|
|
access_token = await access_strategy.write_token(user_to_login)
|
|
refresh_token = await refresh_strategy.write_token(user_to_login)
|
|
|
|
# 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)
|
|
|
|
@router.get('/apple/login')
|
|
async def apple_login(request: Request):
|
|
return await oauth.apple.authorize_redirect(request, settings.APPLE_REDIRECT_URI)
|
|
|
|
@router.get('/apple/callback')
|
|
async def apple_callback(request: Request, db: AsyncSession = Depends(get_transactional_session)):
|
|
token_data = await oauth.apple.authorize_access_token(request)
|
|
user_info = token_data.get('user', await oauth.apple.userinfo(token=token_data) if hasattr(oauth.apple, 'userinfo') else {})
|
|
if 'email' not in user_info and 'sub' in token_data:
|
|
parsed_id_token = await oauth.apple.parse_id_token(request, token_data) if hasattr(oauth.apple, 'parse_id_token') else {}
|
|
user_info = {**parsed_id_token, **user_info}
|
|
|
|
if 'email' not in user_info:
|
|
return RedirectResponse(url=f"{settings.FRONTEND_URL}/auth/callback?error=apple_email_missing")
|
|
|
|
# Check if user exists
|
|
existing_user = (await db.execute(select(User).where(User.email == user_info['email']))).scalar_one_or_none()
|
|
|
|
user_to_login = existing_user
|
|
if not existing_user:
|
|
# Create new user
|
|
name_info = user_info.get('name', {})
|
|
first_name = name_info.get('firstName', '')
|
|
last_name = name_info.get('lastName', '')
|
|
full_name = f"{first_name} {last_name}".strip() if first_name or last_name else user_info.get('email')
|
|
|
|
new_user = User(
|
|
email=user_info['email'],
|
|
name=full_name,
|
|
is_verified=True, # Email is verified by Apple
|
|
is_active=True
|
|
)
|
|
db.add(new_user)
|
|
await db.flush() # Use flush instead of commit since we're in a transaction
|
|
user_to_login = new_user
|
|
|
|
# Generate JWT tokens using the new backend
|
|
access_strategy = get_jwt_strategy()
|
|
refresh_strategy = get_refresh_jwt_strategy()
|
|
|
|
access_token = await access_strategy.write_token(user_to_login)
|
|
refresh_token = await refresh_strategy.write_token(user_to_login)
|
|
|
|
# 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) |