Skip to content

Historical snapshot archived 2026-09-25. This records an earlier review or plan, not current implementation or live ticket state. For current work, follow root AGENTS.md, the relevant BloxClips skill, and owning repository source/tests. Preserve approved decisions as evidence; verify their present authority before acting.

Permissions & Actors ​

Who can do what with submissions.


Actors ​

ActorDescriptionAuth Method
ClipperAuthenticated user submitting videosCookie/JWT (requireAuth)
AdminStaff with admin flagrequireAdmin middleware
System WorkerBackground schedulerInternal (no auth)
Discord BotLegacy (removed for submissions)—

Note: No separate "moderator" or "staff" role for submissions — only admin/non-admin.


Permission Matrix ​

ActionClipperAdminSystemNotes
Create submission✅✅ (via addedByAdmin)❌Clipper: own campaigns only. Admin: manual add (no UI)
View own submissions✅✅❌Backend: userOwnedWhere (webUserId OR discordId)
View all submissions❌✅❌Admin queue (/admin/submissions/review)
View submission detail✅ (own)✅❌Clipper: history page. Admin: user-detail, campaign-detail
Accept submission❌✅❌PUT /admin/submissions/:id/status {ACCEPTED}
Deny submission❌✅❌PUT /admin/submissions/:id/status {DENIED}
Flag submission❌✅✅ (auto)Admin: manual. System: 3 scrape failures
Re-review (re-open)❌✅❌Set status back to PENDING
Delete submission❌✅❌DELETE /api/admin/submissions/:id (exists, no ownership check)
Update manual views❌✅❌PUT /admin/submissions/:id/views
Update custom rate❌✅❌PUT /admin/submissions/:id/rate
Update custom cap❌✅❌PUT /admin/submissions/:id/cap
Request payout✅❌❌POST /api/payouts/request (own submissions only)
View payout history✅✅❌Own / all
Review payout items❌✅❌Admin payout review tab
Approve payout❌✅❌POST /admin/payouts/review/:id/approve
Send payout❌✅ (TOTP)❌POST /admin/payouts/review/:id/send
Trigger tracking poll❌❌✅Scheduler (30 min)
Budget clamp/freeze❌❌✅Automatic in tick/rescrape
Campaign auto-close❌❌✅checkAndCloseCampaign (95%)

Authorization Implementation ​

Clipper Access Control ​

Backend: src/api/routes/submissions.ts:11-15

typescript
function userOwnedWhere(req) {
  const clauses = [{ webUserId: req.user!.userId }];
  if (req.user!.discordId) clauses.push({ userId: req.user!.discordId });
  return { OR: clauses };
}

Used in: GET /api/submissions, POST /api/submissions (implicit via req.user)

Admin Access Control ​

Middleware: src/api/middleware/adminAuth.ts

typescript
export function requireAdmin(req, res, next) {
  if (!isAdminRequestUser(req.user)) return res.status(403).json({error: 'Admin required'});
  next();
}

Check: isAdminRequestUser → checks req.user.isAdmin or Discord role

Applied to: All /api/admin/* routes

System Worker (No Auth) ​

Scheduler: src/scheduler.ts — runs on server startup, no auth context
Functions: runTrackingTick, checkAndCloseCampaign, rescrapeForPayout — called internally


Frontend vs. Backend Enforcement Gaps ​

ActionFrontend GateBackend EnforcementGap
Submit videoAuth + campaign selectrequireAuth + campaign checksNone
View own submissionsAuthuserOwnedWhereNone
Admin queueAdmin nav linkrequireAdminNone
Accept/Deny/FlagAdmin UI buttonsrequireAdmin + auditNone
Manual view overrideAdmin user detailrequireAdminNone
Custom rate/capAdmin user detailrequireAdminNone
Delete submissionAdmin user detail buttonrequireAdmin + auditLogExists but no ownership check
View all submissionsAdmin onlyrequireAdminNone
Request payoutAuth + balance checkrequireAuth + ownershipNone

Ownership Model ​

Submission Ownership ​

  • Primary: webUserId (WebUser CUID) — preferred, set on create
  • Legacy: userId (Discord ID) — fallback for pre-webUser submissions
  • Query: userOwnedWhere checks BOTH

Campaign Ownership ​

  • No per-user ownership — campaigns are global
  • acceptingSubmissions + active + isDeleted gates submission creation

Payout Ownership ​

  • Payout.webUserId (preferred) or userId (legacy)
  • User scope: userScopeOr(req) checks both

Audit Trail ​

Every admin action on submissions creates AdminAuditLog:

  • Status change: SUBMISSION_ACCEPTED/DENIED/FLAGGED/PENDING
  • View update: UPDATE_SUBMISSION_VIEWS
  • Rate update: UPDATE_SUBMISSION_RATE
  • Cap update: UPDATE_SUBMISSION_CAP

Fields: adminId, action, resourceType=SUBMISSION, resourceId, details, ipAddress, userAgent


Special Cases ​

Cross-User Submission (Admin Manual Add) ​

  • Field: Submission.addedByAdmin: Boolean
  • No frontend UI — would require direct DB insert or API call
  • Still subject to campaign rules (platform, budget)

Orphan Submissions (Pre-WebUser) ​

  • webUserId = null, only userId (Discord ID)
  • userOwnedWhere handles via discordId fallback
  • Admin user list shows "orphan" users (submissions without WebUser)

Banned Users ​

  • Ban model + IdentityBlacklist block submission creation?
  • Not enforced in POST /api/submissions — banned users can still submit
  • Ban only blocks Discord DM reports and login

Missing Permissions ​

Missing CheckRisk
Banned user can submitFraud/abuse
No rate limit on submission createSpam
No ownership check on DELETE (if implemented)Data loss
Admin can accept own submission?Conflict of interest (no self-check)
No "moderator" role — only adminOver-privileged staff