Skip to content

Conversation

@KumarADITHYA123
Copy link

@KumarADITHYA123 KumarADITHYA123 commented Feb 5, 2026

Closes #268

📝 Description

This PR fixes a critical Login CSRF / Session Fixation vulnerability (Bug A) in the OAuth flow.
Previously, the state parameter was missing from the Discord-to-GitHub verification process, allowing attackers to theoretically force a victim to link the attacker's GitHub account. This fix implements full RFC 6749 compliance by generating, storing, and strictly validating a cryptographically secure state token.

🔧 Changes Made

  • Secure State Storage: Implemented store_oauth_state in supabase.py to store tokens in Redis with a 5-minute TTL.
  • Atomic Validation: Implemented validate_oauth_state in supabase.py using Lua scripts to ensure tokens are checked and consumed atomically (preventing replay attacks).
  • Updated OAuth Flow: Modified cogs.py (/verify_github and onboarding) to generate and pass the state parameter to GitHub.
  • Strict Callback Validation: Updated auth.py to reject any callback missing the state parameter or containing an invalid one.
  • Logging: Added security-focused logging for validation failures.

📷 Screenshots or Visual Changes (if applicable)

Backend security fix only - no visual UI changes.

✅ Checklist

  • I have read the contributing guidelines.
  • I have added tests that prove my fix is effective (Verified locally with custom test_fix_bug_a.py, plus ruff, mypy, and bandit checks).
  • I have added necessary documentation (Docstrings added to new security functions).
  • Any dependent changes have been merged and published in downstream modules.

Summary by CodeRabbit

  • New Features

    • Enforced OAuth state validation on authentication callbacks
    • Generation, storage, and validation of state tokens to protect OAuth flows
    • OAuth provider redirect URLs now include state for CSRF protection
    • OAuth URL generation integrated into onboarding and provider flows for consistent behavior
  • Bug Fixes

    • More consistent and informative error messages for failed authentication
    • Improved error handling across OAuth provider integrations

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

📝 Walkthrough

Walkthrough

Adds Redis-backed OAuth state generation and validation: new store_oauth_state/validate_oauth_state, state included when creating provider auth URLs in the Discord integration, and the OAuth callback now requires and verifies the state before linking accounts.

Changes

Cohort / File(s) Summary
OAuth State Management
backend/app/services/auth/supabase.py
Added store_oauth_state(session_id: str, ttl: int = 300) -> str and validate_oauth_state(state_token: str, expected_session_id: str) -> bool. Uses secure tokens and Redis with atomic consume (Lua) semantics.
OAuth Callback Validation
backend/app/api/v1/auth.py
auth_callback() signature extended with state: Optional[str] = Query(None) and now requires presence and validation of state via validate_oauth_state(...); returns explicit CSRF/security errors on missing/invalid state.
Discord GitHub OAuth Integration
backend/integrations/discord/cogs.py
Added helper _generate_secure_github_auth_url(...) which calls store_oauth_state and passes the resulting state into login_with_github(...). Updated verify and onboarding flows to handle state generation / auth URL errors and to propagate state to provider URLs.
Imports & Typing / Logging
backend/app/api/v1/auth.py, backend/integrations/discord/cogs.py
Added imports for store_oauth_state/validate_oauth_state, typing updates (e.g., Optional), and adjusted logging to include state-related events.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant DiscordBot as Discord Bot (cogs)
    participant Backend as Auth Service
    participant Redis
    participant Provider as OAuth Provider (GitHub)
    participant Callback as Callback Handler

    User->>DiscordBot: /verify_github (initiate)
    DiscordBot->>Backend: request auth URL with session_id
    Backend->>Redis: store_oauth_state(session_id) -> state_token
    Redis-->>Backend: OK
    Backend->>Provider: redirect_to=callback_url, state=state_token
    Provider-->>User: auth_url
    User->>Provider: authorize
    Provider->>Callback: GET /callback?code=...&state=...
    Callback->>Redis: validate_oauth_state(state, session_id)
    Redis-->>Callback: ok / failed (state consumed)
    Callback->>Backend: on valid state, exchange code and link accounts
    Callback-->>User: success / error response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hid a token, snug and small,
In Redis burrow down the hall.
State in the URL — match just right,
CSRF hops off into the night.
A tiny thump — the flow is tight! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: implement OAuth CSRF protection (Bug A) #268' clearly and specifically describes the main change: implementing OAuth CSRF protection to address a critical security vulnerability.
Linked Issues check ✅ Passed All coding objectives from issue #268 are met: cryptographically secure state generation [validate_oauth_state, store_oauth_state], Redis storage with 5-minute TTL, state parameter passing in Discord integration, and strict validation with atomic consumption in callback handler.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing OAuth CSRF protection as defined in issue #268: state management functions, callback handler updates, and Discord integration modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/app/services/auth/supabase.py (1)

96-105: ⚠️ Potential issue | 🟠 Major

Remove set_session() call; use sign_out() alone for logout.

