Skip to content

fix team invites#993

Merged
BilalG1 merged 7 commits intodevfrom
fix-team-invites
Nov 5, 2025
Merged

fix team invites#993
BilalG1 merged 7 commits intodevfrom
fix-team-invites

Conversation

@BilalG1
Copy link
Contributor

@BilalG1 BilalG1 commented Oct 31, 2025

Summary by CodeRabbit

  • Refactor

    • Invitation flow now derives the invitation link from a provided origin rather than accepting a full callback URL.
  • Bug Fixes / Security

    • Enforced origin whitelist for invitation redirects to prevent untrusted callback URLs.
  • Tests

    • Added a test ensuring untrusted callback URLs are rejected with a proper error response.

@vercel
Copy link

vercel bot commented Oct 31, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
stack-backend Ready Ready Preview Comment Nov 5, 2025 8:43pm
stack-dashboard Ready Ready Preview Comment Nov 5, 2025 8:43pm
stack-demo Ready Ready Preview Comment Nov 5, 2025 8:43pm
stack-docs Ready Ready Preview Comment Nov 5, 2025 8:43pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 31, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

inviteUser now accepts an origin, validates it against trusted team domains (including localhost and pattern matching), constructs callbackUrl from the validated origin and stackServerApp.urls.teamInvitation, and sends the invitation. An e2e test was added to assert untrusted callback URLs return 400 with REDIRECT_URL_NOT_WHITELISTED.

Changes

Cohort / File(s) Summary
Invite flow / origin validation
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts
Replaced callbackUrl parameter with origin; added origin validation (hostname pattern matching, localhost allowance) and constructs callbackUrl via new URL(stackServerApp.urls.teamInvitation, origin).toString() before calling team.inviteUser. Adjusted imports and internal flow accordingly.
End-to-end test for redirect whitelist
apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts
Added a test asserting that sending an untrusted callback_url to the team invitation endpoint returns HTTP 400 with error code REDIRECT_URL_NOT_WHITELISTED and includes the stacked error header.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant DashboardActions as Dashboard Actions
  participant InviteAPI as Team Invite Service
  note right of DashboardActions `#DFF2E1`: validate origin -> build callbackUrl
  Client->>DashboardActions: inviteUser(teamId, email, origin)
  DashboardActions->>DashboardActions: assertTrustedOrigin(teamId, origin)\n(hostname pattern / localhost checks)
  alt origin trusted
    DashboardActions->>InviteAPI: POST /invite { email, callbackUrl }
    InviteAPI-->>DashboardActions: 200 OK
    DashboardActions-->>Client: success
  else origin untrusted
    DashboardActions-->>Client: throw validation error (REDIRECT_URL_NOT_WHITELISTED)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to origin validation edge cases (IDNs, trailing dots, ports, percent-encoding).
  • Verify correctness of new URL(..., origin) usage and that constructed callback URLs cannot be abused.
  • Review the new e2e test for robustness and matching error header semantics.

Possibly related PRs

Poem

🐰
I hopped along the origin trail,
Checked each hostname, sniffed each mail.
Built the link from root to door,
Hopped an invite — safe once more. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description contains only the template comment reminding contributors to read CONTRIBUTING.md, with no actual description of the changes, rationale, or implementation details. Add a detailed description explaining the purpose of the changes, the problem being solved, and why the parameter refactoring and URL validation improvements were necessary.
Title check ❓ Inconclusive The title 'fix team invites' is vague and generic, using non-descriptive language that doesn't convey the specific technical change (refactoring origin parameter handling and URL validation). Provide a more specific title that describes the actual change, such as 'Refactor team invite parameter from callbackUrl to origin' or 'Add redirect URL whitelist validation for team invitations'.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-team-invites

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

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

Changed inviteUser function to accept origin parameter instead of full callbackUrl, constructing the callback URL internally using new URL(stackServerApp.urls.teamInvitation, origin). This centralizes URL construction logic.

Key changes:

  • Parameter change from callbackUrl: string to origin: string on line 32
  • Added URL construction: new URL(stackServerApp.urls.teamInvitation, origin).toString() on line 33
  • Caller in page-client.tsx:200 now passes window.location.origin instead of full URL

Issues found:

  • Missing origin validation creates security vulnerability - the origin parameter is directly used to construct callback URLs without validation against trusted domains, which could allow attackers to redirect invitation flows to malicious sites

Confidence Score: 2/5

  • This PR has a critical security issue - missing origin validation could allow redirect attacks
  • Score reflects the security vulnerability introduced by accepting untrusted origin without validation. While the refactoring improves code organization, the lack of origin validation against trusted domains violates the custom security rules and creates a potential attack vector for phishing or redirect attacks in the team invitation flow
  • Pay close attention to apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts - requires origin validation

Important Files Changed

File Analysis

Filename Score Overview
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts 2/5 Changed parameter from callbackUrl to origin for constructing invitation URLs, but missing origin validation which creates security risk

Sequence Diagram

sequenceDiagram
    participant User as User (Browser)
    participant PageClient as page-client.tsx
    participant Actions as actions.ts
    participant StackServer as stackServerApp
    participant Team as Team Service
    participant Email as Email Service

    User->>PageClient: Click "Invite" button
    PageClient->>PageClient: Get window.location.origin
    PageClient->>Actions: inviteUser(teamId, email, origin)
    Actions->>Actions: Construct callbackUrl from origin
    Note over Actions: callbackUrl = new URL(stackServerApp.urls.teamInvitation, origin)
    Actions->>StackServer: getUser()
    StackServer-->>Actions: Return user
    Actions->>StackServer: user.getTeam(teamId)
    StackServer-->>Actions: Return team
    Actions->>Team: team.inviteUser({ email, callbackUrl })
    Team->>Email: Send invitation email with callbackUrl
    Email-->>User: Team invitation email
    User->>User: Click invitation link
    User->>PageClient: Navigate to callbackUrl
Loading

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Copy link
Contributor

@N2D4 N2D4 left a comment

Choose a reason for hiding this comment

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

just one comment!

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: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between efbb81c and ac7bf4c.

📒 Files selected for processing (1)
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts (1)
packages/stack-shared/src/utils/urls.tsx (3)
  • createUrlIfValid (4-10)
  • isLocalhost (199-205)
  • matchHostnamePattern (144-173)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: restart-dev-and-test
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: setup-tests
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: all-good
  • GitHub Check: build (22.x)
🔇 Additional comments (3)
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts (3)

3-4: LGTM! Appropriate utility imports for URL validation.

The imported functions (createUrlIfValid, isLocalhost, matchHostnamePattern) are correctly sourced and will be used for the origin validation logic.


16-18: Localhost validation relies on the flawed fallback logic.

While the localhost check itself is reasonable, it operates on projectsToCheck which may include unrelated projects due to the fallback on line 14. Once that critical issue is resolved, verify whether allowing localhost if any team project allows it is the intended behavior, or if stricter per-project validation is required.


79-88: Addresses security concern from previous review, but depends on fixing critical issue.

This refactor correctly implements origin validation before constructing the callback URL, addressing the security concern raised in the previous review. The validated origin is used as the base for constructing the callback URL on line 81.

However, the security effectiveness depends on resolving the critical fallback issue in assertTrustedOrigin (lines 13-14). Once that's fixed, this implementation will properly prevent invitations with malicious callback URLs.

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

🧹 Nitpick comments (1)
apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts (1)

529-557: LGTM! Good security test coverage.

This test correctly validates that untrusted callback URLs are rejected with the appropriate error response. The test structure follows existing patterns and properly exercises server-side whitelist validation.

Minor optional optimization: receiveMailbox (line 531) is created but never used since the request fails early. You could remove it for efficiency, though keeping it maintains consistency with the test pattern at lines 107-143.

 it("should error when untrusted callback URL is provided", async ({ expect }) => {
   const { teamId } = await Team.create();
-  const receiveMailbox = createMailbox();
 
   backendContext.set({ userAuth: null });
   const sendTeamInvitationResponse = await niceBackendFetch("/api/v1/team-invitations/send-code", {
     method: "POST",
     accessType: "server",
     body: {
-      email: receiveMailbox.emailAddress,
+      email: "test@example.com",
       team_id: teamId,
       callback_url: "https://malicious.com/callback",
     },
   });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac7bf4c and b3d6a93.

📒 Files selected for processing (2)
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts
🧰 Additional context used
🧬 Code graph analysis (1)
apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts (1)
apps/e2e/tests/backend/backend-helpers.ts (3)
  • createMailbox (59-66)
  • backendContext (35-57)
  • niceBackendFetch (109-173)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: all-good
  • GitHub Check: restart-dev-and-test
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: docker
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)

@BilalG1 BilalG1 assigned N2D4 and unassigned BilalG1 Nov 5, 2025
@BilalG1 BilalG1 requested a review from N2D4 November 5, 2025 21:16
@BilalG1 BilalG1 merged commit 9fa7e3b into dev Nov 5, 2025
25 checks passed
@BilalG1 BilalG1 deleted the fix-team-invites branch November 5, 2025 21:17
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.

2 participants