| """ |
| GitHub OAuth routes with PKCE support for LandPPT |
| """ |
|
|
| from fastapi import APIRouter, Request, Depends |
| from fastapi.responses import RedirectResponse |
| from sqlalchemy.orm import Session |
| from typing import Optional |
| from urllib.parse import quote |
| import logging |
|
|
| from .auth_service import get_auth_service, AuthService |
| from .middleware import get_current_user |
| from ..database.database import get_db |
|
|
| logger = logging.getLogger(__name__) |
|
|
| router = APIRouter() |
|
|
|
|
| @router.get("/auth/github/login") |
| async def github_login( |
| request: Request, |
| redirect_url: str = "/dashboard", |
| invite_code: Optional[str] = None, |
| ): |
| """Initiate GitHub OAuth login with PKCE""" |
| logger.info("=== GitHub OAuth login route called ===") |
| |
| from .github_oauth_service import ( |
| is_github_oauth_enabled, |
| generate_pkce_pair, |
| generate_state, |
| store_oauth_state, |
| build_authorization_url, |
| get_callback_url, |
| ) |
| |
| |
| user = get_current_user(request) |
| if user: |
| logger.info("User already logged in, redirecting to dashboard") |
| return RedirectResponse(url="/dashboard", status_code=302) |
| |
| |
| enabled = is_github_oauth_enabled() |
| logger.info(f"GitHub OAuth enabled: {enabled}") |
| if not enabled: |
| logger.warning("GitHub OAuth not enabled - check config") |
| return RedirectResponse( |
| url="/auth/login?error=GitHub登录未启用", |
| status_code=302 |
| ) |
| |
| |
| code_verifier, code_challenge = generate_pkce_pair() |
| |
| |
| state = generate_state() |
| callback_url = get_callback_url(request) |
| |
| |
| await store_oauth_state( |
| state, |
| code_verifier, |
| redirect_url, |
| callback_url=callback_url, |
| invite_code=invite_code, |
| ) |
| |
| |
| auth_url = build_authorization_url(state, code_challenge, callback_url=callback_url) |
| |
| logger.info(f"Redirecting to GitHub OAuth: state={state[:8]}..., callback_url={callback_url}") |
| return RedirectResponse(url=auth_url, status_code=302) |
|
|
|
|
| @router.get("/auth/github/callback") |
| async def github_callback( |
| request: Request, |
| code: Optional[str] = None, |
| state: Optional[str] = None, |
| error: Optional[str] = None, |
| error_description: Optional[str] = None, |
| db: Session = Depends(get_db), |
| auth_service: AuthService = Depends(get_auth_service) |
| ): |
| """Handle GitHub OAuth callback with PKCE verification""" |
| from .github_oauth_service import ( |
| is_github_oauth_enabled, |
| get_and_consume_oauth_state, |
| exchange_code_for_token, |
| get_github_user_info, |
| get_or_create_user_by_github, |
| get_callback_url, |
| ) |
| |
| |
| if error: |
| error_msg = error_description or error |
| logger.warning(f"GitHub OAuth error: {error_msg}") |
| return RedirectResponse( |
| url=f"/auth/login?error=GitHub授权失败: {error_msg}", |
| status_code=302 |
| ) |
| |
| |
| if not code or not state: |
| return RedirectResponse( |
| url="/auth/login?error=GitHub授权参数缺失", |
| status_code=302 |
| ) |
| |
| |
| if not is_github_oauth_enabled(): |
| return RedirectResponse( |
| url="/auth/login?error=GitHub登录未启用", |
| status_code=302 |
| ) |
| |
| |
| state_data = await get_and_consume_oauth_state(state) |
| if not state_data: |
| logger.warning(f"Invalid or expired OAuth state: {state[:8]}...") |
| return RedirectResponse( |
| url="/auth/login?error=授权请求已过期,请重试", |
| status_code=302 |
| ) |
| |
| code_verifier = state_data.get("code_verifier") |
| redirect_url = state_data.get("redirect_url", "/dashboard") |
| callback_url = state_data.get("callback_url") or get_callback_url(request) |
| invite_code = state_data.get("invite_code") |
| |
| |
| access_token = await exchange_code_for_token(code, code_verifier, callback_url=callback_url) |
| if not access_token: |
| return RedirectResponse( |
| url="/auth/login?error=获取GitHub访问令牌失败", |
| status_code=302 |
| ) |
| |
| |
| github_user = await get_github_user_info(access_token) |
| if not github_user: |
| return RedirectResponse( |
| url="/auth/login?error=获取GitHub用户信息失败", |
| status_code=302 |
| ) |
| |
| |
| user, _, error_message = get_or_create_user_by_github( |
| db=db, |
| github_id=github_user["id"], |
| github_login=github_user["login"], |
| email=github_user.get("email"), |
| name=github_user.get("name"), |
| avatar_url=github_user.get("avatar_url"), |
| invite_code=invite_code, |
| ) |
| |
| if not user: |
| return RedirectResponse( |
| url=( |
| f"/auth/login?tab=register®ister_error={quote(error_message or '用户创建失败')}" |
| f"®ister_invite_code={quote(invite_code or '')}" |
| ), |
| status_code=302 |
| ) |
| |
| |
| session_id = auth_service.create_session(db, user) |
| |
| |
| response = RedirectResponse(url=redirect_url, status_code=302) |
| |
| |
| current_expire_minutes = auth_service._get_current_expire_minutes() |
| cookie_max_age = None if current_expire_minutes == 0 else current_expire_minutes * 60 |
| |
| response.set_cookie( |
| key="session_id", |
| value=session_id, |
| max_age=cookie_max_age, |
| httponly=True, |
| secure=False, |
| samesite="lax" |
| ) |
| |
| logger.info(f"User {user.username} logged in via GitHub OAuth (github_id={github_user['id']})") |
| return response |
|
|