Skip to content

Add REST API v1 for external PAT clients - #1556

Closed
maxijabase wants to merge 20 commits into
sbpp:mainfrom
srcdslab:feat/rest-api-v1
Closed

maxijabase wants to merge 20 commits into
sbpp:mainfrom
srcdslab:feat/rest-api-v1

Conversation

@maxijabase

Copy link
Copy Markdown
Contributor

Description

This ships a versioned HTTP API at /api/v1 for external clients: bots, website backends, and scripts. It is a separate product from POST /api.php. Panel JavaScript stays on the JSON RPC (cookie JWT + CSRF). REST never reads the panel cookie.

How operators use it

  1. Sign in, open Your account, mint a Personal Access Token (name + expiry, or never). The secret is sbpp_pat_ plus 64 hex characters and is shown once. The panel stores only a SHA-256 hash.
  2. Send Authorization: Bearer sbpp_pat_… on each request.
  3. Pretty URLs: /api/v1/… (Apache rewrite in the prod image and the rebuilt dev image). PATH_INFO fallback always works: /api/v1.php/….
  4. Revoke from the same card if a token leaks.

Tokens inherit that admin's web flags. There are no extra scopes. A read-only bot is an admin with list flags, not a trimmed token. Soft-retired admins (enabled = 0) cannot use a token. Password lockout does not apply. Revoke is the kill switch.

The intended staff-hub flow (website-next): mint an Owner (or Add/Edit/Delete Admins) PAT in the backend, PUT /admins/{steam64} to grant in-game admin, POST /admins/{steam64}/deactivate to demote. Discord stays in the bot. SourceBans does not know about Discord.

Request shape

  • Success: { "data": …, "meta": … } with real HTTP status (200, 201, 400, 401, 403, 404, 409, 429, 500).
  • Errors: { "error": { "code", "message", "field"? } }.
  • Steam64 values in JSON are strings, not numbers.
  • {id} on /admins/{id} is a numeric aid or a 17-digit Steam64 starting with 7. Steam2/Steam3 in the path is 400.
  • POST /bans and /comms length is minutes (0 = permanent). GET length is seconds (what is stored).
  • Writes reuse existing RPC handlers via Api::invoke() where those already exist (ban, unban, comms, comments, servers add/remove/rcon, notes, mods, protests, submissions, admin deactivate/reactivate/remove, rehash). List/get, Steam64 upsert, PATCH /servers, and GET/PATCH /settings are dedicated REST queries.
  • After admin mutate, the panel fires sm_rehash server-side when config.enableadminrehashing is on and puts the result in meta.rehash. If the caller lacks rehash permission, the mutate still succeeds and meta.rehash.attempted is false (the write is not rolled back with a 403).
  • Rate limit: 60 req/min. Token callers keyed by token id, anonymous by IP. 429 includes Retry-After.
  • CORS is off by default. Optional SB_REST_CORS_ORIGINS in config.php for a browser origin. Backend-to-panel calls do not need it.

Auth and isolation (baked into the design)

  • Only Authorization: Bearer sbpp_pat_…. Cookie JWT must not authenticate REST (that would be a CSRF trap in browsers, and would leak IPs on public GET).
  • REST does not start a PHP session. api/v1.php defines SBPP_REST before init.php; CSRF::init() no-ops. Panel POST /api.php still requires CSRF.
  • After PAT bind, Log::init is rebound to the PAT (or anonymous) userbank so audit rows are attributed to the token admin, not a leftover cookie session.
  • A well-formed token that is revoked, expired, unknown, or belongs to a soft-retired admin is 401 on every route, including public GET. Missing or junk Authorization stays anonymous.
  • GET responses never include forbidden fields: admin password / validate / attempts / lockout_until / srv_password, servers.rcon, settings.smtp.pass, settings.telemetry.instance_id.
  • PUT/PATCH of an Owner account requires an Owner editor (same gate as the details page). EDIT_ADMINS alone cannot change an Owner.
  • PUT + Steam64 upserts (create or update + reactivate). PUT + aid 404s if missing. PATCH never creates.
  • Comment create returns the comment cid, not a later audit-log insert id.
  • Anonymous GET /servers matches ?p=servers: enabled hosts only, no group_ids, ignores enabled=, 404 on a disabled {sid}. A PAT may filter enabled= and sees group_ids. rcon is never returned.
  • Anonymous GET /comms is 404 when config.enablecomms is off. A PAT still reads.
  • Public GET /bans and /comms apply the same banlist.hideplayerips / banlist.hideadminname gates as the panel lists.
  • GET comments is public and empty when config.enablepubliccomments is off (admins still see them). DELETE /comments/{id} is Owner.

