Skip to content

Feat: Google Integration & Auth Flow#730

Open
Ayush8923 wants to merge 20 commits intomainfrom
feat/google-integration-auth-flow
Open

Feat: Google Integration & Auth Flow#730
Ayush8923 wants to merge 20 commits intomainfrom
feat/google-integration-auth-flow

Conversation

@Ayush8923
Copy link
Copy Markdown
Collaborator

@Ayush8923 Ayush8923 commented Mar 31, 2026

Target issue: #731

Summary

  • Added the google-auth library to verifying the google token.
  • Implement the Google Sign-In with cookie-based access & refresh tokens
  • Updated the JWT token handling with organization and project context claims
  • Implement the Project selection endpoints for the multi-project users
  • Logout endpoint that clears session cookies
  • User profile now returns associated projects.

Checklist

Before submitting a pull request, please ensure that you mark these task.

  • Ran fastapi run --reload app/main.py or docker compose up in the repository root and test.
  • If you've fixed a bug or added code that is tested and has test cases.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Refactors JWT auth into a centralized helper, expands get_auth_context to accept Request and prefer API keys, adds cookie-based access/refresh JWT handling, introduces Google OAuth endpoints (/auth/google, /auth/select-project, /auth/refresh, /auth/logout), extends token/user models, updates settings and dependencies, and expands tests.

Changes

Cohort / File(s) Summary
Auth deps & token creation
backend/app/api/deps.py, backend/app/core/security.py
Centralized JWT validation in _authenticate_with_jwt; get_auth_context(request: Request, ...) now checks X-API-KEY first, then Bearer token, then access_token cookie. Adds explicit expired-token (401) vs other validation (403) handling. create_access_token accepts organization_id/project_id; new create_refresh_token.
Google OAuth routes & docs
backend/app/api/routes/google_auth.py, backend/app/api/docs/auth/google.md
New /auth router with POST /auth/google, /auth/select-project, /auth/refresh, /auth/logout. Verifies Google ID token, builds/returns access+refresh JWTs (HTTP-only cookies + response), supports project selection, refresh flow, and logout cookie clearing; docs added.
Models & package exports
backend/app/models/auth.py, backend/app/models/user.py, backend/app/models/__init__.py
TokenPayload gains org_id, project_id, type. Added GoogleAuthRequest, GoogleAuthResponse, SelectProjectRequest. Added UserProjectAccess, UserMeResponse. Package exports updated to include new models.
Config & router registration
backend/app/core/config.py, backend/app/api/main.py
Added REFRESH_TOKEN_EXPIRE_MINUTES and GOOGLE_CLIENT_ID to settings; registered google_auth router in API.
Dependencies
backend/pyproject.toml
Added google-auth>=2.49.1.
Tests
backend/app/tests/api/test_deps.py, backend/app/tests/api/test_permissions.py, backend/app/tests/api/test_google_auth.py, backend/app/tests/core/test_security.py
Updated tests to pass a mocked request (cookies) to get_auth_context; added tests for cookie auth, expired-token handling, refresh-token rejection for API access, org/project claims, Google auth flows (including select-project, refresh, logout), and access/refresh token creation tests. Adjusted some expected error messages.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server
    participant Google as Google Auth
    participant DB as Database
    participant JWT as JWT/Cookie

    Client->>Server: POST /auth/google (id_token)
    Server->>Google: verify_oauth2_token(token, audience=GOOGLE_CLIENT_ID)
    alt invalid / expired / unverified
        Google-->>Server: error
        Server-->>Client: 400/401/403
    else valid
        Google-->>Server: idinfo (email, name, ...)
        Server->>DB: lookup user by email
        alt no user / inactive
            DB-->>Server: not found / inactive
            Server-->>Client: 401/403
        else user active
            Server->>DB: query active org/project via API keys
            DB-->>Server: available projects
            Server->>JWT: create_access_token + create_refresh_token (embed org/project if selected)
            JWT-->>Server: tokens
            Server-->>Client: set cookies, return access_token JSON (or require project selection)
        end
    end
