Skip to content

Conversation

@dheeru0198
Copy link
Member

@dheeru0198 dheeru0198 commented Jan 8, 2026

Description

  • Modified the invite link to include a token for enhanced security.
  • Updated the WorkspaceJoinEndpoint to validate the token instead of the email.
  • Adjusted the workspace invitation task to generate links with the token.
  • Refactored the frontend to handle token in the invitation process.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Screenshots and Media (if applicable)

Test Scenarios

References

Summary by CodeRabbit

  • Refactor
    • Updated workspace invitation system to use token-based validation in place of email-based validation
    • Invitation URLs now include a unique token parameter for workspace joins

✏️ Tip: You can customize this high-level summary in your review settings.

- Modified the invite link to include a token for enhanced security.
- Updated the WorkspaceJoinEndpoint to validate the token instead of the email.
- Adjusted the workspace invitation task to generate links with the token.
- Refactored the frontend to handle token in the invitation process.
@makeplane
Copy link

makeplane bot commented Jan 8, 2026

Linked to Plane Work Item(s)

This comment was auto-generated by Plane

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 8, 2026

📝 Walkthrough

Walkthrough

The pull request replaces email-based validation with token-based validation in workspace invitations. Changes span the invite URL generation in serializers, the workspace join endpoint validation logic, background tasks that generate invitation URLs, and the frontend workspace invitations page.

Changes

Cohort / File(s) Summary
Backend API - Serializer & Join Validation
apps/api/plane/app/serializers/workspace.py, apps/api/plane/app/views/workspace/invite.py
Modified invite URL generation to include token parameter instead of email; updated WorkspaceJoinEndpoint to validate incoming token against stored workspace invitation token
Background Task - Invitation URL
apps/api/plane/bgtasks/workspace_invitation_task.py
Updated relative invitation URL to reorder and include token parameter while removing email from query string
Frontend - Workspace Invitations Page
apps/web/app/(all)/workspace-invitations/page.tsx
Replaced email parameter with token from query parameters in joinWorkspace payload; updated invitation validation to compare stored email instead of query parameter email

Sequence Diagram(s)

sequenceDiagram
    participant User as User/Frontend
    participant API as Backend API
    participant DB as Workspace Invite

    User->>API: POST /workspace-invitations/join<br/>(invitation_id, slug, token)
    API->>DB: Fetch workspace_invite by ID
    DB-->>API: Return invitation record
    alt Token Valid
        API->>API: Extract email from workspace_invite
        API->>API: Create/Update WorkspaceMember
        API-->>User: Success response
    else Token Invalid/Missing
        API-->>User: Forbidden (403)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

Hop hop, tokens light the way,
Email fades to yesterday,
Security's our grand ballet,
Token magic saves the day! 🐰✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: updating the workspace invitation flow to use token-based validation instead of email-based validation.
Description check ✅ Passed The description covers the main changes and correctly identifies the improvement type, but the Test Scenarios and References sections are empty.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request updates the workspace invitation flow from email-based validation to token-based validation, significantly enhancing security by preventing unauthorized users from accepting invitations simply by knowing the email address.

Key Changes:

  • Frontend now extracts and uses a token parameter from the URL instead of email for invitation validation
  • Backend API validates invitations using the token field instead of the email field
  • Invitation links now include the token parameter in the URL
  • Error handling improved with explicit typing for caught errors

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
apps/web/app/(all)/workspace-invitations/page.tsx Updated to extract token from URL params, send token in API calls instead of email, improved error typing, removed unused React import
apps/api/plane/app/views/workspace/invite.py Modified WorkspaceJoinEndpoint to validate token instead of email, updated user lookup to use workspace_invite.email
apps/api/plane/bgtasks/workspace_invitation_task.py Updated invitation link generation to include token parameter instead of email
apps/api/plane/app/serializers/workspace.py Modified get_invite_link method to generate links with token parameter
Comments suppressed due to low confidence (1)

apps/api/plane/app/views/workspace/invite.py:169

  • The change from email-based to token-based validation is a significant security enhancement, but there are no tests covering this critical authentication flow. Given that the repository has comprehensive test coverage for other API endpoints (as seen in the tests directory), consider adding tests to verify: 1) token validation works correctly, 2) invalid tokens are rejected with appropriate error messages, 3) missing tokens are handled properly, and 4) the token cannot be reused after the invitation is accepted or rejected.
        token = request.data.get("token", "")

        # Validate the token to verify the user received the invitation email
        if not token or workspace_invite.token != token:
            return Response(
                {"error": "You do not have permission to join the workspace"},
                status=status.HTTP_403_FORBIDDEN,
            )

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const invitation_id = searchParams.get("invitation_id");
const email = searchParams.get("email");
const slug = searchParams.get("slug");
const token = searchParams.get("token");
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The token parameter is not being validated or included in the error handling flow. When a user visits the page without a token, they'll still see the invitation UI and only encounter an error when they try to accept or reject. Consider checking for the token early and showing an appropriate error message if it's missing from the URL parameters.

