feat(errors): translate infra-api blocked-team 403s into friendly text#325
feat(errors): translate infra-api blocked-team 403s into friendly text#325drankou wants to merge 1 commit into
Conversation
infra-api returns 403 with a 'team is blocked[: <reason>]' wire format for mutations a blocked team isn't allowed to perform. The dashboard previously collapsed every 403 into the generic 'You are not authorized to access this resource' string, so users never saw the actual reason (billing limit, verification required, missing payment method). This change recognizes the wire format inside the central server error adapter and translates it into the user-facing strings the dashboard already maintains via getBlockedMessage. Because TRPCError.message and ActionError.message both flow through getPublicRepoErrorMessage, every existing toast call site automatically picks up the friendly text — no mass refactor needed. What's added: - TEAM_BLOCKED_MESSAGE_PREFIX constant + isTeamBlockedError and extractBlockedReason helpers in core/shared/errors.ts - getPublicErrorMessage gains an optional 'message' field and branches on isTeamBlockedError before its existing code/status logic - getPublicRepoErrorMessage forwards error.message so the new branch fires for repo-level 403s - getBlockedReasonText slug-less variant in team-blocked-message; the existing getBlockedMessage delegates to it for the 'text' field - 'billingLimit' added to TEAM_BLOCKED_REASONS for typeable contract - 22 new unit tests covering detection, extraction, translation, status filtering, and the not-found/validation/conflict pass-through Non-blocked-team 403s keep their existing obfuscated message. Pairs with infra/packages/api per-endpoint enforcement work in feat/blocked-team-intent-enforcement.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0689634514
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| message?: string | null | ||
| }): boolean { | ||
| if (input.status !== 403 || !input.message) return false | ||
| return input.message.toLowerCase().startsWith(TEAM_BLOCKED_MESSAGE_PREFIX) |
There was a problem hiding this comment.
Require a delimiter before treating 403s as blocked-team
When a 403 message starts with the same words but is not the documented team is blocked[: <reason>] wire format, such as team is blocked from accessing this resource, this predicate still returns true. getPublicErrorMessage then calls extractBlockedReason and returns the raw suffix instead of the generic forbidden message, widening the blocked-team exception and potentially exposing backend 403 details; this should only match the exact bare prefix or a colon-delimited reason.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
LGTM — focused error-translation change with comprehensive unit tests; existing callers that don't pass message continue to fall through to the generic forbidden message.
Extended reasoning...
Overview
This PR translates infra-api 403 team is blocked[: <reason>] responses into the existing friendly user-facing messages. Touches src/core/shared/errors.ts (adds isTeamBlockedError, extractBlockedReason, and threads message through getPublicErrorMessage/getPublicRepoErrorMessage), src/features/dashboard/team-blocked/team-blocked-message.ts (extracts getBlockedReasonText and refactors getBlockedMessage to use it), and the teams constants (adds billingLimit). Two test files cover the new behavior with ~25 cases total.
Security risks
None. The translation only exposes user-facing strings that infra-api already intended to be shown (billing/verification/payment reasons). The pattern-matching is anchored to a 403 + literal prefix, so it cannot accidentally surface other 403 bodies — non-matching 403s fall through to the generic forbidden message as before. No injection vector since the message is used as a returned string, not interpolated into HTML/SQL.
Level of scrutiny
Low. This is a narrow user-experience improvement in the central error adapter with explicit unit-test coverage of fall-through paths (non-403, missing message, unknown reason, etc.). The refactor of getBlockedMessage is behavior-preserving — it now delegates the text computation to getBlockedReasonText but produces identical {text, cta, href} outputs as before.
Other factors
The new dependency from core/shared/errors.ts into features/dashboard/team-blocked/team-blocked-message.ts reverses the usual features → core layering direction, but it's safe (no import cycle since the team-blocked module doesn't import errors). Callers like src/core/server/actions/utils.ts and src/core/server/functions/sandboxes/get-team-metrics.ts that don't pass message are unaffected because the parameter is optional.
Pairs with the infra-api
ActionIntentPR. infra-api returns 403 "team is blocked[: ]" for mutations a blocked team can't perform. The dashboard previously collapsed every 403 into "You are not authorized to access this resource", hiding the actual reason from the user.This recognizes the wire format inside the central error adapter and translates it into the existing user-facing messages.