-
Notifications
You must be signed in to change notification settings - Fork 4.2k
fix(api): require token to view workspace invitation (GHSA-gf48-p6jp-cwc4) #9015
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: preview
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -39,9 +39,9 @@ function WorkspaceInvitationPage() { | |||||||||||||||||||||||||||||||||
| const { data: currentUser } = useUser(); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const { data: invitationDetail, error } = useSWR( | ||||||||||||||||||||||||||||||||||
| invitation_id && slug && WORKSPACE_INVITATION(invitation_id.toString()), | ||||||||||||||||||||||||||||||||||
| invitation_id && slug | ||||||||||||||||||||||||||||||||||
| ? () => workspaceService.getWorkspaceInvitation(slug.toString(), invitation_id.toString()) | ||||||||||||||||||||||||||||||||||
| invitation_id && slug && token && WORKSPACE_INVITATION(invitation_id.toString()), | ||||||||||||||||||||||||||||||||||
| invitation_id && slug && token | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+42
to
+43
|
||||||||||||||||||||||||||||||||||
| ? () => workspaceService.getWorkspaceInvitation(slug.toString(), invitation_id.toString(), token.toString()) | ||||||||||||||||||||||||||||||||||
| : null | ||||||||||||||||||||||||||||||||||
|
Comment on lines
41
to
45
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's check if the file exists and read the relevant section
cat -n apps/web/app/\(all\)/workspace-invitations/page.tsx | sed -n '35,55p'Repository: makeplane/plane Length of output: 956 🏁 Script executed: # Also check the structure of WORKSPACE_INVITATION function to understand what it returns
grep -n "WORKSPACE_INVITATION" apps/web/app/\(all\)/workspace-invitations/page.tsx -B 2 -A 2Repository: makeplane/plane Length of output: 604 🏁 Script executed: # Search for WORKSPACE_INVITATION definition across the codebase
rg "WORKSPACE_INVITATION\s*=" --type ts --type tsx -A 3Repository: makeplane/plane Length of output: 87 🏁 Script executed: # Search for WORKSPACE_INVITATION definition
rg "WORKSPACE_INVITATION" -A 3 | head -50Repository: makeplane/plane Length of output: 3581 🏁 Script executed: # Check what the function returns
fd "fetch-keys" -type f | head -5Repository: makeplane/plane Length of output: 230 🏁 Script executed: # Let's read the fetch-keys file to see WORKSPACE_INVITATION definition
find . -name "*fetch-keys*" -type fRepository: makeplane/plane Length of output: 99 🏁 Script executed: # Let's check the fetch-keys file to see the full context
cat -n apps/web/core/constants/fetch-keys.ts | head -50Repository: makeplane/plane Length of output: 2168 🏁 Script executed: # Check if there are other usages of SWR with token in cache keys
rg "useSWR.*token" -B 2 -A 3 | head -100Repository: makeplane/plane Length of output: 41 🏁 Script executed: # Look at the auth-header.tsx example more closely
cat -n apps/web/core/components/account/auth-forms/auth-header.tsx | grep -A 10 -B 5 "WORKSPACE_INVITATION"Repository: makeplane/plane Length of output: 765 🏁 Script executed: # Check if there are other invitation-related SWR calls with token parameters
rg "getWorkspaceInvitation|joinWorkspace" -B 5 -A 5 | head -80Repository: makeplane/plane Length of output: 5954 🏁 Script executed: # Let's check if invitation_id is globally unique or workspace-specific by looking at the API
rg "invitations.*invitation_id" --type ts -B 5 | head -60Repository: makeplane/plane Length of output: 41 🏁 Script executed: # Check SWR caching behavior and if there are comments about token-based caching
cat -n apps/web/app/\(all\)/workspace-invitations/page.tsx | sed -n '1,80p'Repository: makeplane/plane Length of output: 3194 🏁 Script executed: # Let's also verify the WORKSPACE_INVITATION constant more thoroughly
rg "export const WORKSPACE_INVITATION" -A 2Repository: makeplane/plane Length of output: 350 🏁 Script executed: # Check the workspace service getWorkspaceInvitation method signature
cat -n apps/web/core/services/workspace.service.ts | grep -A 15 "async getWorkspaceInvitation"Repository: makeplane/plane Length of output: 615 🏁 Script executed: # Look at API endpoint to understand if token is user-specific or invitation-specific
rg "invitations.*join" -B 3 -A 8 | head -40Repository: makeplane/plane Length of output: 2927 🏁 Script executed: # Check if there are other places in the codebase where token is included in SWR keys
rg "useSWR.*token.*\[" -B 1 -A 5 | head -60Repository: makeplane/plane Length of output: 41 🏁 Script executed: # Check if there are any comments or documentation about SWR key design in the project
rg "SWR.*key|cache.*key" --type ts -B 2 -A 2 | head -50Repository: makeplane/plane Length of output: 1143 🏁 Script executed: # Let's verify the exact behavior difference - check if slug is used in other similar patterns
rg "WORKSPACE_INVITATION\(" -B 3 -A 3Repository: makeplane/plane Length of output: 754 🏁 Script executed: # Verify the diff format is correct for SWR array keys
rg "useSWR.*\[" -B 1 -A 3 | head -50Repository: makeplane/plane Length of output: 41 Include The fetcher requires Suggested cache-key fix const { data: invitationDetail, error } = useSWR(
- invitation_id && slug && token && WORKSPACE_INVITATION(invitation_id.toString()),
+ invitation_id && slug && token
+ ? [WORKSPACE_INVITATION(invitation_id.toString()), slug.toString(), token.toString()]
+ : null,
invitation_id && slug && token
? () => workspaceService.getWorkspaceInvitation(slug.toString(), invitation_id.toString(), token.toString())
: null
);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
@@ -58,6 +58,7 @@ function WorkspaceInvitationPage() { | |||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||
| router.push("/"); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||
| .catch((err: unknown) => console.error(err)); | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
@@ -71,6 +72,7 @@ function WorkspaceInvitationPage() { | |||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||
| .then(() => { | ||||||||||||||||||||||||||||||||||
| router.push("/"); | ||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||
| .catch((err: unknown) => console.error(err)); | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -19,6 +19,7 @@ import { WorkspaceService } from "@/services/workspace.service"; | |||||||||||||||||||||||||||||||||||||
| type TAuthHeader = { | ||||||||||||||||||||||||||||||||||||||
| workspaceSlug: string | undefined; | ||||||||||||||||||||||||||||||||||||||
| invitationId: string | undefined; | ||||||||||||||||||||||||||||||||||||||
| invitationToken: string | undefined; | ||||||||||||||||||||||||||||||||||||||
| invitationEmail: string | undefined; | ||||||||||||||||||||||||||||||||||||||
| authMode: EAuthModes; | ||||||||||||||||||||||||||||||||||||||
| currentAuthStep: EAuthSteps; | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -58,13 +59,17 @@ const Titles = { | |||||||||||||||||||||||||||||||||||||
| const workSpaceService = new WorkspaceService(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export const AuthHeader = observer(function AuthHeader(props: TAuthHeader) { | ||||||||||||||||||||||||||||||||||||||
| const { workspaceSlug, invitationId, invitationEmail, authMode, currentAuthStep } = props; | ||||||||||||||||||||||||||||||||||||||
| const { workspaceSlug, invitationId, invitationToken, invitationEmail, authMode, currentAuthStep } = props; | ||||||||||||||||||||||||||||||||||||||
| // plane imports | ||||||||||||||||||||||||||||||||||||||
| const { t } = useTranslation(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const { data: invitation, isLoading } = useSWR( | ||||||||||||||||||||||||||||||||||||||
| workspaceSlug && invitationId ? `WORKSPACE_INVITATION_${workspaceSlug}_${invitationId}` : null, | ||||||||||||||||||||||||||||||||||||||
| async () => workspaceSlug && invitationId && workSpaceService.getWorkspaceInvitation(workspaceSlug, invitationId), | ||||||||||||||||||||||||||||||||||||||
| workspaceSlug && invitationId && invitationToken ? `WORKSPACE_INVITATION_${workspaceSlug}_${invitationId}` : null, | ||||||||||||||||||||||||||||||||||||||
| async () => | ||||||||||||||||||||||||||||||||||||||
| workspaceSlug && | ||||||||||||||||||||||||||||||||||||||
| invitationId && | ||||||||||||||||||||||||||||||||||||||
| invitationToken && | ||||||||||||||||||||||||||||||||||||||
| workSpaceService.getWorkspaceInvitation(workspaceSlug, invitationId, invitationToken), | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
66
to
+72
Comment on lines
66
to
+72
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n apps/web/core/components/account/auth-forms/auth-header.tsx | head -80Repository: makeplane/plane Length of output: 3073 🏁 Script executed: # Get the full extent of the useSWR call and understand the context
sed -n '60,80p' apps/web/core/components/account/auth-forms/auth-header.tsxRepository: makeplane/plane Length of output: 785 🏁 Script executed: # Check if invitationToken is a query parameter in the URL
rg "invitationToken" apps/web/core/components/account/auth-forms/auth-header.tsx -A 2 -B 2Repository: makeplane/plane Length of output: 865 Include The cache key only uses Suggested cache-key fix const { data: invitation, isLoading } = useSWR(
- workspaceSlug && invitationId && invitationToken ? `WORKSPACE_INVITATION_${workspaceSlug}_${invitationId}` : null,
+ workspaceSlug && invitationId && invitationToken
+ ? ["WORKSPACE_INVITATION", workspaceSlug, invitationId, invitationToken]
+ : null,
async () =>
workspaceSlug &&
invitationId &&
invitationToken &&
workSpaceService.getWorkspaceInvitation(workspaceSlug, invitationId, invitationToken),📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| revalidateOnFocus: false, | ||||||||||||||||||||||||||||||||||||||
| shouldRetryOnError: false, | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -74,11 +79,11 @@ export const AuthHeader = observer(function AuthHeader(props: TAuthHeader) { | |||||||||||||||||||||||||||||||||||||
| const getHeaderSubHeader = ( | ||||||||||||||||||||||||||||||||||||||
| step: EAuthSteps, | ||||||||||||||||||||||||||||||||||||||
| mode: EAuthModes, | ||||||||||||||||||||||||||||||||||||||
| invitation: IWorkspaceMemberInvitation | undefined, | ||||||||||||||||||||||||||||||||||||||
| inviteData: IWorkspaceMemberInvitation | undefined, | ||||||||||||||||||||||||||||||||||||||
| email: string | undefined | ||||||||||||||||||||||||||||||||||||||
| ) => { | ||||||||||||||||||||||||||||||||||||||
| if (invitation && email && invitation.email === email && invitation.workspace) { | ||||||||||||||||||||||||||||||||||||||
| const workspace = invitation.workspace; | ||||||||||||||||||||||||||||||||||||||
| if (inviteData && email && inviteData.email === email && inviteData.workspace) { | ||||||||||||||||||||||||||||||||||||||
| const workspace = inviteData.workspace; | ||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||
| header: ( | ||||||||||||||||||||||||||||||||||||||
| <div className="relative inline-flex items-center gap-2"> | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -169,8 +169,15 @@ export class WorkspaceService extends APIService { | |
| }); | ||
| } | ||
|
|
||
| async getWorkspaceInvitation(workspaceSlug: string, invitationId: string): Promise<IWorkspaceMemberInvitation> { | ||
| return this.get(`/api/workspaces/${workspaceSlug}/invitations/${invitationId}/join/`, { headers: {} }) | ||
| async getWorkspaceInvitation( | ||
| workspaceSlug: string, | ||
| invitationId: string, | ||
| token: string | ||
| ): Promise<IWorkspaceMemberInvitation> { | ||
| return this.get(`/api/workspaces/${workspaceSlug}/invitations/${invitationId}/join/`, { | ||
| headers: {}, | ||
| params: { token }, | ||
| }) | ||
|
Comment on lines
+172
to
+180
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: # Verify the method exists at the stated lines and get context
cat -n apps/web/core/services/workspace.service.ts | sed -n '165,185p'Repository: makeplane/plane Length of output: 803 🏁 Script executed: # Check the type definition of IWorkspaceMemberInvitation
cat packages/types/src/workspace.ts | grep -A 20 "IWorkspaceMemberInvitation"Repository: makeplane/plane Length of output: 467 🏁 Script executed: # Verify the backend endpoint mentioned in the review
cat apps/api/plane/app/views/workspace/invite.py | sed -n '255,275p'Repository: makeplane/plane Length of output: 757 🏁 Script executed: # Check all imports in workspace.service.ts to see if types are properly imported
head -50 apps/web/core/services/workspace.service.tsRepository: makeplane/plane Length of output: 1396 🏁 Script executed: # Find all usages of getWorkspaceInvitation
rg "getWorkspaceInvitation" --type ts --type tsx -B 2 -A 2Repository: makeplane/plane Length of output: 87 🏁 Script executed: # Check if there's an existing public invitation type or similar pattern in workspace.ts
rg "Invitation|Invite" packages/types/src/workspace.ts -iRepository: makeplane/plane Length of output: 213 🏁 Script executed: # Check the entire WorkspaceService to see if there are similar type mismatches
rg "Promise<I" apps/web/core/services/workspace.service.tsRepository: makeplane/plane Length of output: 1756 🏁 Script executed: # Fix the file type search - use ts for TypeScript files
rg "getWorkspaceInvitation" -t ts -B 2 -A 5Repository: makeplane/plane Length of output: 6372 🏁 Script executed: # Check if userWorkspaceInvitations and workspaceInvitations also have similar issues
rg "userWorkspaceInvitations|workspaceInvitations" apps/web/core/services/workspace.service.ts -B 2 -A 10Repository: makeplane/plane Length of output: 896 🏁 Script executed: # Find where getWorkspaceInvitation is called to understand how the return value is used
rg "getWorkspaceInvitation" --type-list | head -5Repository: makeplane/plane Length of output: 449 🏁 Script executed: # Search for files that call getWorkspaceInvitation
find apps/web -type f -name "*.ts" -o -name "*.tsx" | xargs grep -l "getWorkspaceInvitation" 2>/dev/nullRepository: makeplane/plane Length of output: 346 🏁 Script executed: # Check how the invitation data is used in workspace-invitations/page.tsx
sed -n '1,100p' apps/web/app/\(all\)/workspace-invitations/page.tsx | head -80Repository: makeplane/plane Length of output: 2634 🏁 Script executed: # Check the full usage of invitationDetail in workspace-invitations/page.tsx
grep -A 20 "invitationDetail" apps/web/app/\(all\)/workspace-invitations/page.tsx | head -50Repository: makeplane/plane Length of output: 1832 🏁 Script executed: # Check how the invitation data is used in auth-header.tsx
grep -B 5 -A 20 "getWorkspaceInvitation" apps/web/core/components/account/auth-forms/auth-header.tsxRepository: makeplane/plane Length of output: 1026 🏁 Script executed: # Check the backend endpoints for userWorkspaceInvitations and workspaceInvitations
# to see if they also strip token and invite_link
rg "token.*invite_link|invite_link.*token" apps/api/plane/app/views/workspace/invite.py -B 5 -A 5Repository: makeplane/plane Length of output: 401 🏁 Script executed: # Check other invitation-related endpoints to understand the pattern
grep -n "def.*invitation" apps/api/plane/app/views/workspace/invite.py -iRepository: makeplane/plane Length of output: 41 Narrow this method's return type to match the public invite shape.
Suggested fix+type TPublicWorkspaceMemberInvitation = Omit<IWorkspaceMemberInvitation, "token" | "invite_link">;
+
async getWorkspaceInvitation(
workspaceSlug: string,
invitationId: string,
token: string
- ): Promise<IWorkspaceMemberInvitation> {
+ ): Promise<TPublicWorkspaceMemberInvitation> {
return this.get(`/api/workspaces/${workspaceSlug}/invitations/${invitationId}/join/`, {
headers: {},
params: { token },
})Per coding guidelines: "Enable TypeScript strict mode and ensure all files must be typed." 🤖 Prompt for AI Agents |
||
| .then((response) => response?.data) | ||
| .catch((error) => { | ||
| throw error?.response?.data; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.