Skip to content

fix(api): use a dedicated forbidden error for role-authority denials#6517

Merged
otavio merged 1 commit into
masterfrom
fix/member-authority-error-semantics
Jun 20, 2026
Merged

fix(api): use a dedicated forbidden error for role-authority denials#6517
otavio merged 1 commit into
masterfrom
fix/member-authority-error-semantics

Conversation

@otavio

@otavio otavio commented Jun 20, 2026

Copy link
Copy Markdown
Member

What

Replaces the misleading NewErrRoleInvalid() returned by role-authority guards
with a dedicated NewErrRoleForbidden() error, so an authorization denial is no
longer reported as a malformed-role-value error. HTTP status is unchanged (403).

Why

The guards in api/services returned NewErrRoleInvalid() ("role is invalid")
when the caller lacked authority over the target role — a forbidden
condition, not a validation failure. This conflated two distinct failure modes
under one error, misleading API consumers and obscuring logs/telemetry.

Across both shellhub and cloud, every call site of NewErrRoleInvalid() was
an authority guard; there were no genuine "invalid role value" uses, so the
symbol is removed entirely.

Fixes: shellhub-io/team#155

Changes

  • api/services/errors.go: add ErrRoleForbidden + NewErrRoleForbidden()
    (ErrCodeForbidden → 403, matching the existing ErrAuthForbidden family);
    remove the now-dead ErrRoleInvalid / NewErrRoleInvalid().
  • api/services/member.go: migrate the four HasAuthority guards
    (AddNamespaceMember, UpdateNamespaceMember ×2, RemoveNamespaceMember).
  • api/services/api-key.go: migrate the CreateAPIKey guard; split the
    UpdateAPIKey guard so a non-member returns NewErrNamespaceMemberNotFound
    (404) instead of conflating member-not-found with the authority denial (403).
  • tests: update service + route assertions to the new error; add an
    UpdateAPIKey member-not-found case (service + route-layer 404).

Coordination

This is the primary of two coordinated PRs. The companion is
shellhub-io/cloud#2407 (same branch name fix/member-authority-error-semantics)
and migrates cloud's call sites. Merge this shellhub PR first — cloud resolves
the services package from the shellhub module via a local replace directive,
so removing the symbol here breaks cloud's build until cloud is also merged.

Testing

go test ./api/services/ ./api/routes/ -run 'Member|APIKey|Errors' -tags docker
— all authority cases assert NewErrRoleForbidden(); the new non-member case
returns 404.

The role-authority guards returned NewErrRoleInvalid() when the caller
lacked authority over the target role — a forbidden condition wrongly
reported as a malformed-role-value error, which misleads consumers and
obscures telemetry. Replace those guards with a dedicated
NewErrRoleForbidden() (still 403) and drop the now-unused
NewErrRoleInvalid/ErrRoleInvalid.

Also split the UpdateAPIKey guard so a non-member returns 404 via
NewErrNamespaceMemberNotFound instead of conflating member-not-found
with the authority denial.

Fixes: shellhub-io/team#155
@otavio otavio requested a review from a team as a code owner June 20, 2026 12:00
@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown

Claude finished @otavio's task in 12m 27s —— View job


Code Review Complete

Reviewed 6 files across code quality, security, testing, Go patterns, and architecture — no issues found. The code looks good as-is.

Highlights:

  • NewErrRoleForbidden() correctly follows the NewErrForbidden(ErrXxx, nil) pattern used by NewErrAuthForbidden(), which is an improvement over the old NewErrRoleInvalid() that returned a bare sentinel.
  • The UpdateAPIKey guard split properly separates member-not-found (404) from authority-denied (403) — req.UserID comes from the authenticated JWT, so the 404 doesn't enable member enumeration.
  • All references to the removed ErrRoleInvalid/NewErrRoleInvalid() symbols are gone. The cloud companion PR already uses NewErrRoleForbidden() at all 4 call sites.
  • Both new error paths (member-not-found and authority-denied) have service-layer and route-layer tests.

To request another review round, comment /review.


@otavio otavio merged commit 47eb7b0 into master Jun 20, 2026
36 checks passed
@otavio otavio deleted the fix/member-authority-error-semantics branch June 20, 2026 12:22
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.

2 participants