Schema, rewrite, docs

  • New table :prefix_api_tokens (fresh install in struc.sql, upgrade via updater 812.php). Hash + prefix only.
  • Account RPC: account.tokens_create / list / revoke (panel UI, not REST).
  • Apache rewrite for /api/v1 in prod + dev conf. Docs nginx snippet + HTTP_AUTHORIZATION note.
  • Operator docs: docs/src/content/docs/configuring/rest-api.mdx. OpenAPI: web/api/openapi-v1.yaml, also served at GET /api/v1/openapi.yaml.
  • Registry: Sbpp\Rest\Routes::all(). Adding a write route requires a row in RestPermissionMatrixTest.

Endpoints

Base: /api/v1 or /api/v1.php. All authenticated routes need a PAT unless marked public.

Meta

Method Path Auth Notes
GET /openapi.yaml Public OpenAPI 3 spec

Caller

Method Path Auth Notes
GET /me PAT Admin bound to this token

Admins and groups

{id} is aid or Steam64.

Method Path Auth / perm Notes
GET /admins List/Add/Edit Admins Paginated (page, per_page, cap 100)
GET /admins/{id} List/Add/Edit Admins
PUT /admins/{id} Add/Edit Admins Steam64 upserts (201 create, 200 update, reactivates inactive). Aid 404s if missing
PATCH /admins/{id} Edit Admins Merge. Never creates. Owner targets need an Owner editor
POST /admins/{id}/deactivate Delete Admins Soft retire (enabled=0). Ban history keeps the name
POST /admins/{id}/reactivate Delete Admins Restore
DELETE /admins/{id} Delete Admins Hard delete. Optional reason
GET /groups List Groups or Add/Edit Admins Web groups + SourceMod groups (gid mapping). No group write in this version
POST /system/rehash Add/Edit Admins or Edit Groups Optional { "sids": [1,2] }. Admin mutate already rehashes when the setting is on

Bans

Method Path Auth / perm Notes
GET /bans Public Hide IP / admin name like the panel. PAT sees hidden fields
GET /bans/{bid} Public Same hide-*
POST /bans Add Ban length minutes. Optional kick: true fans RCON (meta.kick)
POST /bans/{bid}/unban Unban flags Requires non-empty ureason

Comm blocks

Method Path Auth / perm Notes
GET /comms Public* kind is mute or gag. Silence is two rows. *404 for anonymous when Comm blocks are off
GET /comms/{cid} Public* Same
POST /comms Add Ban kind: mute, gag, or silence
POST /comms/{cid}/unblock Unban flags Requires ureason
DELETE /comms/{cid} Delete Ban Hard delete

Servers

Method Path Auth / perm Notes
GET /servers Public Enabled hosts, A2S in query, never rcon. Anonymous omits group_ids and ignores enabled=. PAT may filter
GET /servers/{sid} Public Anonymous 404 if disabled. PAT can fetch a disabled host
POST /servers Add Server ip / address, port, mod. enabled defaults true
PATCH /servers/{sid} Edit Servers Merge. Omit rcon to keep the stored password
DELETE /servers/{sid} Delete Servers Hard delete
POST /servers/{sid}/rcon SM RCON or Root and per-server mapping

Notes, mods, queues

Method Path Auth / perm Notes
GET /notes Any web admin Requires ?steam=
POST /notes Any web admin steam + body
DELETE /notes/{nid} Author or Owner
GET /mods, /mods/{mid} List/Add/Edit Mods
POST /mods Add Mods name + folder
DELETE /mods/{mid} Delete Mods Optional ureason
GET /protests, /protests/{pid} Ban Protests Current queue. archived=true for the archive
DELETE /protests/{pid} Ban Protests Hard delete (archiv=0)
GET /submissions, /submissions/{sid} Ban Submissions Current queue. archived=true for archive
DELETE /submissions/{sid} Ban Submissions Hard delete