According to Supabase Python documentation, set_session() requires a real refresh_token value—passing an empty string will fail. For logout, call sign_out() directly; it properly revokes the session and refresh token without needing to set a session first. Remove the set_session() line entirely.

Current code
async def logout(access_token: str):
    """Logs out a user by revoking their session."""
    supabase = get_supabase_client()
    try:
        await supabase.auth.set_session(access_token, refresh_token="")  # nosec
        await supabase.auth.sign_out()
        return {"message": "User logged out successfully"}
    except Exception as e:
        logger.error(f"Logout failed: {e}", exc_info=True)
        raise
🧹 Nitpick comments (4)
backend/integrations/discord/cogs.py (4)

149-166: Move the import to module level to avoid repeated import overhead and improve readability.

The inline import of store_oauth_state inside the function body works but is unconventional. Since login_with_github is already imported at module level from the same module, store_oauth_state should be imported alongside it.

♻️ Proposed fix

At line 17, update the import:

-from app.services.auth.supabase import login_with_github
+from app.services.auth.supabase import login_with_github, store_oauth_state

Then remove the inline import at line 150:

             # SECURITY FIX: Generate and store OAuth state parameter
-            from app.services.auth.supabase import store_oauth_state
             try:
                 state_token = await store_oauth_state(session_id, ttl=300)

153-161: Use logger.exception for automatic stack trace inclusion.

Per static analysis hint TRY400, logger.exception automatically includes the exception info, making the code cleaner and ensuring consistent stack trace logging.

♻️ Proposed fix
             except Exception as e:
-                logger.error(f"Failed to generate OAuth state: {e}")
+                logger.exception("Failed to generate OAuth state")
                 error_embed = discord.Embed(

518-536: Duplicate OAuth state generation logic - consider extracting a helper.

This block is nearly identical to lines 149-161 in verify_github. Both generate state, handle errors similarly, and pass state to login_with_github. A private helper method would reduce duplication and ensure consistent behavior.

♻️ Optional: Extract helper method
async def _generate_oauth_url_with_state(
    self, 
    session_id: str, 
    callback_url: str
) -> tuple[str | None, str | None]:
    """
    Generate OAuth URL with CSRF state protection.
    Returns (auth_url, error_message) tuple.
    """
    try:
        state_token = await store_oauth_state(session_id, ttl=300)
    except Exception:
        logger.exception("Failed to generate OAuth state")
        return None, "Verification service is temporarily unavailable. Please try again."
    
    auth = await login_with_github(redirect_to=callback_url, state=state_token)
    auth_url = auth.get("url")
    if not auth_url:
        return None, "Couldn't generate a verification link. Please use /verify_github."
    
    return auth_url, None

This helper can then be used in both verify_github and _send_onboarding_flow.


523-534: Use logger.exception and remove redundant exception argument.

Static analysis correctly identifies that logging.exception already includes the exception, making {send_error} redundant at line 533.

♻️ Proposed fix
                 except Exception as e:
-                    logger.error(f"Failed to generate OAuth state in onboarding: {e}")
+                    logger.exception("Failed to generate OAuth state in onboarding")
                     try:
                         ...
                     except Exception as send_error:
-                        logger.exception(
-                            f"Failed to send auth state failure fallback DM to user {user.id}: {send_error}")
+                        logger.exception(
+                            f"Failed to send auth state failure fallback DM to user {user.id}")

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@backend/integrations/discord/cogs.py`:
- Around line 534-548: The except block is catching Exception as send_error but
never uses send_error, causing Ruff F841; update the fallback error handling in
the verification flow (the block around _generate_secure_github_auth_url and the
user.send calls) to remove the unused variable by changing "except Exception as
send_error:" to "except Exception:" (keep the logger.exception call unchanged so
the exception is still logged), ensuring the rest of the logic (sending fallback
DMs with build_encourage_verification_message and build_final_handoff_embed)
remains the same.
🧹 Nitpick comments (1)
backend/integrations/discord/cogs.py (1)

26-44: Handle OAuth URL generation failures inside the helper.

If login_with_github raises or returns a falsy payload, the caller falls back to generic exception handling. Wrapping it here keeps error messaging consistent and prevents leaking stack traces into higher-level flows.

♻️ Suggested update
-    # Pass state parameter to OAuth flow (RFC 6749 compliant)
-    auth = await login_with_github(redirect_to=callback_url, state=state_token)
-    auth_url = auth.get("url")
+    # Pass state parameter to OAuth flow (RFC 6749 compliant)
+    try:
+        auth = await login_with_github(redirect_to=callback_url, state=state_token)
+    except Exception:
+        logger.exception("Failed to generate GitHub OAuth URL")
+        return None, "Couldn't generate a verification link. Please use /verify_github."
+
+    auth_url = auth.get("url") if auth else None
     if not auth_url:
         return None, "Couldn't generate a verification link. Please use /verify_github."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: [Security] Critical: Login CSRF vulnerability in OAuth flow (Missing 'state' parameter)

1 participant