diff --git a/navi/api/routes/auth.py b/navi/api/routes/auth.py index 9d1d1f6..2c67280 100644 --- a/navi/api/routes/auth.py +++ b/navi/api/routes/auth.py @@ -100,11 +100,35 @@ @router.get("/callback") -async def auth_callback(code: str, state: str, request: Request) -> Response: +async def auth_callback( + request: Request, + code: str | None = None, + state: str | None = None, + error: str | None = None, + error_description: str | None = None, +) -> Response: """Handle OAuth callback from gnexus-auth.""" if not _auth_configured(): raise HTTPException(status_code=503, detail="OAuth is not configured. Set GNAUTH_CLIENT_ID and GNAUTH_CLIENT_SECRET in .env") + # OAuth error response from the authorization server (e.g. user denied consent, + # or the requested scope is not allowed for this client). + if error: + log.warning( + "auth.oauth_error", + error=error, + error_description=error_description, + state=state[:8] if state else None, + ) + raise HTTPException( + status_code=400, + detail=f"OAuth authorization failed: {error_description or error}", + ) + + if not code: + log.warning("auth.missing_code", query=str(request.query_params)) + raise HTTPException(status_code=400, detail="Missing authorization code") + redirect_uri = _get_redirect_uri() client = get_gauth_client(redirect_uri=redirect_uri) encryptor = get_encryptor() @@ -113,7 +137,7 @@ try: token_set = await asyncio.to_thread(client.exchange_authorization_code, code, state) except (StateValidationException, PkceException) as e: - log.warning("auth.invalid_state", state=state[:8], error=str(e)) + log.warning("auth.invalid_state", state=state[:8] if state else None, error=str(e)) raise HTTPException(status_code=400, detail="Invalid or expired state") from e except TokenExchangeException as e: log.warning("auth.token_exchange_failed", error=str(e)) @@ -203,7 +227,7 @@ log.warning("auth.create_session_failed", user_id=auth_user.user_id, exc_info=True) # Retrieve platform/return_to info before state is forgotten - state_info = _mobile_auth_states.pop(state, {}) + state_info = _mobile_auth_states.pop(state, {}) if state else {} is_mobile = state_info.get("platform") == "android" return_to = state_info.get("return_to", "/")