Comments and settings

Method Path Auth / perm Notes
GET /bans/{bid}/comments, /comms/{cid}/comments Public Empty when public comments are off. Admins still see them
POST /bans/{bid}/comments, /comms/{cid}/comments Any web admin body. Response id is the comment cid
PATCH /comments/{cid} Any web admin body
DELETE /comments/{cid} Owner
GET /settings Web Settings Flat key/value. Never smtp.pass or telemetry.instance_id
PATCH /settings Web Settings Existing keys only. Same forbidden keys

Motivation and Context

External clients (website-next staff hub, Discord bots, scripts) need a real HTTP API with Bearer tokens, real status codes, and a stable resource model. POST /api.php is the panel RPC: cookie + CSRF, chrome envelopes, no PAT. Mixing those clients onto RPC would either leak session auth into browsers or force bots through CSRF. This API is the supported path for that work.

Not in this version (by design): generated password on PUT create, a designed-from-scratch rate limiter beyond the file limiter, comment PATCH author check, OpenAPI completeness polish, kick: true vs enablekickit, blockit fan-out on POST /comms. Group create/edit stays in the panel.

How Has This Been Tested?

  • PHPUnit REST suite (RestAuth, RestAdmins, RestBans, RestComms, RestServers, RestNotes, RestMods, RestProtests, RestSubmissions, RestComments, RestSettings, RestSession, RestPermissionMatrix) plus account token RPC snapshots.
  • Playwright web/tests/e2e/specs/flows/rest-api.spec.ts (mint PAT on Your account, call /me and a write).
  • OpenAPI and operator docs land in this PR. Apache rewrite in prod + dev conf.

Screenshots (if appropriate):

N/A (API + Your account token card). Operator docs at Configuring → REST API.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.

The assistant workflow and CLA.md are unused here. Contributor docs and the PR template no longer tell people to sign.
chore: remove the CLA signing gate
fix(admin-mods): modernize mods list and forms
feat(admin-groups): polish Groups list and edit chrome
* progress towards activating/deactivating admins

* feat(admins): soft-retire, durable issuer names, and bulk actions (sbpp#1509)

Close deactivate/reactivate polish plus admins.bulk with list checkboxes,
keeping ban/comm Admin cells on snapshot names after hard delete.

* fix seeder to insert coherent data in admins

* fix(admins): match reactivate row action to icon-only chrome

Use the same ghost icon button pattern as deactivate/delete so inactive
rows do not show a bordered labeled control.

* feat(frontend): theme single-selects to match multi-select chrome

Replace OS caret inconsistency with a progressive .ssel enhancer sharing Lucide chevron chrome with .msel, and keep narrow grid tracks from overflowing.

* feat(admins): add password visibility toggle on add-admin form

One eye control reveals password and confirm together; generate still resets to masked.

* fix(frontend): flip themed select panels when they would overflow

Open upward when there is more room above the trigger, and shrink max-height to the available viewport space.

* switch add-admin server access to multiselects

* fix missing mb-3 spacing on admins list

* rehash admins after deactivate/reactivate

* document windows powershell docker compose

* theme bulk admin group selects

* let dialog selects escape palette clip

* keep filter select widths on themed wrap

* mirror flex sizing onto themed select wrap

* unify admins bulk action button chrome

* fix mobile admins bulk select chrome

* replace native confirm with panel dialogs

* use multiselects on edit admin servers

* fix ci

* revert format version bump to 1
Publish the production image to ghcr.io/srcdslab/sourcebans-pp, including a rolling :main tag on pushes to main. Stop dispatching docs deploys into sbpp/sbpp.github.io. Point panel and operator-facing links at this repo.
External bots and website-next need PAT-authenticated REST instead of the panel RPC. Slice 0 covers admins, groups, and rehash.
@maxijabase
maxijabase requested a review from a team August 31, 2026 22:57
@maxijabase

Copy link
Copy Markdown
Contributor Author

Opened against the wrong remote; this change is going to srcdslab/sourcebans-pp main.

@maxijabase maxijabase closed this Aug 31, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 31, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant