Skip to content

Comments

fix: always check team.parentID even if no membership#23976

Merged
sean-brydon merged 4 commits intomainfrom
fix/memebership-fallback
Sep 22, 2025
Merged

fix: always check team.parentID even if no membership#23976
sean-brydon merged 4 commits intomainfrom
fix/memebership-fallback

Conversation

@sean-brydon
Copy link
Member

What does this PR do?

  • Fixes #XXXX (GitHub issue number)
  • Fixes CAL-XXXX (Linear issue number - should be visible at the bottom of the GitHub issue description)

Visual Demo (For contributors especially)

A visual demonstration is strongly recommended, for both the original and new change (video / image - any one).

Video Demo (if applicable):

  • Show screen recordings of the issue or feature.
  • Demonstrate how to reproduce the issue, the behavior before and after the change.

Image Demo (if applicable):

  • Add side-by-side screenshots of the original and updated change.
  • Highlight any significant change(s).

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  • Are there environment variables that should be set?
  • What are the minimal test data to have?
  • What is expected (happy path) to have (input and output)?
  • Any other important info that could help to test that PR

Checklist

  • I haven't read the contributing guide
  • My code doesn't follow the style guidelines of this project
  • I haven't commented my code, particularly in hard-to-understand areas
  • I haven't checked if my changes generate no new warnings

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 22, 2025

Walkthrough

Adds getTeamById(teamId) to IPermissionRepository and implements it in PermissionRepository (selecting id and parentId). PermissionCheckService is updated to compute org membership via a team’s parentId when no direct team membership exists, to allow org-level permissions without requiring team membership, and to route PBAC-enabled checks through unified hasPermission/hasPermissions paths that accept userId and teamId. Tests are updated to mock and use getTeamById. An event type create handler now uses PermissionCheckService.checkPermission for org- and team-level checks instead of direct PBAC calls.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description Check ❓ Inconclusive The PR description is mostly a generic template with placeholders (e.g., "Fixes #XXXX") and requested demo/instruction scaffolding but does not describe the actual code changes, testing steps, or reference real issue IDs, making it too vague to verify it documents this changeset. Please update the PR description with a concise summary of the implemented changes (mention getTeamById and the permission/membership fallback behavior), include the correct issue/Linear IDs, add clear testing steps and expected results, and complete the mandatory checklist and test details so reviewers can validate the change.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "fix: always check team.parentID even if no membership" concisely describes the primary change — ensuring team.parentId is considered even when no membership exists — and aligns with the added getTeamById method and the permission-lookup adjustments in the changeset. It is specific and relevant for a teammate scanning PR history.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/memebership-fallback

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.

@graphite-app graphite-app bot requested a review from a team September 22, 2025 07:29
@keithwillcode keithwillcode added consumer core area: core, team members only labels Sep 22, 2025
@dosubot dosubot bot added the 🐛 bug Something isn't working label Sep 22, 2025
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)
packages/features/pbac/services/permission-check.service.ts (1)

236-246: Verify potential null reference on membership.userId.

At line 239, when membership exists and has a team.parentId, the code accesses membership.userId. However, when membership is null (lines 240-245), the fallback correctly uses query.userId.

