Skip to content

Admin Console

The Admin Console is at /dashboard/admin on the Singularity Marketplace site. It exposes two tabs (Users, Packages) backed by admin-scoped endpoints under /api/v1/admin/... and an audit-log search. Access requires publishers.is_admin = TRUE AND a MFA factor enrolled AND an interactive web session — API keys are blocked on every write.

CapabilityUIBackend
List/search publishers (incl. disabled / deleted / banned)/dashboard/admin/usersGET /api/v1/admin/publishers
Inspect a publisher (audit history, current license, ban status)drawerGET /api/v1/admin/publishers/{id}
Disable / re-enable an accountdrawer actionPATCH /api/v1/admin/publishers/{id}
Soft-delete an account (PII-scrub, cascade-disable packages)drawer actionDELETE /api/v1/admin/publishers/{id}
Hard-delete an account (30-day buffer + dual-control)drawer actionDELETE /api/v1/admin/publishers/{id}?hard=true
Change a publisher’s Snippbot license tierdrawer dropdownPATCH /api/v1/admin/publishers/{id}/license
List/search packages (incl. unpublished / admin-disabled)/dashboard/admin/packagesGET /api/v1/admin/packages
Admin-disable / re-enable a packagedrawer actionPOST /api/v1/admin/packages/{id}/{disable,enable}
Hard-delete a package (dual-control when installs > 100)drawer actionDELETE /api/v1/admin/packages/{id}
Yank / unyank a versionper-version buttonPOST /api/v1/admin/packages/{id}/versions/{v}/{yank,unyank}

The first admin must be set in the database directly. There is no self-promotion path via HTTP.

Terminal window
psql -d singularity_registry <<SQL
UPDATE publishers SET is_admin = TRUE WHERE name = 'your-handle';
SQL

Notes:

  • A trigger in migration 033 flips mfa_required = TRUE automatically when is_admin becomes TRUE. The admin must enrol a TOTP or WebAuthn factor at /dashboard/settings/mfa before they can log in again.
  • mfa_required is sticky — once TRUE it can never go back to FALSE. Demoting an admin keeps the MFA requirement (prevents the “demote then re-promote without MFA” attack).
  • The grant operation should run through pg_audit / log_statement = 'all' so it leaves a Postgres-level trail.
  • Always keep ≥ 2 admins. The application refuses to disable, delete, or demote the last remaining active admin.

Every destructive action passes through six gates before it touches the database:

  1. Admin guard_require_admin(request, write=True) rejects unauthenticated and non-admin requests.
  2. API-key block — admin writes refuse Authorization: Bearer sgty_… API keys. Reads still allow API keys.
  3. CSRF — double-submit token (singularity_csrf cookie ↔ X-CSRF-Token header).
  4. Sudo-mode — a fresh password challenge issued via POST /api/v1/auth/sudo produces a 5-minute one-shot token sent in X-Admin-Sudo-Token.
  5. Rate limits — per-admin per-action caps (e.g. 20 disables/hour, 1 hard-delete/day). Returns 429 with Retry-After.
  6. Self/last-admin guards — refuses self-actions and never lets the platform reach zero active admins.

Hard-delete (publishers) and high-impact package delete (> 100 installs) additionally require dual control: one admin submits via POST /api/v1/admin/pending-actions, a different admin approves via POST /api/v1/admin/pending-actions/{id}/approve, then the original endpoint is called with pending_id=.

StateReversible byPII intactCan re-registerAffects
Disabledany adminyesn/a (same account)login + UI visibility
Banned (via ban_violation)any adminyesNO (banned_identifiers)login + email/IP re-registration
Soft-deletedany admin (within 30 days)NO (scrubbed)NO (name reserved)account removed from app, recoverable manually
Hard-deletednobodyNOdepends on force_reuse flagrow permanently removed

The same vocabulary applies to packages: admin-disable (reversible, distinct from owner-initiated unpublish) → admin-delete (hard, version history retained in audit_logs only).

Every successful write inserts exactly one row into audit_logs. The details JSONB column stores the operator’s stated reason verbatim plus action-specific metadata (e.g. cascade summary on a publisher delete). Reads of detail endpoints also write audit rows so slow exfiltration is traceable.

Schema enforcement:

  • The application DB role has only INSERT and SELECT on audit_logs. UPDATE and DELETE are blocked at the role grant level.
  • A BEFORE UPDATE OR DELETE trigger raises an exception as defence-in-depth so the policy survives a future GRANT mistake.
  • Hard-deleting audit rows requires DBA-level access AND is itself logged at the Postgres WAL layer.

Query the log via the existing endpoint:

Terminal window
curl -H "Authorization: Bearer $TOKEN" \
"https://api.singularitymarketplace.com/api/v1/admin/audit-log?resource_type=publisher&action_prefix=publisher.disabled"
  • Impersonation — deliberately deferred. High blast radius; needs its own plan.
  • Admin-promote/demote UI — promotion stays a psql operation (small surface area, easier to audit).
  • GDPR data-export — handled by snippbot export on the daemon side, not the registry.
  • Per-package permissions — admin powers are coarse-grained. Fine-grained permissions are a separate proposal.