Copilot uses AI. Check for mistakes.
)
@invalidate_cache(path="/api/users/me/settings/", multiple=True)
def post(self, request, slug, pk):
workspace_invite = WorkspaceMemberInvite.objects.get(pk=pk, workspace__slug=slug)
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The API endpoint lacks proper error handling for the case where the WorkspaceMemberInvite object doesn't exist. If pk or slug are invalid, this will raise a DoesNotExist exception which will result in a 500 error instead of a more appropriate 404 error. Consider using get_object_or_404 or wrapping this in a try-except block to return a proper 404 response.

Copilot uses AI. Check for mistakes.
# Validate the token to verify the user received the invitation email
if not token or workspace_invite.token != token:
return Response(
{"error": "You do not have permission to join the workspace"},
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The error message "You do not have permission to join the workspace" is misleading when the token validation fails. This could be due to an invalid token, expired link, or tampered URL, not necessarily a permission issue. Consider using a more specific error message like "Invalid or expired invitation token" to help users understand the actual problem.

Suggested change
{"error": "You do not have permission to join the workspace"},
{"error": "Invalid or expired invitation token"},

Copilot uses AI. Check for mistakes.
# Check the email
if email == "" or workspace_invite.email != email:
# Validate the token to verify the user received the invitation email
if not token or workspace_invite.token != token:
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The token comparison using direct equality (!=) is vulnerable to timing attacks. An attacker could potentially determine the correct token character by character by measuring response times. Consider using Django's constant_time_compare function from django.utils.crypto to perform the token comparison securely.

Copilot uses AI. Check for mistakes.
if (!invitationDetail) return;
workspaceService
if (!invitationDetail || !token) return;
void workspaceService
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The void operator is used here but not for handleAccept. For consistency and to explicitly indicate that the promise is intentionally not being awaited, consider applying the same pattern to handleAccept or removing it from both if the promises are meant to be fire-and-forget operations.

Copilot uses AI. Check for mistakes.
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/web/app/(all)/workspace-invitations/page.tsx (1)

42-70: Consider adding user-facing error feedback.

Both handleAccept and handleReject catch errors but only log them to the console. Users won't see any feedback if the invitation acceptance/rejection fails (e.g., due to network issues, expired invitations, or invalid tokens).

💡 Suggested improvement for error handling

Consider using a toast notification or error state to inform users when operations fail:

  const handleAccept = () => {
    if (!invitationDetail) return;
    workspaceService
      .joinWorkspace(invitationDetail.workspace.slug, invitationDetail.id, {
        accepted: true,
        token: token,
      })
      .then(() => {
        if (invitationDetail.email === currentUser?.email) {
          router.push(`/${invitationDetail.workspace.slug}`);
        } else {
          router.push("/");
        }
      })
-      .catch((err: unknown) => console.error(err));
+      .catch((err: unknown) => {
+        console.error(err);
+        // Show user-facing error message
+        setToastAlert({
+          type: TOAST_TYPE.ERROR,
+          title: "Error",
+          message: "Failed to accept invitation. Please try again.",
+        });
+      });
  };
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b83d460 and 349539a.

📒 Files selected for processing (4)
  • apps/api/plane/app/serializers/workspace.py
  • apps/api/plane/app/views/workspace/invite.py
  • apps/api/plane/bgtasks/workspace_invitation_task.py
  • apps/web/app/(all)/workspace-invitations/page.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,mts,cts}

📄 CodeRabbit inference engine (.github/instructions/typescript.instructions.md)

**/*.{ts,tsx,mts,cts}: Use const type parameters for more precise literal inference in TypeScript 5.0+
Use the satisfies operator to validate types without widening them
Leverage inferred type predicates to reduce the need for explicit is return types in filter/check functions
Use NoInfer<T> utility to block inference for specific type arguments when they should be determined by other arguments
Utilize narrowing in switch(true) blocks for control flow analysis (TypeScript 5.3+)
Rely on narrowing from direct boolean comparisons for type guards
Trust preserved narrowing in closures when variables aren't modified after the check (TypeScript 5.4+)
Use constant indices to narrow object/array properties (TypeScript 5.5+)
Use standard ECMAScript decorators (Stage 3) instead of legacy experimentalDecorators
Use using declarations for explicit resource management with Disposable pattern instead of manual cleanup (TypeScript 5.2+)
Use with { type: "json" } for import attributes; avoid deprecated assert syntax (TypeScript 5.3/5.8+)
Use import type explicitly when importing types to ensure they are erased during compilation, respecting verbatimModuleSyntax flag
Use .ts, .mts, .cts extensions in import type statements (TypeScript 5.2+)
Use import type { Type } from "mod" with { "resolution-mode": "import" } for specific module resolution contexts (TypeScript 5.3+)
Use new iterator methods (map, filter, etc.) if targeting modern environments (TypeScript 5.6+)
Utilize new Set methods like union, intersection, etc., when available (TypeScript 5.5+)
Use Object.groupBy / Map.groupBy standard methods for grouping instead of external libraries (TypeScript 5.4+)
Use Promise.withResolvers() for creating promises with exposed resolve/reject functions (TypeScript 5.7+)
Use copying array methods (toSorted, toSpliced, with) for immutable array operations (TypeScript 5.2+)
Avoid accessing instance fields via super in classes (TypeScript 5....

Files:

  • apps/web/app/(all)/workspace-invitations/page.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Enable TypeScript strict mode and ensure all files are fully typed

Files:

  • apps/web/app/(all)/workspace-invitations/page.tsx
**/*.{js,jsx,ts,tsx,json,css}