While the current implementation appears correct since membership.userId would match query.userId when the membership was fetched using the userId, consider using query.userId consistently for clarity:

    // Get org membership either through the team membership or directly from teamId
    if (membership?.team.parentId) {
      // User has team membership, check org through that
-     orgMembership = await this.repository.getOrgMembership(membership.userId, membership.team.parentId);
+     orgMembership = await this.repository.getOrgMembership(query.userId!, membership.team.parentId);
    } else if (query.userId && query.teamId) {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c4547c5 and e8db796.

📒 Files selected for processing (4)
  • packages/features/pbac/domain/repositories/IPermissionRepository.ts (1 hunks)
  • packages/features/pbac/infrastructure/repositories/PermissionRepository.ts (1 hunks)
  • packages/features/pbac/services/__tests__/permission-check.service.test.ts (10 hunks)
  • packages/features/pbac/services/permission-check.service.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*Repository.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Repository files must include Repository suffix, prefix with technology if applicable (e.g., PrismaAppRepository.ts), and use PascalCase matching the exported class

Files:

  • packages/features/pbac/infrastructure/repositories/PermissionRepository.ts
  • packages/features/pbac/domain/repositories/IPermissionRepository.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/features/pbac/infrastructure/repositories/PermissionRepository.ts
  • packages/features/pbac/domain/repositories/IPermissionRepository.ts
  • packages/features/pbac/services/permission-check.service.ts
  • packages/features/pbac/services/__tests__/permission-check.service.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/features/pbac/infrastructure/repositories/PermissionRepository.ts
  • packages/features/pbac/domain/repositories/IPermissionRepository.ts
  • packages/features/pbac/services/permission-check.service.ts
  • packages/features/pbac/services/__tests__/permission-check.service.test.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/features/pbac/infrastructure/repositories/PermissionRepository.ts
  • packages/features/pbac/domain/repositories/IPermissionRepository.ts
  • packages/features/pbac/services/permission-check.service.ts
  • packages/features/pbac/services/__tests__/permission-check.service.test.ts
**/*.{service,repository}.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Avoid dot-suffixes like .service.ts or .repository.ts for new files; reserve .test.ts, .spec.ts, .types.ts for their specific purposes

Files:

  • packages/features/pbac/services/permission-check.service.ts
🧬 Code graph analysis (1)
packages/features/pbac/services/permission-check.service.ts (1)
packages/platform/libraries/repositories.ts (1)
  • MembershipRepository (11-11)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Detect changes
🔇 Additional comments (10)
packages/features/pbac/infrastructure/repositories/PermissionRepository.ts (1)

97-105: LGTM! Clean implementation of team data retrieval.

The new getTeamById method correctly implements the interface contract, using select to fetch only the required fields (id and parentId) as per the coding guidelines.

packages/features/pbac/domain/repositories/IPermissionRepository.ts (1)

40-44: LGTM! Well-defined interface addition.

The new method signature is properly typed and clearly indicates its purpose for retrieving team hierarchy information.

packages/features/pbac/services/__tests__/permission-check.service.test.ts (4)

40-40: LGTM! Mock repository properly updated.

The test mock correctly includes the new getTeamById method to support the updated test scenarios.


140-164: LGTM! Comprehensive test for org-level fallback.

Excellent test coverage for the new org-level permission check behavior when team membership is absent. The test properly verifies the call sequence: attempt team membership → fetch team details → check org membership → validate org permissions.


276-303: LGTM! Consistent test coverage for bulk permissions.

The test properly validates that checkPermissions follows the same org-level fallback pattern as checkPermission, ensuring consistency across permission check methods.


403-429: LGTM! Thorough resource permissions test.

Good test coverage for getResourcePermissions with org-level fallback, properly verifying that permissions are correctly resolved through the organization when team membership is absent.

packages/features/pbac/services/permission-check.service.ts (4)

74-81: LGTM! Clear comment update reflecting new behavior.

The comment accurately describes that org-level permissions now work without requiring team membership, which aligns with the PR's objective.


117-118: Cleaner permission check flow with unified method.

Good refactoring to use the internal hasPermission method, reducing code duplication and ensuring consistent permission evaluation logic.


121-127: LGTM! Proper fallback handling for non-PBAC teams.

The fallback correctly ensures that team membership is required when PBAC is disabled, maintaining backward compatibility.


162-163: Consistent implementation for bulk permission checks.

Good consistency with the single permission check, using the same unified hasPermissions method.

@vercel
Copy link

vercel bot commented Sep 22, 2025

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

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Sep 22, 2025 10:12am
cal-eu Ignored Ignored Sep 22, 2025 10:12am

userId,
teamId: ctx.user.organizationId,
permission: "eventType.create",
fallbackRoles: [MembershipRole.ADMIN, MembershipRole.OWNER],
Copy link
Member Author

Choose a reason for hiding this comment

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

These are the roles we fallback to checking if PBAC is disabled.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts (1)

79-81: Fix user linkage guard — use teamId (not schedulingType)

Gating users on isManagedEventType || schedulingType is incorrect: any set schedulingType will skip linking the creator. Gate on teamId instead.

File: packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts (users line ~75–81)

-    // Only connecting the current user for non-managed event types and non team event types
-    users: isManagedEventType || schedulingType ? undefined : { connect: { id: userId } },
+    // Only connect the current user for non-managed, non-team event types
+    users: isManagedEventType || !!teamId ? undefined : { connect: { id: userId } },

Verify test coverage for managed vs non-managed × with/without teamId.

🧹 Nitpick comments (3)
packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts (3)

57-57: Inject request-scoped dependencies into PermissionCheckService.
Constructing with defaults binds to the global Prisma instance; prefer request-scoped repositories/services (e.g., using ctx.prisma) to preserve transactions/region affinity.

Would you like me to draft a follow-up change to accept ctx.prisma (or a repository instance) here and wire it through the service?


114-119: Good: team-level permission check now runs even without team membership.
This enables the new parentId fallback path in the service.

The comments above (Lines 110–114) are now stale—consider updating/removing them.


100-106: Use Prisma select to minimize data fetched.
You only use membership as a boolean; fetch just an id.

Apply this diff:

-    const hasMembership = await ctx.prisma.membership.findFirst({
+    const hasMembership = await ctx.prisma.membership.findFirst({
       where: {
         userId,
         teamId: teamId,
         accepted: true,
       },
+      select: { id: true },
     });
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e8db796 and 968b517.

📒 Files selected for processing (1)
  • packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts
🧬 Code graph analysis (1)
packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts (2)
packages/features/pbac/services/permission-check.service.ts (1)
  • PermissionCheckService (19-290)
packages/platform/libraries/index.ts (1)
  • MembershipRole (34-34)
🔇 Additional comments (1)
packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts (1)

5-5: Centralizing permission checks via PermissionCheckService looks good.
This aligns the handler with the new PBAC flow.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts (2)

80-80: Bug: users connect condition uses schedulingType, effectively disabling user connect.

TCreateInputSchema.schedulingType is always truthy (enum), so this prevents connecting the creating user on personal (non-team, non-managed) event types.

-    users: isManagedEventType || schedulingType ? undefined : { connect: { id: userId } },
+    users: isManagedEventType || teamId ? undefined : { connect: { id: userId } },

146-153: Guard against null profile.id before create.

profile.id is typed as number | null. Creating with a null profileId will fail at runtime.

   const profile = ctx.user.profile;
+  if (!profile?.id) {
+    throw new TRPCError({ code: "BAD_REQUEST", message: "Profile not found for user." });
+  }
   try {
     const eventTypeRepo = new EventTypeRepository(ctx.prisma);
     const eventType = await eventTypeRepo.create({
       ...data,
       profileId: profile.id,
🧹 Nitpick comments (3)
packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts (3)

99-109: Tighten condition and name for clarity.

schedulingType is always truthy; the extra check is redundant. Also consider a clearer variable name.

-  if (teamId && schedulingType) {
+  if (teamId) {
@@
-    const hasCreatePermission = await permissionService.checkPermission({
+    const hasTeamEventTypeCreatePermission = await permissionService.checkPermission({
       userId,
       teamId,
       permission: "eventType.create",
       fallbackRoles: [MembershipRole.ADMIN, MembershipRole.OWNER],
     });
@@
-    if (!isSystemAdmin && !hasOrgEventTypeCreatePermission && !hasCreatePermission) {
+    if (!isSystemAdmin && !hasOrgEventTypeCreatePermission && !hasTeamEventTypeCreatePermission) {

57-57: Instantiate PermissionCheckService once (inject or hoist) to reduce per-request churn.

Not a blocker, but constructing per-call adds overhead and complicates testing.

Outside this function, add:

// at module scope
const permissionCheckService = new PermissionCheckService();

Then inside the handler use permissionCheckService.


110-115: Minor: augment log context.

Consider adding organizationId to aid triage while keeping PII minimal.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 968b517 and b3030e3.

📒 Files selected for processing (1)
  • packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts
🧠 Learnings (1)
📚 Learning: 2025-08-26T08:08:23.395Z
Learnt from: SinghaAnirban005
PR: calcom/cal.com#23343
File: packages/features/insights/server/trpc-router.ts:1080-1101
Timestamp: 2025-08-26T08:08:23.395Z
Learning: In packages/features/insights/server/trpc-router.ts, when filtering personal event types (userId provided, no teamId, not isAll), the query correctly uses user.id (authenticated user) instead of the input userId parameter for security reasons. This prevents users from accessing other users' personal event types by passing arbitrary user IDs.

Applied to files:

  • packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts
🧬 Code graph analysis (1)
packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts (1)
packages/features/pbac/services/permission-check.service.ts (1)
  • PermissionCheckService (19-290)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Tests / Unit
  • GitHub Check: Type check / check-types
🔇 Additional comments (2)
packages/trpc/server/routers/viewer/eventTypes/heavy/create.handler.ts (2)

128-135: Good Prisma usage: only selecting required fields.

Adheres to the guideline to avoid include and select only what's needed.


61-68: Don't overwrite org-admin fallback; OR-in the PBAC result.

Overwriting drops existing isOrgAdmin truthiness if PBAC returns false. Preserve any prior truthy value.

-  if (ctx.user.organizationId) {
-    hasOrgEventTypeCreatePermission = await permissionService.checkPermission({
-      userId,
-      teamId: ctx.user.organizationId,
-      permission: "eventType.create",
-      fallbackRoles: [MembershipRole.ADMIN, MembershipRole.OWNER],
-    });
-  }
+  if (ctx.user.organizationId) {
+    const orgPBAC = await permissionService.checkPermission({
+      userId,
+      teamId: ctx.user.organizationId,
+      permission: "eventType.create",
+      fallbackRoles: [MembershipRole.ADMIN, MembershipRole.OWNER],
+    });
+    hasOrgEventTypeCreatePermission = hasOrgEventTypeCreatePermission || orgPBAC;
+  }

Please reconfirm that ctx.user.organizationId is the org “teamId” used by PBAC (was previously verified via seed scripts).

@github-actions
Copy link
Contributor

github-actions bot commented Sep 22, 2025

E2E results are ready!

@sean-brydon sean-brydon enabled auto-merge (squash) September 22, 2025 12:12
@sean-brydon sean-brydon merged commit 730aab3 into main Sep 22, 2025
60 of 63 checks passed
@sean-brydon sean-brydon deleted the fix/memebership-fallback branch September 22, 2025 12:19
saurabhraghuvanshii pushed a commit to saurabhraghuvanshii/cal.com that referenced this pull request Sep 24, 2025
* fix: always check team.parentID even if no membership

* refactor permission handling in create event type handler

* remove hasMembership

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐛 bug Something isn't working consumer core area: core, team members only ready-for-e2e size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants