Skip to content

feat(auth): [main hotfix] upload approval scopes — public / private / all variations - #310

Merged
lopugit merged 1 commit into
mainfrom
claude/upload-approval-scopes-main
Aug 18, 2026
Merged

feat(auth): [main hotfix] upload approval scopes — public / private / all variations#310
lopugit merged 1 commit into
mainfrom
claude/upload-approval-scopes-main

Conversation

@lopugit

@lopugit lopugit commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Upload approval: public / private / all variations — main hotfix

Main-targeted twin of #309 (same single commit, cherry-picked onto main so no develop-only history rides along). Follow-up to the merged signup-permissions hotfix (#301): that change withheld every upload start behind one 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 re-run on THIS main-based branch: 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.
  • The identical commit was verified live on a dev stack for feat(auth): upload approval scopes — public / private / all variations #309: register with a malicious meta body (both keys stripped), verify email (still withheld), per-purpose 403 codes for all six purposes, admin round-trips for private-only / legacy scope-omitted (public-only) / all / withhold-all, bad scope 400, non-admin 403, updated admin.new_user outbox email copy, and the admin UI Approve menu exercised in the browser (optimistic badge + toasts, no 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 12:49pm

Request Review

@lopugit
lopugit enabled auto-merge August 18, 2026 12:50
@lopugit
lopugit merged commit 440c30d into main Aug 18, 2026
38 checks passed
lopugit added a commit that referenced this pull request Aug 18, 2026
…lidation

PR #310 split the upload permission into public/private/all scopes with a
purpose-aware start-only gate. Integrate this branch onto that model: adopt
requireUploadPermission wholesale (drop the purpose-blind requireUploadGrant
option, its parts/complete opt-ins, and its superseded test — the blind gate
would have blocked #310's private-only accounts), redefine canUploadMediaDoc
as the any-scope alias (public !== false || private === true || admin) so a
pre-scope account stamped only publicUploads:false stays withheld, make the
composer's approval-pending card purpose-aware (public purposes check
publicUploadsEnabled, message/profile purposes check privateUploadsEnabled,
any-scope fallback for stale payloads), key the friendly client copy to the
scoped 403 codes, and remove the resurrected set-media-upload docs entry +
useApi method whose endpoint no longer exists. attachments 108/108, full
nitro build green, lint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lopugit added a commit that referenced this pull request Aug 18, 2026
…tion branch

Brings the #310 public/private/all scope model + the base branch's
integration (purpose-aware gate only, any-scope canUploadMedia alias,
purpose-aware composer card) into the moderation stack. Conflict
resolutions keep the moderation additions (admin moderation useApi methods,
admin-moderation docs entry, TESTING drill, changelog entry) on top of the
scoped-gate versions; the stale set-media-upload docs entry and useApi
method stay deleted. attachments 108/108, moderation 10/10, full nitro
build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lopugit added a commit that referenced this pull request Aug 18, 2026
…I rebase

The rebase replayed pre-integration commits, resurrecting the purpose-blind
requireUploadGrant gate (which 403s #310 private-only accounts on message/
profile uploads), the single-flag canUploadMediaDoc + its
userPublicUploadsEnabled delegation, the purpose-blind composer card, and
the dead set-media-upload docs entry/useApi method. Restore the verified
integrated versions (commit 780a385 content): purpose-aware gate only,
any-scope alias predicate, purpose-aware composer card, scoped error copy,
scoped tests. attachments 111/111, full nitro build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lopugit added a commit that referenced this pull request Aug 18, 2026
Re-syncs after the AI rebase force-rewrote the base and the restoration
commit df008d3 re-applied the #310 integration there. Doc/test conflicts
resolved to the moderation side (a strict superset); graphify-out taken
one-sided per repo rule. attachments 111/111, moderation 10/10, nitro
build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lopugit added a commit that referenced this pull request Aug 18, 2026
…e AI rebase

The rebase of this branch re-introduced the purpose-blind requireUploadGrant
option/branch/route opt-ins (near-harmless under the any-scope predicate but
contradicting #310's start-only purpose-aware design), plus the dead
set-media-upload docs entry, useApi method, and the AdminDashboard Grant
media button whose endpoint no longer exists (runtime 404s). Take the base
branch's clean gate/route/test files and surgically remove the dead admin
surface while keeping the moderation tab/methods. attachments 111/111,
moderation 10/10, nitro build green, lint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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