📄 CodeRabbit inference engine (AGENTS.md)

Use Prettier with Tailwind plugin for code formatting, run pnpm fix:format

Files:

  • apps/web/app/(all)/workspace-invitations/page.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,jsx,ts,tsx}: Use ESLint with shared config across packages, adhering to max warnings limits per package
Use camelCase for variable and function names, PascalCase for components and types
Use try-catch with proper error types and log errors appropriately

Files:

  • apps/web/app/(all)/workspace-invitations/page.tsx
🧠 Learnings (4)
📓 Common learnings
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.
📚 Learning: 2025-07-23T18:18:06.875Z
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.

Applied to files:

  • apps/api/plane/app/views/workspace/invite.py
  • apps/api/plane/app/serializers/workspace.py
📚 Learning: 2025-12-23T14:18:32.899Z
Learnt from: dheeru0198
Repo: makeplane/plane PR: 8339
File: apps/api/plane/db/models/api.py:35-35
Timestamp: 2025-12-23T14:18:32.899Z
Learning: Django REST Framework rate limit strings are flexible: only the first character of the time unit matters. Acceptable formats include: "60/s", "60/sec", "60/second" (all equivalent), "60/m", "60/min", "60/minute" (all equivalent), "60/h", "60/hr", "60/hour" (all equivalent), and "60/d", "60/day" (all equivalent). Abbreviations like "min" are valid and do not need to be changed to "minute". Apply this guidance to any Python files in the project that configure DRF throttling rules.

Applied to files:

  • apps/api/plane/app/views/workspace/invite.py
  • apps/api/plane/app/serializers/workspace.py
  • apps/api/plane/bgtasks/workspace_invitation_task.py
📚 Learning: 2025-10-21T17:22:05.204Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7989
File: apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx:45-46
Timestamp: 2025-10-21T17:22:05.204Z
Learning: In the makeplane/plane repository, the refactor from useParams() to params prop is specifically scoped to page.tsx and layout.tsx files in apps/web/app (Next.js App Router pattern). Other components (hooks, regular client components, utilities) should continue using the useParams() hook as that is the correct pattern for non-route components.

Applied to files:

  • apps/web/app/(all)/workspace-invitations/page.tsx
🧬 Code graph analysis (1)
apps/api/plane/app/views/workspace/invite.py (1)
apps/api/plane/db/models/user.py (1)
  • User (42-183)
⏰ 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). (5)
  • GitHub Check: Lint API
  • GitHub Check: Agent
  • GitHub Check: CodeQL analysis (javascript-typescript)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Build packages
🔇 Additional comments (8)
apps/api/plane/bgtasks/workspace_invitation_task.py (1)

28-28: LGTM! Invitation URL updated correctly.

The invitation URL now includes the token parameter instead of email, which enhances security by avoiding exposure of email addresses in URLs while maintaining proper validation on the backend.

apps/api/plane/app/serializers/workspace.py (1)

109-110: LGTM! Serializer updated correctly for token-based invitations.

The invite link now includes the token parameter instead of email, aligning with the enhanced security approach across the invitation flow.

apps/api/plane/app/views/workspace/invite.py (3)

162-169: Excellent security improvement with token-based validation!

The token validation correctly ensures that only users who received the invitation email can access the workspace invitation flow. The check for both token presence and equality is appropriate.


179-179: Good security practice using invitation email.

Using workspace_invite.email for user lookup after token validation ensures the email comes from the validated invitation record rather than potentially malicious user input.


234-237: The GET endpoint intentionally allows unauthenticated access to invitation details.

The endpoint exposes workspace metadata (name, role, email) without token validation. This is by design: the frontend calls getWorkspaceInvitation() to display invitation details to unauthenticated users before they log in or create an account. Token validation is only required for the POST endpoint (accepting/rejecting the invitation), not for viewing.

Since the serializer already exposes the token in the response (via invite_link), requiring token on the GET would provide no additional security and would break the UX pattern. If you have concerns about enumerating invitations by guessing IDs, consider adding rate limiting or allowing only pre-registered email addresses to view invitations.

apps/web/app/(all)/workspace-invitations/page.tsx (3)

31-31: LGTM! Token properly retrieved from query params.

The token is correctly extracted from the URL query parameters for validation in subsequent API calls.


42-57: Token-based acceptance flow implemented correctly.

The accept flow now:

  1. Validates invitation details exist before proceeding
  2. Includes the token in the API payload (line 47) for server-side validation
  3. Uses server-provided email data for comparison (line 50) rather than query params
  4. Properly types errors as unknown (line 56)

59-70: Good defensive programming with token guard.

The reject flow correctly validates both invitationDetail and token presence before making the API call. The void prefix on line 61 explicitly documents that the promise result is intentionally ignored.

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