Loading
sequenceDiagram
    participant Client
    participant Server
    participant JWT as JWT Validator
    participant DB as Database

    Client->>Server: POST /auth/refresh (refresh_token cookie)
    Server->>JWT: decode & validate refresh_token
    alt invalid / expired / wrong type
        JWT-->>Server: error
        Server-->>Client: 401
    else valid
        JWT-->>Server: claims (sub, org_id, project_id)
        Server->>DB: verify user exists & active
        alt user missing/inactive
            DB-->>Server: not found / inactive
            Server-->>Client: 403/404
        else valid
            Server->>JWT: create new access & refresh tokens (preserve org/project)
            JWT-->>Server: tokens
            Server-->>Client: set new cookies, return access_token
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

enhancement, ready-for-review

Suggested reviewers

  • Prajna1999
  • AkhileshNegi
  • nishika26

Poem

🐰 I hopped in with tokens and a Google sign,
Cookies snug, refreshing by design.
Select a project, set claims with delight,
Logout clears crumbs in the pale moonlight.
A carrot-coded session — secure and bright! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feat: Google Integration & Auth Flow' accurately summarizes the main changes: implementation of Google Sign-In with authentication flows and related endpoints. It is clear, specific, and directly reflects the primary purpose of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 96.77% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/google-integration-auth-flow

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.

@Ayush8923 Ayush8923 self-assigned this Mar 31, 2026
@Ayush8923 Ayush8923 changed the base branch from feat/adding-query-params-to-config to feat/projects-by-org-and-pagination March 31, 2026 17:23
@Ayush8923 Ayush8923 changed the base branch from feat/projects-by-org-and-pagination to feat/adding-query-params-to-config March 31, 2026 17:24
@Ayush8923 Ayush8923 changed the base branch from feat/adding-query-params-to-config to feat/projects-by-org-and-pagination March 31, 2026 17:25
@Ayush8923 Ayush8923 changed the base branch from feat/projects-by-org-and-pagination to feat/adding-query-params-to-config March 31, 2026 17:26
Base automatically changed from feat/adding-query-params-to-config to main April 1, 2026 10:04
Copy link
Copy Markdown

@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: 5

🧹 Nitpick comments (6)
backend/app/models/auth.py (1)

27-33: Consider using more specific types for dict fields.

The google_profile: dict and available_projects: list[dict] fields lack specific type parameters. Using dict[str, Any] provides better type clarity, or consider defining dedicated schema classes for stronger typing.

Proposed improvement
+from typing import Any
+
 class GoogleAuthResponse(SQLModel):
     access_token: str
     token_type: str = "bearer"
     user: UserPublic
-    google_profile: dict
+    google_profile: dict[str, Any]
     requires_project_selection: bool = False
-    available_projects: list[dict] = []
+    available_projects: list[dict[str, Any]] = []
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/models/auth.py` around lines 27 - 33, The GoogleAuthResponse
model uses untyped dicts; update the type annotations for google_profile and
available_projects to be more specific (e.g., google_profile: dict[str, Any] and
available_projects: list[dict[str, Any]]) or better yet define dedicated
Pydantic/SQLModel schema classes (e.g., GoogleProfile and ProjectInfo) and
replace google_profile and available_projects types with those classes (or
list[ProjectInfo]) to improve type clarity and validation in the
GoogleAuthResponse model.
backend/app/core/config.py (1)

57-58: Consider validating GOOGLE_CLIENT_ID in production environments.

The empty default for GOOGLE_CLIENT_ID could lead to silent failures if Google OAuth is expected to work but the client ID is not configured. Consider adding validation in _enforce_non_default_secrets to warn or error when this is empty in production, similar to how SECRET_KEY and POSTGRES_PASSWORD are validated.

Example validation addition
`@model_validator`(mode="after")
def _enforce_non_default_secrets(self) -> Self:
    self._check_default_secret("SECRET_KEY", self.SECRET_KEY)
    self._check_default_secret("POSTGRES_PASSWORD", self.POSTGRES_PASSWORD)
    self._check_default_secret(
        "FIRST_SUPERUSER_PASSWORD", self.FIRST_SUPERUSER_PASSWORD
    )
    
    # Warn if Google OAuth is not configured in production
    if self.ENVIRONMENT == "production" and not self.GOOGLE_CLIENT_ID:
        warnings.warn(
            "GOOGLE_CLIENT_ID is not set. Google OAuth will not work.",
            stacklevel=1,
        )

    return self
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/core/config.py` around lines 57 - 58, Add validation for
GOOGLE_CLIENT_ID in the model validator _enforce_non_default_secrets: if
ENVIRONMENT == "production" and GOOGLE_CLIENT_ID is empty, call
_check_default_secret-like behavior or emit a warning/error (consistent with how
SECRET_KEY and POSTGRES_PASSWORD are handled) so missing Google OAuth
configuration is surfaced in production; reference the GOOGLE_CLIENT_ID field
and the _enforce_non_default_secrets method when implementing this check.
backend/app/core/security.py (2)

88-88: Use datetime.UTC alias instead of timezone.utc.

Python 3.11+ provides datetime.UTC as a cleaner alias. Since the project requires Python 3.12+, this is the preferred form.

Proposed fix
-    expire = datetime.now(timezone.utc) + expires_delta
+    expire = datetime.now(datetime.UTC) + expires_delta

Also update the imports:

-from datetime import datetime, timedelta, timezone
+from datetime import datetime, timedelta

Also applies to: 115-115

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/core/security.py` at line 88, Replace uses of timezone.utc with
the new datetime.UTC alias: change expressions like "datetime.now(timezone.utc)"
to "datetime.now(datetime.UTC)" (e.g., the "expire" assignments). Update imports
to remove timezone (e.g., remove "timezone" from "from datetime import datetime,
timezone, timedelta" or similar) so only datetime and timedelta are imported;
ensure any other occurrences (both instances noted) are updated to use
datetime.UTC.

70-121: Consider extracting shared token creation logic.

create_access_token and create_refresh_token share nearly identical logic, differing only in the type claim. This duplication could be reduced with a private helper function.

Proposed refactor
def _create_token(
    subject: str | Any,
    expires_delta: timedelta,
    token_type: str,
    organization_id: int | None = None,
    project_id: int | None = None,
) -> str:
    """Internal helper to create JWT tokens."""
    expire = datetime.now(datetime.UTC) + expires_delta
    to_encode: dict[str, Any] = {"exp": expire, "sub": str(subject), "type": token_type}
    if organization_id is not None:
        to_encode["org_id"] = organization_id
    if project_id is not None:
        to_encode["project_id"] = project_id
    return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM)


def create_access_token(
    subject: str | Any,
    expires_delta: timedelta,
    organization_id: int | None = None,
    project_id: int | None = None,
) -> str:
    """Create a JWT access token."""
    return _create_token(subject, expires_delta, "access", organization_id, project_id)


def create_refresh_token(
    subject: str | Any,
    expires_delta: timedelta,
    organization_id: int | None = None,
    project_id: int | None = None,
) -> str:
    """Create a JWT refresh token."""
    return _create_token(subject, expires_delta, "refresh", organization_id, project_id)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/core/security.py` around lines 70 - 121, The two functions
create_access_token and create_refresh_token duplicate token-building logic;
extract the shared behavior into a private helper (e.g., _create_token) that
accepts subject, expires_delta, token_type, organization_id, and project_id,
builds the same to_encode payload (including "exp", "sub", "type", optional
"org_id" and "project_id"), and returns jwt.encode(..., settings.SECRET_KEY,
algorithm=ALGORITHM); then have create_access_token call _create_token(...,
"access", ...) and create_refresh_token call _create_token(..., "refresh", ...).
backend/app/api/docs/auth/google.md (1)

14-16: Documentation may be incomplete regarding refresh token handling.

