Skip to content

feat(auth): upload approval scopes — public / private / all variations - #309

Open
lopugit wants to merge 1 commit into
developfrom
claude/upload-approval-scopes
Open

feat(auth): upload approval scopes — public / private / all variations#309
lopugit wants to merge 1 commit into
developfrom
claude/upload-approval-scopes

Conversation

@lopugit

@lopugit lopugit commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Upload approval: public / private / all variations

Follow-up to the merged signup-permissions hotfix (#301). That change withheld every upload start behind a single flag — so an unapproved new user couldn't set an avatar or attach a file in a DM either. This PR keeps its foundation (flag shape, endpoint, Users-tab UI, tests, verify-time admin email, grandfathering, admin bypass) and splits the permission into the three approval variations:

Scope Covers Flag
public post, comment, custom-emoji attachments meta.publicUploads (existing)
private message attachments + own profile avatar/banner meta.privateUploads (new)
all both in one write both flags

What changed

  • Registration stamps both flags false; both are privileged meta keys (self-grant stripped). Email verification still grants neither.
  • Upload start gate is purpose-aware: 403 public_uploads_not_approved or private_uploads_not_approved depending on the requested purpose. Lifecycle routes (parts/complete/abort/delete) stay ungated as before, so a mid-upload revoke can't strand a reservation.
  • POST /api/v1/admin/users/public-uploads accepts scope: 'public' | 'private' | 'all' — defaults to public, wire-compatible with the pre-scope body. Unknown scope → 400.
  • /admin → Users: the Uploads control becomes an Approve ▾ menu (enable/withhold per scope + Enable all / Withhold all) with a summary badge (all / public / private / pending / off), per-scope pending flags in the query fields, and the pending banner counts accounts missing either scope.
  • Admin "new user" email copy now says both scopes are withheld and points at the Approve menu.
  • Grandfathering unchanged: pre-hotfix accounts (keys absent) and admins keep uploading — no migration.

Verification

  • npm run test:attachments: 108/108 pass, including new tests for per-purpose scope gating (each of the 6 purposes → correct 403 code; each scope grants only its own purposes; unknown purpose falls through to service validation; lifecycle routes stay ungated) and the independent private tri-state.
  • Live dev stack: registered a user with meta: { publicUploads: true, privateUploads: true, admin: true } → all stripped, both scopes withheld; verified email → still withheld; per-purpose 403 codes confirmed for all six purposes; admin round-trips for private-only, legacy scope-omitted (public-only), all, and withhold-all — gates open/close per scope each time; bad scope 400; non-admin 403; admin.new_user outbox email carries the updated copy.
  • Admin UI in the browser: pending badge + banner, Approve menu labels flip per state (Withhold private uploads after enabling private; Withhold all disabled while both off), optimistic badge updates with Lopu toasts, server state confirmed after each action, no horizontal overflow.
  • Targeted ESLint clean on all changed files.

🤖 Generated with Claude Code

Builds on the merged signup-permissions hotfix (#301), which withheld ALL
upload starts behind one flag. The permission is now two independent
tri-state flags so an admin can approve each variation per user:

- public (meta.publicUploads): post, comment, custom-emoji attachments
- private (meta.privateUploads, new): message attachments + own profile
  avatar/banner
- all: both flags in one write

Registration stamps BOTH false (both are privileged meta keys, so a signup
body cannot self-grant either); email verification still grants neither.
The upload start gate is now purpose-aware and answers 403
public_uploads_not_approved or private_uploads_not_approved by requested
purpose. Grandfathering (absent key = allowed) and the admin bypass are
unchanged, so pre-hotfix accounts and admins keep uploading.

POST /api/v1/admin/users/public-uploads accepts scope
'public' | 'private' | 'all' (default 'public' — wire-compatible with the
pre-scope body). The /admin Users tab control becomes an Approve menu with
per-scope enable/withhold + enable/withhold-all actions, a scope-summary
badge (all/public/private/pending/off), per-scope pending query fields, and
the pending banner counts accounts missing either scope. The "new user"
admin email now says both scopes are withheld and which menu to use.

Verified: 108-test attachments suite (extended for scope purposes + an
independent private tri-state), lint, and a live dev stack — register
(self-grant of both keys stripped), verify-email keeps both withheld,
per-purpose 403 codes, private-only/public-only/all/withhold-all round
trips via API and via the admin UI menu (optimistic badge + toasts),
scope-omitted legacy behavior, bad scope 400, non-admin 403, admin outbox
email copy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
thingtime Ready Ready Preview Aug 18, 2026 8:43am
thingtime (develop) Ready Ready Preview Aug 18, 2026 8:43am

Request Review

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

✅ Develop S3 preview ready

The alias passed the develop bucket CORS preflight and a final live PR/SHA fence.

Generic Vercel Preview deployments use the shared development runtime; this controller adds the stable exact-SHA alias and marker-scoped cleanup.

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