Skip to content

fix(api): gate suppressDomain behind an admin role check - #32

Closed
ghost wants to merge 1 commit into
mainfrom
unknown repository
Closed

fix(api): gate suppressDomain behind an admin role check#32
ghost wants to merge 1 commit into
mainfrom
unknown repository

Conversation

@ghost

@ghost ghost commented Aug 5, 2026

Copy link
Copy Markdown

Why

google.suppressDomain is a destructive, workspace-global mutation: with purge: true it hard-deletes every email thread and calendar event for the target company and permanently silences that domain for auto-create/notification matching across the whole workspace. Unlike every other destructive workspace mutation, it had no role check — any signed-in member could call it, deleting data they do not own.

Compare the repo's own guard for workspace administration: workspace.setMemberRole is gated by canChangeRole (owner/admin only) in workspace.service.ts.

What

  • suppressDomain now takes the caller's userId, resolves their workspace role, and rejects non-owner/admin callers with ForbiddenException before touching anything.
  • The router passes ctx.user.id through.
  • The role resolution reuses the same isWorkspaceAdmin + isWorkspaceRole helpers from @crm/auth that the workspace service already relies on, so the authorization model stays in one place.

Tests

New apps/api/test/google-connection.spec.ts:

  • a member is refused
  • a user with no workspace membership is refused
  • an admin may suppress without purging
  • an owner may suppress and purge (verifies the delete counts flow through)

Verified: bun run check-types, bunx biome check on changed files, and the new spec all pass.


Summary by cubic

Gated the destructive google.suppressDomain mutation behind an owner/admin role check to prevent non-admin members from deleting workspace data. Router now passes the caller userId; non-admins receive a ForbiddenException.

  • Bug Fixes
    • Enforced role check using isWorkspaceAdmin/isWorkspaceRole from @crm/auth in suppressDomain.
    • Service now requires userId, resolves workspace role, and rejects non-owner/admin callers.
    • Router updated to pass ctx.user.id to the service.
    • Added tests covering refusal for members/no-membership and allow for admin/owner (with purge counts).

Written for commit 9f36c25. Summary will update on new commits.

Review in cubic

@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Comp AI - PoC Team on Vercel.

A member of the Team first needs to authorize it.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/api/src/google/google-connection.service.ts">

<violation number="1" location="apps/api/src/google/google-connection.service.ts:234">
P3: The new roleOf/toRole code in GoogleConnectionService duplicates the workspace-role resolution already present in WorkspaceService.roleOf/toRole (and inline in SsoService.roleOf). The PR correctly centralizes the authorization predicates (isWorkspaceAdmin/isWorkspaceRole in @crm/auth), but the member lookup query itself is still copy-pasted across three services. Since this is the exact access-control path used to gate a destructive workspace-global operation, keeping three divergent copies is risky — a future change to how workspace membership/roles are resolved could update one service and silently leave suppressDomain (or another gate) out of sync. Consider extracting the role lookup (e.g. resolveWorkspaceRole(userId)) into @crm/auth alongside isWorkspaceAdmin so all role-gated mutations share one implementation.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

where: {
organizationId_userId: { organizationId: WORKSPACE_ID, userId },
},
select: { role: true },

@cubic-dev-ai cubic-dev-ai Bot Aug 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The new roleOf/toRole code in GoogleConnectionService duplicates the workspace-role resolution already present in WorkspaceService.roleOf/toRole (and inline in SsoService.roleOf). The PR correctly centralizes the authorization predicates (isWorkspaceAdmin/isWorkspaceRole in @crm/auth), but the member lookup query itself is still copy-pasted across three services. Since this is the exact access-control path used to gate a destructive workspace-global operation, keeping three divergent copies is risky — a future change to how workspace membership/roles are resolved could update one service and silently leave suppressDomain (or another gate) out of sync. Consider extracting the role lookup (e.g. resolveWorkspaceRole(userId)) into @crm/auth alongside isWorkspaceAdmin so all role-gated mutations share one implementation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/google/google-connection.service.ts, line 234:

<comment>The new roleOf/toRole code in GoogleConnectionService duplicates the workspace-role resolution already present in WorkspaceService.roleOf/toRole (and inline in SsoService.roleOf). The PR correctly centralizes the authorization predicates (isWorkspaceAdmin/isWorkspaceRole in @crm/auth), but the member lookup query itself is still copy-pasted across three services. Since this is the exact access-control path used to gate a destructive workspace-global operation, keeping three divergent copies is risky — a future change to how workspace membership/roles are resolved could update one service and silently leave suppressDomain (or another gate) out of sync. Consider extracting the role lookup (e.g. resolveWorkspaceRole(userId)) into @crm/auth alongside isWorkspaceAdmin so all role-gated mutations share one implementation.</comment>

<file context>
@@ -204,4 +225,19 @@ export class GoogleConnectionService {
+			where: {
+				organizationId_userId: { organizationId: WORKSPACE_ID, userId },
+			},
+			select: { role: true },
+		});
+
</file context>
Fix with cubic

Repository owner closed this by deleting the head repository Aug 5, 2026
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