The documentation mentions setting an access_token cookie, but based on the PR summary, refresh tokens are also managed via HTTP-only cookies. Consider updating to clarify whether both tokens are set as cookies or just the access token.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/docs/auth/google.md` around lines 14 - 16, Update the Google
auth docs to explicitly document refresh token behavior: state that the server
sets both access and refresh tokens as HTTP-only secure cookies (e.g.,
access_token and refresh_token) when issuing tokens, describe when a refresh
token is issued/rotated and its cookie expiration, and note that the response
body returns user details and Google profile but tokens are stored in cookies
for client security; reference the Google auth flow described in this file so
readers know both cookies are used and how to obtain a new access token via the
refresh endpoint.
backend/app/api/deps.py (1)

59-64: Fail closed on unexpected JWT type values.

Anything other than "refresh" is currently accepted here, so a future token type would silently gain API access. It’s safer to allowlist "access" and reject everything else.

🔐 Tighten the token-type check
-    if token_data.type == "refresh":
+    if token_data.type != "access":
         raise HTTPException(
             status_code=status.HTTP_401_UNAUTHORIZED,
-            detail="Refresh tokens cannot be used for API access",
+            detail="Invalid token type for API access",
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/deps.py` around lines 59 - 64, The current check only rejects
token_data.type == "refresh" and lets any other type through; update the logic
in the dependency that checks token_data (e.g., the code handling
token_data.type in deps.py / the get_current_user or similar function) to
explicitly allow only "access" tokens and raise
HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token
type") for any other value; keep the existing refresh-specific message if you
prefer, but ensure any non-"access" type is rejected to fail closed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/api/routes/google_auth.py`:
- Around line 128-137: The response currently puts access_token into the JSON
body even though _set_auth_cookies already sets it as an HttpOnly cookie; remove
access_token from the JSON response payload and only return user/metadata (keep
google_profile, requires_project_selection, available_projects). Update the code
that constructs GoogleAuthResponse (the instantiation here and other occurrences
that build JSONResponse with access_token) to stop including access_token, and
if necessary adjust the GoogleAuthResponse schema so access_token is not
required; still call _set_auth_cookies(response, access_token, refresh_token) to
set the cookie. Ensure any other return sites that mirror this pattern (other
GoogleAuthResponse constructions) are changed consistently.
- Line 182: Replace direct interpolation of raw identifiers in all logger calls
in google_auth.py with masked versions using the existing mask_string utility
and ensure messages are prefixed with the function name in square brackets.
Concretely, change logger.info(f"[google_auth] No account found for email:
{email}") (and the other logger calls that include email, user_id, project_id,
etc.) to logger.info(f"[google_auth] No account found for email:
{mask_string(email)}") and similarly wrap project_id and user identifiers with
mask_string(project_id) or mask_string(user_id); ensure any
logger.error/logger.warning uses the same pattern and import/usage of
mask_string in the file.
- Around line 241-244: The router decorators for the auth endpoints
/select-project, /refresh, and /logout are missing external Swagger
descriptions; update each `@router.post`(...) decorator to include
load_description("auth/<action>.md") like the existing /google decorator (e.g.,
use load_description("auth/select-project.md") for the /select-project route)
and create corresponding markdown files (auth/select-project.md,
auth/refresh.md, auth/logout.md) with the endpoint docs; ensure you use the same
decorator pattern and keep response_model=Token (or the correct model) unchanged
so all operations are consistently documented.
- Around line 327-331: The refresh flow currently reuses org_id/project_id from
token_data when calling _create_token_pair (lines around the refresh branch),
which can extend revoked API-key scoped access; instead, re-query the user's
current project authorizations via _get_user_projects (or the existing
project-checking helper) using the user.id and the token_data (e.g., api_key id)
before minting a new token pair, then pass the re-derived organization_id and
project_id to _create_token_pair (or null them if no access) so refresh cannot
re-grant projects that were revoked.
- Around line 35-61: The functions _create_token_and_response and
_get_user_projects use overly-generic types; add precise type annotations by
changing the _create_token_and_response signature to accept user: User and
replace google_profile: dict and available_projects: list[dict] with concrete
types (define a GoogleProfile TypedDict or Pydantic model and a ProjectEntry
TypedDict/Pydantic model for project objects), update all internal usages to
those types, and import User from your models; also change _get_user_projects
return type from list[dict] to list[ProjectEntry] (or the chosen concrete
project type) and update the comprehension to match the new TypedDict/Model
fields. Ensure the new types are exported/imported where needed and used
consistently across token creation and response construction.

---

Nitpick comments:
In `@backend/app/api/deps.py`:
- Around line 59-64: The current check only rejects token_data.type == "refresh"
and lets any other type through; update the logic in the dependency that checks
token_data (e.g., the code handling token_data.type in deps.py / the
get_current_user or similar function) to explicitly allow only "access" tokens
and raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token type") for any other value; keep the existing
refresh-specific message if you prefer, but ensure any non-"access" type is
rejected to fail closed.

In `@backend/app/api/docs/auth/google.md`:
- Around line 14-16: Update the Google auth docs to explicitly document refresh
token behavior: state that the server sets both access and refresh tokens as
HTTP-only secure cookies (e.g., access_token and refresh_token) when issuing
tokens, describe when a refresh token is issued/rotated and its cookie
expiration, and note that the response body returns user details and Google
profile but tokens are stored in cookies for client security; reference the
Google auth flow described in this file so readers know both cookies are used
and how to obtain a new access token via the refresh endpoint.

In `@backend/app/core/config.py`:
- Around line 57-58: Add validation for GOOGLE_CLIENT_ID in the model validator
_enforce_non_default_secrets: if ENVIRONMENT == "production" and
GOOGLE_CLIENT_ID is empty, call _check_default_secret-like behavior or emit a
warning/error (consistent with how SECRET_KEY and POSTGRES_PASSWORD are handled)
so missing Google OAuth configuration is surfaced in production; reference the
GOOGLE_CLIENT_ID field and the _enforce_non_default_secrets method when
implementing this check.

In `@backend/app/core/security.py`:
- Line 88: Replace uses of timezone.utc with the new datetime.UTC alias: change
expressions like "datetime.now(timezone.utc)" to "datetime.now(datetime.UTC)"
(e.g., the "expire" assignments). Update imports to remove timezone (e.g.,
remove "timezone" from "from datetime import datetime, timezone, timedelta" or
similar) so only datetime and timedelta are imported; ensure any other
occurrences (both instances noted) are updated to use datetime.UTC.
- Around line 70-121: The two functions create_access_token and
create_refresh_token duplicate token-building logic; extract the shared behavior
into a private helper (e.g., _create_token) that accepts subject, expires_delta,
token_type, organization_id, and project_id, builds the same to_encode payload
(including "exp", "sub", "type", optional "org_id" and "project_id"), and
returns jwt.encode(..., settings.SECRET_KEY, algorithm=ALGORITHM); then have
create_access_token call _create_token(..., "access", ...) and
create_refresh_token call _create_token(..., "refresh", ...).

In `@backend/app/models/auth.py`:
- Around line 27-33: The GoogleAuthResponse model uses untyped dicts; update the
type annotations for google_profile and available_projects to be more specific
(e.g., google_profile: dict[str, Any] and available_projects: list[dict[str,
Any]]) or better yet define dedicated Pydantic/SQLModel schema classes (e.g.,
GoogleProfile and ProjectInfo) and replace google_profile and available_projects
types with those classes (or list[ProjectInfo]) to improve type clarity and
validation in the GoogleAuthResponse model.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aad3753a-f63f-4429-8291-11acc69e1a8f

📥 Commits

Reviewing files that changed from the base of the PR and between 7d310ce and 6731b1d.

⛔ Files ignored due to path filters (1)
  • backend/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • backend/app/api/deps.py
  • backend/app/api/docs/auth/google.md
  • backend/app/api/main.py
  • backend/app/api/routes/google_auth.py
  • backend/app/core/config.py
  • backend/app/core/security.py
  • backend/app/models/__init__.py
  • backend/app/models/auth.py
  • backend/app/models/user.py
  • backend/pyproject.toml

Copy link
Copy Markdown

@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

🧹 Nitpick comments (1)
backend/app/tests/api/test_permissions.py (1)

13-17: Use a real Request object instead of bare MagicMock to catch unexpected attribute access.

The _mock_request() helper at line 13-17 creates a bare MagicMock() which silently returns nested mocks for any undefined attribute access. This can mask regressions in get_auth_context if its request-dependent behavior changes. Replace with a real Request object that will fail fast on unexpected attribute access.

Suggested change
-from unittest.mock import MagicMock
+from starlette.requests import Request

-def _mock_request() -> MagicMock:
-    """Create a mock Request object with empty cookies."""
-    request = MagicMock()
-    request.cookies = {}
-    return request
+def _mock_request() -> Request:
+    """Create a minimal Request object with empty cookies."""
+    return Request({"type": "http", "headers": []})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/tests/api/test_permissions.py` around lines 13 - 17, Replace the
bare MagicMock returned by _mock_request with a real Request instance so
unexpected attribute access fails fast; specifically, stop returning MagicMock()
in _mock_request and instead construct a real starlette.requests.Request (or
fastapi.Request) with an empty scope/cookies and pass that into get_auth_context
in tests; update references to _mock_request() used in test_permissions to
ensure tests use this real Request object rather than a MagicMock to catch
regressions in get_auth_context.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/tests/api/test_deps.py`:
- Line 46: The test's docstring in backend/app/tests/api/test_deps.py claims to
validate "invalid API key" but asserts the generic fallback message ("Invalid
Authorization format") produced by backend/app/api/deps.py; either make the test
explicitly assert an API-key-specific failure (e.g., exercise the API-key
validation branch and assert a distinct message like "Invalid API key" or check
the API-key validation function returns the API-key error) or rename/retitle the
test to indicate it verifies the generic fallback behavior (e.g.,
"authentication fails with invalid or missing credentials -> generic fallback"),
and update the assertion and test name/docstring accordingly so the intent
matches the asserted message from deps.py.

---

Nitpick comments:
In `@backend/app/tests/api/test_permissions.py`:
- Around line 13-17: Replace the bare MagicMock returned by _mock_request with a
real Request instance so unexpected attribute access fails fast; specifically,
stop returning MagicMock() in _mock_request and instead construct a real
starlette.requests.Request (or fastapi.Request) with an empty scope/cookies and
pass that into get_auth_context in tests; update references to _mock_request()
used in test_permissions to ensure tests use this real Request object rather
than a MagicMock to catch regressions in get_auth_context.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a4c2dcd6-f849-4c9c-bfb2-4fe6e9aafd79

📥 Commits

Reviewing files that changed from the base of the PR and between 6731b1d and 6bb875c.

📒 Files selected for processing (2)
  • backend/app/tests/api/test_deps.py
  • backend/app/tests/api/test_permissions.py

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 1, 2026

Codecov Report

❌ Patch coverage is 98.62069% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
backend/app/api/routes/google_auth.py 95.57% 5 Missing ⚠️
backend/app/api/deps.py 97.43% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@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

🧹 Nitpick comments (5)
backend/app/tests/core/test_security.py (2)

205-233: Add -> None return type hints to test methods.

Per coding guidelines, all function parameters and return values require type hints. The test methods in TestCreateAccessToken are missing return type annotations.

🔧 Proposed fix
-    def test_creates_valid_jwt(self):
+    def test_creates_valid_jwt(self) -> None:
         """Test that a valid JWT is created."""
         ...

-    def test_includes_org_and_project(self):
+    def test_includes_org_and_project(self) -> None:
         """Test that org_id and project_id are embedded in the token."""
         ...

-    def test_omits_org_and_project_when_none(self):
+    def test_omits_org_and_project_when_none(self) -> None:
         """Test that org_id and project_id are omitted when not provided."""

As per coding guidelines: "Always add type hints to all function parameters and return values in Python code".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/tests/core/test_security.py` around lines 205 - 233, Add explicit
return type hints "-> None" to the three test methods so they follow the
project's typing guidelines: update test_creates_valid_jwt,
test_includes_org_and_project, and test_omits_org_and_project_when_none to
include "-> None" on their definitions (no other behavior changes).

239-268: Add -> None return type hints to test methods.

Same issue in TestCreateRefreshToken - the test methods are missing return type annotations.

🔧 Proposed fix
-    def test_creates_valid_refresh_jwt(self):
+    def test_creates_valid_refresh_jwt(self) -> None:
         """Test that a valid refresh JWT is created."""
         ...

-    def test_includes_org_and_project(self):
+    def test_includes_org_and_project(self) -> None:
         """Test that org_id and project_id are embedded in the refresh token."""
         ...

-    def test_omits_org_and_project_when_none(self):
+    def test_omits_org_and_project_when_none(self) -> None:
         """Test that org_id and project_id are omitted when not provided."""

As per coding guidelines: "Always add type hints to all function parameters and return values in Python code".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/tests/core/test_security.py` around lines 239 - 268, Add explicit
return type hints (-> None) to the test methods in TestCreateRefreshToken:
test_creates_valid_refresh_jwt, test_includes_org_and_project, and
test_omits_org_and_project_when_none. Update each def signature to include "->
None" after the parameter list (e.g., def test_creates_valid_refresh_jwt(self)
-> None:) so they conform to the project's typing guideline; no other logic
changes are needed.
backend/app/tests/api/test_deps.py (1)

207-224: Move timedelta import to file top.

The from datetime import timedelta is imported inline in multiple test methods (Lines 209, 228, 249). Since this is a standard library import used across several tests, it should be at the file top for consistency and to avoid redundant imports.

🔧 Proposed fix

Add at file top (around line 3):

from datetime import timedelta

Then remove the inline imports at lines 209, 228, and 249.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/tests/api/test_deps.py` around lines 207 - 224, Move the inline
"from datetime import timedelta" into the module-level imports and remove the
redundant inline imports in the test functions; specifically add a single "from
datetime import timedelta" at the top of the file and delete the inline imports
inside test_get_auth_context_with_expired_token and the other test functions
that currently re-import timedelta so those tests (e.g.,
test_get_auth_context_with_expired_token) use the top-level timedelta import.
backend/app/tests/api/test_google_auth.py (2)

253-267: Consider verifying org/project claims are actually preserved in the new token.

The test test_refresh_with_org_project creates a refresh token with org/project claims and verifies a 200 response, but doesn't decode the returned access token to confirm the claims were preserved. This would strengthen the test's assertion.

🔧 Proposed enhancement
+import jwt
+from app.core.security import ALGORITHM
+
 def test_refresh_with_org_project(
     self, db: Session, client: TestClient, user_api_key: TestAuthContext
 ):
     """Test refresh preserves org/project claims."""
     refresh_token = create_refresh_token(
         subject=str(user_api_key.user.id),
         expires_delta=timedelta(days=7),
         organization_id=user_api_key.organization.id,
         project_id=user_api_key.project.id,
     )
     client.cookies.set("refresh_token", refresh_token)

     resp = client.post(REFRESH_URL)
     assert resp.status_code == 200
-    assert "access_token" in resp.json()
+    
+    new_access_token = resp.json()["access_token"]
+    payload = jwt.decode(new_access_token, settings.SECRET_KEY, algorithms=[ALGORITHM])
+    assert payload["org_id"] == user_api_key.organization.id
+    assert payload["project_id"] == user_api_key.project.id
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/tests/api/test_google_auth.py` around lines 253 - 267, Update the
test_refresh_with_org_project test to decode the returned access token and
assert the organization/project claims are preserved: after calling
client.post(REFRESH_URL) and confirming status_code==200, extract
resp.json()["access_token"], decode it (using the same JWT secret/algorithm as
create_refresh_token) and assert the decoded payload contains organization_id ==
user_api_key.organization.id and project_id == user_api_key.project.id;
reference the test function name test_refresh_with_org_project and the
create_refresh_token/REFRESH_URL symbols to locate where to add the decoding and
assertions.

34-40: Add -> None return type hints to test methods.

Per coding guidelines, all test methods should have -> None return type annotations. This applies to all test methods in this file.

🔧 Proposed fix for test class signatures
-    def test_google_auth_not_configured(self, mock_settings, client: TestClient):
+    def test_google_auth_not_configured(self, mock_settings, client: TestClient) -> None:

-    def test_google_auth_invalid_token(self, mock_settings, mock_verify, client: TestClient):
+    def test_google_auth_invalid_token(self, mock_settings, mock_verify, client: TestClient) -> None:

Apply similar changes to all test methods in this file.

As per coding guidelines: "Always add type hints to all function parameters and return values in Python code".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/tests/api/test_google_auth.py` around lines 34 - 40, The test
methods (e.g., test_google_auth_not_configured) lack return type annotations;
update each test function in this file to include the return type hint "-> None"
on their definitions (preserve existing decorators like `@patch` and parameter
lists such as mock_settings, client: TestClient) so every test_* function
signature ends with "-> None".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/tests/api/test_google_auth.py`:
- Around line 87-111: The test test_google_auth_activates_inactive_user
conflicts with the google_auth handler which currently rejects inactive users
(raises HTTPException 403 with "Inactive user account"); either update the
handler (in google_auth) to flip the user.is_active to True and persist
(db.commit()/db.refresh()) before the inactive-user check so the request returns
200 and the user becomes active, or change the test to expect a 403 response and
the "Inactive user account" detail instead of asserting activation; reference
the google_auth handler and the test_google_auth_activates_inactive_user test
when making the fix.

---

Nitpick comments:
In `@backend/app/tests/api/test_deps.py`:
- Around line 207-224: Move the inline "from datetime import timedelta" into the
module-level imports and remove the redundant inline imports in the test
functions; specifically add a single "from datetime import timedelta" at the top
of the file and delete the inline imports inside
test_get_auth_context_with_expired_token and the other test functions that
currently re-import timedelta so those tests (e.g.,
test_get_auth_context_with_expired_token) use the top-level timedelta import.

In `@backend/app/tests/api/test_google_auth.py`:
- Around line 253-267: Update the test_refresh_with_org_project test to decode
the returned access token and assert the organization/project claims are
preserved: after calling client.post(REFRESH_URL) and confirming
status_code==200, extract resp.json()["access_token"], decode it (using the same
JWT secret/algorithm as create_refresh_token) and assert the decoded payload
contains organization_id == user_api_key.organization.id and project_id ==
user_api_key.project.id; reference the test function name
test_refresh_with_org_project and the create_refresh_token/REFRESH_URL symbols
to locate where to add the decoding and assertions.
- Around line 34-40: The test methods (e.g., test_google_auth_not_configured)
lack return type annotations; update each test function in this file to include
the return type hint "-> None" on their definitions (preserve existing
decorators like `@patch` and parameter lists such as mock_settings, client:
TestClient) so every test_* function signature ends with "-> None".

In `@backend/app/tests/core/test_security.py`:
- Around line 205-233: Add explicit return type hints "-> None" to the three
test methods so they follow the project's typing guidelines: update
test_creates_valid_jwt, test_includes_org_and_project, and
test_omits_org_and_project_when_none to include "-> None" on their definitions
(no other behavior changes).
- Around line 239-268: Add explicit return type hints (-> None) to the test
methods in TestCreateRefreshToken: test_creates_valid_refresh_jwt,
test_includes_org_and_project, and test_omits_org_and_project_when_none. Update
each def signature to include "-> None" after the parameter list (e.g., def
test_creates_valid_refresh_jwt(self) -> None:) so they conform to the project's
typing guideline; no other logic changes are needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2c9473ab-ec7a-4ed6-9dd6-3cf21db24ef7

📥 Commits

Reviewing files that changed from the base of the PR and between 6bb875c and 3a1c9c8.

📒 Files selected for processing (3)
  • backend/app/tests/api/test_deps.py
  • backend/app/tests/api/test_google_auth.py
  • backend/app/tests/core/test_security.py

Copy link
Copy Markdown

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/tests/api/test_google_auth.py`:
- Around line 250-255: The test calls create_refresh_token with extra keyword
args organization_id and project_id which the function create_refresh_token
(signature create_refresh_token(subject, expires_delta)) does not accept,
causing a TypeError; update the test to only pass the supported parameters
(subject and expires_delta) when invoking create_refresh_token in
test_google_auth.py or extend/create an overload of create_refresh_token to
accept organization_id and project_id if those values are required by downstream
logic—locate the call to create_refresh_token in the test and either remove the
organization_id/project_id arguments or adjust create_refresh_token’s signature
in security.py to accept and handle them.
- Around line 35-286: Tests are missing explicit type hints for parameters and
return types; update each test function (e.g., test_google_auth_invalid_token,
test_google_auth_unverified_email, test_select_project_no_access,
test_refresh_with_access_token_instead, etc.) to add return type "-> None" and
annotate patched mock arguments and fixtures (e.g., mock_settings, mock_verify
-> unittest.mock.Mock or MagicMock; client: TestClient; db: Session;
user_api_key: TestAuthContext; normal_user_token_headers: dict[str, str]) so
every parameter and the return are fully typed per the repo rule.
- Around line 282-286: The test_logout_clears_cookies test only asserts status
and message but doesn't verify cookies are removed; update this test to inspect
the response cookies from client.post(LOGOUT_URL) and assert that the
authentication cookies (e.g., names used by your app cookie keys) are expired or
absent after logout. Specifically, in test_logout_clears_cookies use the
TestClient response to check Set-Cookie headers or response.cookies for the auth
cookie names used by your app and assert their max-age/expires indicate deletion
or that the cookie value is empty/removed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 98c25c97-caf8-4eda-8a09-44a2d7a7bfae

📥 Commits

Reviewing files that changed from the base of the PR and between 3a1c9c8 and d1c9416.

📒 Files selected for processing (1)
  • backend/app/tests/api/test_google_auth.py

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants