Address Gadfly review on #16: RemoveShare choke point + dedup
Build image / build-and-push (push) Successful in 5s
Build image / build-and-push (push) Successful in 5s
- RemoveShare now routes through requireGardenRole(roleViewer) — the standard authorization choke point (masks existence for non-participants) — then applies the owner-or-self rule on top (a participant removing someone else's share is now ErrForbidden, not ErrNotFound). No more bespoke GetGarden+manual check. - Add domain.RoleOwner constant; gardenRole.String() and the tests use it instead of the bare "owner" literal. - UpdatePlanting fetches the plant once and only re-checks visibility when the plant id actually CHANGES (new ≠ old), so a no-op plantId resend can't break a shared editor editing a plop that uses the owner's private plant. - Bound ListSharesForGarden with a LIMIT backstop. Skipped: AddShare email-existence disclosure (the issue explicitly accepts it as inherent to email-based sharing), 404-on-missing-share (correct DELETE semantics), and the join-scan/test-helper dedup nits. GOWORK=off go build/vet/test ./internal/... green. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi
This commit is contained in:
@@ -61,15 +61,17 @@ func (s *Service) UpdateShareRole(ctx context.Context, actorID, gardenID, target
|
||||
}
|
||||
|
||||
// RemoveShare revokes a share. The garden owner may remove anyone; a recipient
|
||||
// may remove themselves ("leave garden"). Any other actor gets the garden's
|
||||
// masked ErrNotFound (existence isn't revealed to non-participants).
|
||||
// may remove themselves ("leave garden"). It routes through requireGardenRole
|
||||
// (roleViewer) so a non-participant gets the standard masked ErrNotFound, then
|
||||
// applies the owner-or-self rule on top (a participant removing someone else's
|
||||
// share is ErrForbidden — they can already see the garden).
|
||||
func (s *Service) RemoveShare(ctx context.Context, actorID, gardenID, targetUserID int64) error {
|
||||
g, err := s.store.GetGarden(ctx, gardenID)
|
||||
g, err := s.requireGardenRole(ctx, actorID, gardenID, roleViewer)
|
||||
if err != nil {
|
||||
return err // ErrNotFound
|
||||
return err // ErrNotFound for a non-participant
|
||||
}
|
||||
if g.OwnerID != actorID && actorID != targetUserID {
|
||||
return domain.ErrNotFound
|
||||
return domain.ErrForbidden
|
||||
}
|
||||
// Owner path removes any share; self-leave removes the actor's own (a missing
|
||||
// row is ErrNotFound either way).
|
||||
|
||||
Reference in New Issue
Block a user