Skip to content

Comments

fix: remove legacy logic into legacy service#24095

Merged
sean-brydon merged 3 commits intomainfrom
fix/legacy-change-member-role-fixes
Sep 26, 2025
Merged

fix: remove legacy logic into legacy service#24095
sean-brydon merged 3 commits intomainfrom
fix/legacy-change-member-role-fixes

Conversation

@sean-brydon
Copy link
Member

@sean-brydon sean-brydon commented Sep 26, 2025

What does this PR do?

This PR moves legacy role logic into the legacy role service as it doesnt need to be ran when we are dealing with PBAC

s.

How should this be tested?

Impersonate a user like "teampro"
Make users member,admin,owner -> verify
impersonate a admin -> verify cannot make owner

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 26, 2025

Walkthrough

The role-management API signatures were extended so checkPermissionToChangeRole accepts optional memberId and newRole across IRoleManager, PBACRoleManager, and LegacyRoleManager. LegacyRoleManager gains a protected validateRoleChange method and invokes it for team-scoped changes when memberId/newRole are supplied; LegacyRoleManager is newly exported. The teams changeMemberRole handler now resolves organizationId via TeamRepository, calls RoleManagementFactory.checkPermissionToChangeRole with memberId and role, fetches the target membership by composite key, removes inline ownership/admin checks, and simplifies error handling (UNAUTHORIZED for permission failures, BAD_REQUEST for assignment errors).

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title accurately highlights the core change of removing legacy logic into a dedicated legacy role service, aligning with the main intent of the pull request, though the phrasing could be slightly refined for clarity.
Description Check ✅ Passed The description directly relates to the changeset by explaining that legacy role logic is moved into a legacy service and provides relevant testing steps for verifying role assignments under PBAC.
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/legacy-change-member-role-fixes

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 26, 2025 09:58
@keithwillcode keithwillcode added consumer core area: core, team members only labels Sep 26, 2025
@dosubot dosubot bot added teams area: teams, round robin, collective, managed event-types 🐛 bug Something isn't working labels Sep 26, 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: 1

🧹 Nitpick comments (1)
packages/trpc/server/routers/viewer/teams/changeMemberRole.handler.ts (1)

48-52: Scope the membership lookup to the fields we actually use

We only need the membership id for the subsequent assignRole call. Narrowing the query avoids pulling unnecessary data and keeps us aligned with our Prisma usage guidelines.

Apply this diff:

   const targetMembership = await prisma.membership.findUnique({
     where: {
       userId_teamId: { userId: input.memberId, teamId: input.teamId },
     },
+    select: { id: true },
   });

As per coding guidelines

📜 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 3054ad3 and fe711b0.

📒 Files selected for processing (2)
  • packages/features/pbac/services/role-management.factory.ts (8 hunks)
  • packages/trpc/server/routers/viewer/teams/changeMemberRole.handler.ts (1 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/teams/changeMemberRole.handler.ts
  • packages/features/pbac/services/role-management.factory.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/teams/changeMemberRole.handler.ts
  • packages/features/pbac/services/role-management.factory.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/teams/changeMemberRole.handler.ts
  • packages/features/pbac/services/role-management.factory.ts
🧬 Code graph analysis (1)
packages/features/pbac/services/role-management.factory.ts (2)
packages/features/pbac/domain/errors/role-management.error.ts (1)
  • RoleManagementError (1-6)
packages/features/ee/teams/lib/queries.ts (1)
  • isTeamOwner (388-397)
⏰ 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: Install dependencies / Yarn install & cache

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@vercel
Copy link

vercel bot commented Sep 26, 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 26, 2025 11:28am
cal-eu Ignored Ignored Sep 26, 2025 11:28am

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

🧹 Nitpick comments (6)
packages/features/pbac/services/role-management.factory.ts (6)

15-21: Clarify what “memberId” represents (userId vs membership.id) and document it

Ambiguity here risks misuse at call sites. If it’s the target user’s userId, rename to targetUserId or add JSDoc.

Would you confirm that memberId is the target user’s userId (not membership.id) at all call sites? I can update names/docs accordingly.


124-128: Simplify enum check (type guard is redundant)

Both enum values and custom roles are strings at runtime. The typeof check can be dropped.

Apply:

-    if (typeof newRole !== "string" || !Object.values(MembershipRole).includes(newRole as MembershipRole)) {
+    if (!Object.values(MembershipRole).includes(newRole as MembershipRole)) {
       return;
     }

138-141: Avoid extra DB round-trip; use myMembership for owner check

You already loaded memberships. Use myMembership instead of isTeamOwner().

Apply:

-    if (newRole === MembershipRole.OWNER && !(await isTeamOwner(userId, teamId))) {
+    if (newRole === MembershipRole.OWNER && myMembership?.role !== MembershipRole.OWNER) {
       throw new RoleManagementError("Only owners can award owner role", RoleManagementErrorCode.UNAUTHORIZED);
     }

159-169: Self-change rule likely too strict (blocks ADMIN -> ADMIN no-op)

Current condition blocks any self-change except demotion to MEMBER. If intent is “admins cannot promote themselves” (esp. to OWNER), narrow the condition.

Option A (only block promotion to OWNER):

-    if (
-      myMembership?.role === MembershipRole.ADMIN &&
-      memberId === userId &&
-      newRole !== MembershipRole.MEMBER
-    ) {
+    if (
+      myMembership?.role === MembershipRole.ADMIN &&
+      memberId === userId &&
+      newRole === MembershipRole.OWNER
+    ) {
       throw new RoleManagementError(
         "You can not change yourself to a higher role.",
         RoleManagementErrorCode.UNAUTHORIZED
       );
     }

If broader “no upward moves” is desired, compare role precedence via a small rank map; I can add that.


203-211: Prisma: select only required fields

Return only userId and role for validation to reduce data surface.

Apply (and mirror this pattern in the findFirst above to select a minimal field, e.g., id). As per coding guidelines.

-      const memberships = await prisma.membership.findMany({
-        where: {
-          teamId: targetId,
-          accepted: true,
-        },
-      });
+      const memberships = await prisma.membership.findMany({
+        where: { teamId: targetId, accepted: true },
+        select: { userId: true, role: true },
+      });

265-267: Avoid comma operator and no-unused-expressions suppression

Use plain assignments; drop the lint suppression.

Apply:

-    // Not used but needed for DI
-    // eslint-disable-next-line @typescript-eslint/no-unused-expressions
-    (this.featuresRepository = new FeaturesRepository(prisma)), (this.roleService = new RoleService());
+    this.featuresRepository = new FeaturesRepository(prisma);
+    this.roleService = new RoleService();
📜 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 fe711b0 and a06792c.

📒 Files selected for processing (1)
  • packages/features/pbac/services/role-management.factory.ts (8 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/features/pbac/services/role-management.factory.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/services/role-management.factory.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/services/role-management.factory.ts
🧬 Code graph analysis (1)
packages/features/pbac/services/role-management.factory.ts (2)
packages/features/pbac/domain/errors/role-management.error.ts (1)
  • RoleManagementError (1-6)
packages/features/ee/teams/lib/queries.ts (1)
  • isTeamOwner (388-397)
⏰ 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: Install dependencies / Yarn install & cache
🔇 Additional comments (2)
packages/features/pbac/services/role-management.factory.ts (2)

203-211: Good fix: filter to accepted memberships for owner-guard

This addresses the prior bug about counting pending owners; thanks for tightening the query.


256-257: Explicit export of LegacyRoleManager

Makes testing and DI easier. LGTM.

Comment on lines +40 to +50
async checkPermissionToChangeRole(
userId: number,
targetId: number,
scope: "org" | "team",
// Not required for this instance
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_memberId?: number,
// Not required for this instance
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_newRole?: MembershipRole | string
): Promise<void> {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify org-scope permission path with PermissionCheckService

checkPermission expects a teamId; for scope="org" we still pass targetId as teamId with permission "organization.changeMemberRole". Confirm this is supported by PermissionCheckService.

Run to locate definitions/usage of these permission strings and validate org handling:


🏁 Script executed:

#!/bin/bash
rg -nP -C2 '\borganization\.changeMemberRole\b|\bteam\.changeMemberRole\b'
rg -nP -C3 'class\s+PermissionCheckService\b(?s).*?checkPermission\s*\('

Length of output: 2930


🏁 Script executed:

#!/bin/bash
# Locate PermissionCheckService and show its checkPermission signature
perm_file=$(rg -lP 'class\s+PermissionCheckService\b')
echo "PermissionCheckService file: $perm_file"
rg -nP -C3 'class\s+PermissionCheckService\b' "$perm_file"
rg -nP -C3 'checkPermission\s*\(' "$perm_file"

# Check if 'organization.changeMemberRole' is registered as a valid permission
echo -e "\nRegistry entries for organization.changeMemberRole:"
rg -nP 'organization\.changeMemberRole' -g 'packages/features/pbac/domain/types/**'

# Show organization permissions section in README.md
echo -e "\nOrganization permissions in README.md:"
sed -n '360,410p' packages/features/pbac/README.md

Length of output: 2154


Add missing “organization.changeMemberRole” permission and support org context
The factory uses "organization.changeMemberRole", but this permission isn’t registered (domain/types, README or tests) and PermissionCheckService only accepts a teamId. Register the new permission, extend the test suite, and update checkPermission to handle organization-level checks (e.g. accept an orgId or dispatch accordingly).

🤖 Prompt for AI Agents
In packages/features/pbac/services/role-management.factory.ts around lines
40-50, the permission "organization.changeMemberRole" is referenced but not
registered and the permission check only supports teamId; register the new
permission constant in the domain/types (and document it in README and tests),
update PermissionCheckService to accept an organization context (e.g., orgId) or
add a separate org check method, then modify checkPermissionToChangeRole to
accept/route when scope === "org" (pass orgId to the permission check with
permission "organization.changeMemberRole") and keep the existing team flow for
scope === "team"; update and add unit tests to cover organization-level
permission checks and the new permission registration.

@sean-brydon sean-brydon enabled auto-merge (squash) September 26, 2025 10:33
@github-actions
Copy link
Contributor

github-actions bot commented Sep 26, 2025

E2E results are ready!

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 teams area: teams, round robin, collective, managed event-types

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants