Skip to content

Conversation

@dhairyashiil
Copy link
Member

Before:

Screen.Recording.2025-11-25.at.6.04.01.AM.mov

After:

Screen.Recording.2025-11-25.at.6.04.44.AM.mov

Problem

  • Users in PBAC-enabled organizations couldn't access insights page
  • Even admins with admin_role (which has insights.read permission) were blocked
  • The code was only checking the base role field (ADMIN/OWNER/MEMBER)
  • It completely ignored PBAC permissions

What This Fixes

  • Added proper PBAC permission checks using PermissionCheckService
  • Admins with PBAC roles can now access insights
  • Custom roles with insights.read permission now work
  • Non-PBAC organizations still work the same way (backward compatible)

Changes Made

  1. Created checkInsightsPermission() helper function

    • Checks if user has insights.read permission
    • Uses PBAC system when enabled
    • Falls back to checking ADMIN/OWNER role when PBAC disabled
  2. Updated organization-level access check

    • Now uses PBAC permission check instead of direct role query
  3. Updated team list query

    • Filters teams based on insights.read permission
    • Works with both PBAC roles and traditional roles

Testing

✅ PBAC enabled + admin_role → Can access insights
✅ PBAC enabled + custom role with insights.read → Can access insights
✅ PBAC enabled + custom role without insights.read → Cannot access insights
✅ PBAC disabled + traditional ADMIN → Can access insights
✅ PBAC disabled + traditional MEMBER → Cannot access insights

- Add checkInsightsPermission() helper that properly checks insights.read permission with PBAC support
- Update userBelongsToTeamProcedure to use PBAC-aware permission check for org-level access
- Update teamListForUser query to filter teams based on insights.read permission instead of only checking base ADMIN/OWNER roles
- Maintain backward compatibility with fallback to traditional role checks (ADMIN/OWNER) when PBAC is disabled
- Org admins (base role ADMIN/OWNER) continue to have automatic insights access as a privileged position
- Team-level admins with custom roles now properly checked for insights.read permission

Fixes issue where users with custom PBAC roles couldn't access insights even if they had insights.read permission.

Related: CAL-XXXX
@vercel
Copy link

vercel bot commented Nov 25, 2025

@dhairyashiil is attempting to deploy a commit to the cal Team on Vercel.

A member of the Team first needs to authorize it.

@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Nov 25, 2025
@keithwillcode keithwillcode added the community-interns The team responsible for reviewing, testing and shipping low/medium community PRs label Nov 25, 2025
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 1 file

Comment on lines 684 to 694
// Filter teams to only include those where the user has insights.read permission
// This properly handles both PBAC permissions and traditional role-based access
const teamsWithAccess = await Promise.all(
belongsToTeams.map(async (membership) => {
const hasAccess = await checkInsightsPermission(user.id, membership.team.id);
return hasAccess ? membership.team : null;
})
);

const result: IResultTeamList[] = teamsWithAccess
.filter((team): team is NonNullable<typeof team> => team !== null)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can use getTeamIdsWithPermission method in permission-service to avoid N+1 queries here

Copy link
Member Author

Choose a reason for hiding this comment

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

I have implemented the suggested changes and tested it from the UI again. it’s working. Thank you for the review 🙏🏼

Replace individual permission checks per team with bulk query using getTeamIdsWithPermission().
This reduces database queries from N (one per team) to a single optimized query.

- Use PermissionCheckService.getTeamIdsWithPermission() for bulk permission checking
- Filter teams based on returned team IDs instead of individual checks
- Maintains same functionality with significantly better performance for users with many teams
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file (reviewed changes from recent commits).

Prompt for AI agents (all 1 issues)

Understand the root cause of the following 1 issues and fix them.


<file name="packages/features/insights/server/trpc-router.ts">

<violation number="1" location="packages/features/insights/server/trpc-router.ts:687">
Bulk permission filtering no longer honors org-level fallback roles, so org admins who are members on child teams lose insights access.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

// Get all team IDs where user has insights.read permission in a single optimized query
// This avoids N+1 queries by fetching all permissions at once
const permissionCheckService = new PermissionCheckService();
const teamIdsWithAccess = await permissionCheckService.getTeamIdsWithPermission({
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 25, 2025

Choose a reason for hiding this comment

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

Bulk permission filtering no longer honors org-level fallback roles, so org admins who are members on child teams lose insights access.

Prompt for AI agents
Address the following comment on packages/features/insights/server/trpc-router.ts at line 687:

<comment>Bulk permission filtering no longer honors org-level fallback roles, so org admins who are members on child teams lose insights access.</comment>

<file context>
@@ -681,18 +681,19 @@ export const insightsRouter = router({
+    // Get all team IDs where user has insights.read permission in a single optimized query
+    // This avoids N+1 queries by fetching all permissions at once
+    const permissionCheckService = new PermissionCheckService();
+    const teamIdsWithAccess = await permissionCheckService.getTeamIdsWithPermission({
+      userId: user.id,
+      permission: &quot;insights.read&quot;,
</file context>

✅ Addressed in 624d1ef

Copy link
Contributor

Choose a reason for hiding this comment

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

@sean-brydon is this true?

Copy link
Member

Choose a reason for hiding this comment

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

I don't believe so - will double check the code

Copy link
Member

Choose a reason for hiding this comment

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

Actually believe this to be the case - working on a fix. SQL will be pretty complex here so wll push a new PR

Copy link
Member

Choose a reason for hiding this comment

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

#25387 PR here fixing

Comment on lines 693 to 696
// Filter to only teams where user has access
const result: IResultTeamList[] = belongsToTeams
.filter((membership) => teamIdsWithAccess.includes(membership.team.id))
.map((membership) => ({ ...membership.team }));
Copy link
Contributor

Choose a reason for hiding this comment

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

Also when we get teamIdsWithAccess. We can simply fetch these teams only instead of all the teams and then filtering

Copy link
Member

Choose a reason for hiding this comment

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

Yeah we should deffo just change the query to check teamIds IN this teamIdsWithAccess

Copy link
Member Author

Choose a reason for hiding this comment

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

Implemented the suggested changes. Thank you both 🙏🏼

Move permission check before team query to filter at database level.
Previously fetched all teams then filtered in JavaScript.
Now only fetches teams the user has insights access to.

- Check permissions first using getTeamIdsWithPermission()
- Add teamId filter to membership query (teamId: { in: teamIdsWithAccess })
- Remove JavaScript filter step (done at DB level)
- Reduces data transfer and improves query efficiency
Copy link
Contributor

@Udit-takkar Udit-takkar left a comment

Choose a reason for hiding this comment

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

LGTM

@github-actions
Copy link
Contributor

E2E results are ready!

@dhairyashiil dhairyashiil merged commit 7242048 into calcom:main Nov 27, 2025
33 of 37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community Created by Linear-GitHub Sync community-interns The team responsible for reviewing, testing and shipping low/medium community PRs ready-for-e2e size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants