Skip to content

feat: api v2 team invite link endpoint#101

Open
tomerqodo wants to merge 2 commits into
qodo_action_req_1_base_feat_api_v2_team_invite_link_endpoint_pr6from
qodo_action_req_1_head_feat_api_v2_team_invite_link_endpoint_pr6
Open

feat: api v2 team invite link endpoint#101
tomerqodo wants to merge 2 commits into
qodo_action_req_1_base_feat_api_v2_team_invite_link_endpoint_pr6from
qodo_action_req_1_head_feat_api_v2_team_invite_link_endpoint_pr6

Conversation

@tomerqodo

Copy link
Copy Markdown

Benchmark PR from agentic-review-benchmarks#6

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Invite creation lacks audit log 📘 Rule violation ⛨ Security
Description
• The new /v2/teams/:teamId/invite endpoint performs a security-sensitive action (creating an
  invite token/link) without writing an audit log entry.
• This makes it difficult to reconstruct who generated invite links (user ID), for which team, and
  whether the operation succeeded or failed.
• Add an audit/audit-log event capturing user ID, teamId, action name, timestamp, and outcome.
Code

apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts[R30-40]

+export class TeamsInviteController {
+  @Post("/invite")
+  @Roles("TEAM_MEMBER")
+  @ApiOperation({ summary: "Create team invite link" })
+  @HttpCode(HttpStatus.OK)
+  async createInvite(
+    @Param("teamId", ParseIntPipe) teamId: number
+  ): Promise<CreateInviteOutputDto> {
+    const result = await TeamService.createInvite(teamId);
+    return { status: SUCCESS_STATUS, data: result };
+  }
Evidence
The compliance rule requires audit trails for critical actions. The new controller calls
TeamService.createInvite(teamId) and returns the token/link but does not emit any audit log entry
with user context or outcome.

Rule 1: Generic: Comprehensive Audit Trails
apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts[30-40]
packages/features/ee/teams/services/teamService.ts[87-101]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new team invite-link creation endpoint performs a critical security action (issuing invite tokens/links) without creating an audit trail entry that records who performed the action, when, on which team, and whether it succeeded.

## Issue Context
Compliance requires audit logs for critical actions to enable incident investigation and security forensics. This endpoint should write an audit/audit-log record on success/failure and include `userId`, `teamId`, action name, timestamp, and outcome.

## Fix Focus Areas
- apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts[30-40]
- packages/features/ee/teams/services/teamService.ts[87-101]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. teamService breaks naming rule 📘 Rule violation ⛯ Reliability
Description
• The PR introduces a new import from @calcom/features/ee/teams/services/teamService, which points
  to a non-PascalCase service filename.
• Compliance requires service filenames to match the exported class name exactly in PascalCase
  (e.g., TeamService.ts).
• This reduces discoverability and violates the repository’s service naming convention.
Code

apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts[21]

+import { TeamService } from "@calcom/features/ee/teams/services/teamService";
Evidence
The checklist mandates service classes use the <Entity>Service naming pattern with filenames
matching exported class names exactly in PascalCase. The new controller imports TeamService from
.../teamService, and the actual service file is teamService.ts while exporting TeamService,
creating a filename/class mismatch.

AGENTS.md
apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts[21-21]
packages/features/ee/teams/services/teamService.ts[55-56]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A service class (`TeamService`) is defined in `teamService.ts`, which violates the naming convention requiring service filenames to match exported class names exactly in PascalCase.

## Issue Context
Compliance requires `&lt;Entity&gt;Service.ts` filenames with matching exported class names for consistency and discoverability.

## Fix Focus Areas
- apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts[20-22]
- packages/features/ee/teams/services/teamService.ts[55-60]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Wrong token returned 🐞 Bug ✓ Correctness
Description
TeamService.createInvite() now returns a constant identifier string instead of the random
  invite token, breaking clients that must use the token to accept invites.
• The returned token will not match the inviteLink (which is built with the real random token),
  so consumers/tests expecting inviteLink to contain the returned token will fail.
• Because identifier is constant per team, repeated calls can return the same “token” value even
  though a new DB token row is created each time.
Code

packages/features/ee/teams/services/teamService.ts[R87-91]

    const token = randomBytes(32).toString("hex");
-    await prisma.verificationToken.create({
+    const newToken = await prisma.verificationToken.create({
      data: {
        identifier: `invite-link-for-teamId-${teamId}`,
        token,
Evidence
The service stores a random token in the verificationToken row, but the API response returns
newToken.identifier (constant invite-link-for-teamId-${teamId}) instead of the random token. The
endpoint tests assert that inviteLink contains the returned token, which will be false if token !=
random token used to build inviteLink.

packages/features/ee/teams/services/teamService.ts[55-101]
packages/features/ee/teams/services/teamService.ts[69-84]
apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.e2e-spec.ts[67-76]
apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.e2e-spec.ts[79-91]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`TeamService.createInvite()` returns `newToken.identifier` as the response token, but `inviteLink` is built using the random `token`. This breaks the API contract (returned token is not usable to accept invites) and makes `inviteLink` not contain the returned token.

### Issue Context
`identifier` is a constant pattern (`invite-link-for-teamId-${teamId}`) and is not the invite token. Multiple callers (including the new API v2 tests) assume the returned `token` is the actual verification token string.

### Fix Focus Areas
- packages/features/ee/teams/services/teamService.ts[87-101]

### Expected fix
- Change the return value to `token: newToken.token` (or just `token`) so it matches the token embedded into `inviteLink`.
- Optionally rename variables to reduce confusion (e.g., `createdToken` vs `token`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (2)
4. Org invite URL inverted 🐞 Bug ✓ Correctness
Description
buildInviteLink() condition was flipped so org-context invites now return /teams?token=...,
  which requires an authenticated session and redirects unauthenticated users to login.
• This contradicts the documented invite behavior (signup URL with callbackUrl) and likely breaks
  org/team-in-org invites for new users.
• The previous behavior (org-context → /signup?token=...&callbackUrl=...) aligns with the API docs
  and supports new-user signup.
Code

packages/features/ee/teams/services/teamService.ts[R104-109]

  private static async buildInviteLink(token: string, isOrgContext: boolean): Promise<string> {
    const teamInviteLink = `${WEBAPP_URL}/teams?token=${token}`;
-    if (!isOrgContext) {
+    if (isOrgContext) {
      return teamInviteLink;
    }
    const gettingStartedPath = await OnboardingPathService.getGettingStartedPathWhenInvited(prisma);
Evidence
The code now returns /teams?token=... when isOrgContext is true. The web /teams page forces
login when a token is present and no session exists. Meanwhile, the org invite output DTO documents
that the inviteLink opens the signup page with the token and callbackUrl, implying org-context
should use /signup?... not /teams?....

packages/features/ee/teams/services/teamService.ts[104-111]
apps/web/app/(use-page-wrapper)/(main-nav)/teams/page.tsx[29-37]
apps/api/v2/src/modules/organizations/teams/invite/outputs/invite.output.ts[11-17]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`buildInviteLink()` now returns `/teams?token=...` for `isOrgContext === true`. The `/teams` page requires login when a token is present, so org-context invite links won’t work for unauthenticated/new users and contradict documented behavior.

### Issue Context
The API docs for org/team invites describe a signup link with `callbackUrl`.

### Fix Focus Areas
- packages/features/ee/teams/services/teamService.ts[104-111]

### Expected fix
- Restore the previous condition (likely `if (!isOrgContext) return teamInviteLink;`), so org-context returns the `/signup?token=...&amp;callbackUrl=...` link.
- Consider renaming `teamInviteLink`/`orgInviteLink` variables to better reflect which context they serve.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Non-admin can invite 🐞 Bug ⛨ Security
Description
• The new /v2/teams/:teamId/invite endpoint uses @Roles("TEAM_MEMBER"), which (per RolesGuard
  hierarchy) allows OWNER/ADMIN/MEMBER — meaning regular members can create invite links.
• This is a privilege escalation: any team member could generate an invite link and add arbitrary
  users.
• The newly-added e2e test expects a MEMBER to get 403, so the current implementation will fail CI.
Code

apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts[R31-33]

+  @Post("/invite")
+  @Roles("TEAM_MEMBER")
+  @ApiOperation({ summary: "Create team invite link" })
Evidence
RolesGuard uses ordered TEAM_ROLES [OWNER, ADMIN, MEMBER] and grants access when the user’s role
index is <= required role index. If required role is TEAM_MEMBER, then even MEMBER (index 2) passes.
Existing org-team invite endpoint uses TEAM_ADMIN, indicating invite creation is intended to be
admin-only.

apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts[31-33]
apps/api/v2/src/lib/roles/constants.ts[9-13]
apps/api/v2/src/modules/auth/guards/roles/roles.guard.ts[191-201]
apps/api/v2/src/modules/organizations/teams/invite/organizations-teams-invite.controller.ts[32-34]
apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.e2e-spec.ts[148-150]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The new Teams Invite endpoint uses `@Roles(&quot;TEAM_MEMBER&quot;)`, which allows any team member to create invite links. This is an authorization bypass/privilege escalation and also contradicts the new e2e test expectation (MEMBER should be 403).

### Issue Context
RolesGuard treats the decorator value as the minimum required role.

### Fix Focus Areas
- apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts[31-33]

### Expected fix
- Change the decorator to `@Roles(&quot;TEAM_ADMIN&quot;)` (allowing ADMIN + OWNER) to match existing invite controller behavior.
- If needed, align with PBAC checks similar to TRPC (`fallbackRoles: [OWNER, ADMIN]`) for consistency.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



ⓘ The new review experience is currently in Beta. Learn more

Qodo Logo

Comment on lines +30 to +40
export class TeamsInviteController {
@Post("/invite")
@Roles("TEAM_MEMBER")
@ApiOperation({ summary: "Create team invite link" })
@HttpCode(HttpStatus.OK)
async createInvite(
@Param("teamId", ParseIntPipe) teamId: number
): Promise<CreateInviteOutputDto> {
const result = await TeamService.createInvite(teamId);
return { status: SUCCESS_STATUS, data: result };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Invite creation lacks audit log 📘 Rule violation ⛨ Security

• The new /v2/teams/:teamId/invite endpoint performs a security-sensitive action (creating an
  invite token/link) without writing an audit log entry.
• This makes it difficult to reconstruct who generated invite links (user ID), for which team, and
  whether the operation succeeded or failed.
• Add an audit/audit-log event capturing user ID, teamId, action name, timestamp, and outcome.
Agent prompt
## Issue description
The new team invite-link creation endpoint performs a critical security action (issuing invite tokens/links) without creating an audit trail entry that records who performed the action, when, on which team, and whether it succeeded.

## Issue Context
Compliance requires audit logs for critical actions to enable incident investigation and security forensics. This endpoint should write an audit/audit-log record on success/failure and include `userId`, `teamId`, action name, timestamp, and outcome.

## Fix Focus Areas
- apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts[30-40]
- packages/features/ee/teams/services/teamService.ts[87-101]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";

import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { TeamService } from "@calcom/features/ee/teams/services/teamService";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. teamservice breaks naming rule 📘 Rule violation ⛯ Reliability

• The PR introduces a new import from @calcom/features/ee/teams/services/teamService, which points
  to a non-PascalCase service filename.
• Compliance requires service filenames to match the exported class name exactly in PascalCase
  (e.g., TeamService.ts).
• This reduces discoverability and violates the repository’s service naming convention.
Agent prompt
## Issue description
A service class (`TeamService`) is defined in `teamService.ts`, which violates the naming convention requiring service filenames to match exported class names exactly in PascalCase.

## Issue Context
Compliance requires `<Entity>Service.ts` filenames with matching exported class names for consistency and discoverability.

## Fix Focus Areas
- apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts[20-22]
- packages/features/ee/teams/services/teamService.ts[55-60]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 87 to 91
const token = randomBytes(32).toString("hex");
await prisma.verificationToken.create({
const newToken = await prisma.verificationToken.create({
data: {
identifier: `invite-link-for-teamId-${teamId}`,
token,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

3. Wrong token returned 🐞 Bug ✓ Correctness

TeamService.createInvite() now returns a constant identifier string instead of the random
  invite token, breaking clients that must use the token to accept invites.
• The returned token will not match the inviteLink (which is built with the real random token),
  so consumers/tests expecting inviteLink to contain the returned token will fail.
• Because identifier is constant per team, repeated calls can return the same “token” value even
  though a new DB token row is created each time.
Agent prompt
### Issue description
`TeamService.createInvite()` returns `newToken.identifier` as the response token, but `inviteLink` is built using the random `token`. This breaks the API contract (returned token is not usable to accept invites) and makes `inviteLink` not contain the returned token.

### Issue Context
`identifier` is a constant pattern (`invite-link-for-teamId-${teamId}`) and is not the invite token. Multiple callers (including the new API v2 tests) assume the returned `token` is the actual verification token string.

### Fix Focus Areas
- packages/features/ee/teams/services/teamService.ts[87-101]

### Expected fix
- Change the return value to `token: newToken.token` (or just `token`) so it matches the token embedded into `inviteLink`.
- Optionally rename variables to reduce confusion (e.g., `createdToken` vs `token`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 104 to 109
private static async buildInviteLink(token: string, isOrgContext: boolean): Promise<string> {
const teamInviteLink = `${WEBAPP_URL}/teams?token=${token}`;
if (!isOrgContext) {
if (isOrgContext) {
return teamInviteLink;
}
const gettingStartedPath = await OnboardingPathService.getGettingStartedPathWhenInvited(prisma);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

4. Org invite url inverted 🐞 Bug ✓ Correctness

buildInviteLink() condition was flipped so org-context invites now return /teams?token=...,
  which requires an authenticated session and redirects unauthenticated users to login.
• This contradicts the documented invite behavior (signup URL with callbackUrl) and likely breaks
  org/team-in-org invites for new users.
• The previous behavior (org-context → /signup?token=...&callbackUrl=...) aligns with the API docs
  and supports new-user signup.
Agent prompt
### Issue description
`buildInviteLink()` now returns `/teams?token=...` for `isOrgContext === true`. The `/teams` page requires login when a token is present, so org-context invite links won’t work for unauthenticated/new users and contradict documented behavior.

### Issue Context
The API docs for org/team invites describe a signup link with `callbackUrl`.

### Fix Focus Areas
- packages/features/ee/teams/services/teamService.ts[104-111]

### Expected fix
- Restore the previous condition (likely `if (!isOrgContext) return teamInviteLink;`), so org-context returns the `/signup?token=...&callbackUrl=...` link.
- Consider renaming `teamInviteLink`/`orgInviteLink` variables to better reflect which context they serve.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +31 to +33
@Post("/invite")
@Roles("TEAM_MEMBER")
@ApiOperation({ summary: "Create team invite link" })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

5. Non-admin can invite 🐞 Bug ⛨ Security

• The new /v2/teams/:teamId/invite endpoint uses @Roles("TEAM_MEMBER"), which (per RolesGuard
  hierarchy) allows OWNER/ADMIN/MEMBER — meaning regular members can create invite links.
• This is a privilege escalation: any team member could generate an invite link and add arbitrary
  users.
• The newly-added e2e test expects a MEMBER to get 403, so the current implementation will fail CI.
Agent prompt
### Issue description
The new Teams Invite endpoint uses `@Roles("TEAM_MEMBER")`, which allows any team member to create invite links. This is an authorization bypass/privilege escalation and also contradicts the new e2e test expectation (MEMBER should be 403).

### Issue Context
RolesGuard treats the decorator value as the minimum required role.

### Fix Focus Areas
- apps/api/v2/src/modules/teams/invite/controllers/teams-invite.controller.ts[31-33]

### Expected fix
- Change the decorator to `@Roles("TEAM_ADMIN")` (allowing ADMIN + OWNER) to match existing invite controller behavior.
- If needed, align with PBAC checks similar to TRPC (`fallbackRoles: [OWNER, ADMIN]`) for consistency.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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.

1 participant