docs: refresh api.md, protocol.md, schema.md against current code (D7)

One-PR spec refresh per decision D7, using the 2026-07-19 audit's
conformance matrix as the checklist:

api.md
- Remove the deleted version field from /health and /api/v1/info
  (anti-fingerprinting C-2) and document the removal.
- Document the profile surface (PATCH /users/me, PUT /users/me/password,
  GET/DELETE /users/me/sessions), the user-blocks surface
  (GET/PUT/DELETE /api/v1/blocks), and the plugin admin surface
  (/api/v1/admin/plugins).
- Correct GET /api/v1/files/{id}: auth is required and caching is
  'private, no-cache' (was documented as public/immutable, unauthenticated).
- Add search (30/min) and upload (10/min) rate limits; note announcement
  channel type as planned-only.

protocol.md
- Document auth_ok.replay_source and the 3-tier reconnect replay
  (ring buffer -> events table -> full resync) with the visibility
  watermark.
- Add the Voice End-to-End Encryption section (voice_e2ee_announce/offer
  in both directions, key-holder semantics, rate limits) and
  voice_token.is_key_holder.
- Document user_update; mark voice_speakers and member_leave as defined
  but not currently emitted; extend voice_config fields.
- Update the reference tables (19 client->server / 30 server->client)
  and point them at protocol-schema.json as the generated inventory.

schema.md
- Correct the migration history to the real 001-015 numbering.
- Document the previously missing tables: login_attempts, settings,
  emoji, sounds (dead schema), rate_lockouts, user_blocks, events,
  plugins, plugin_kv; add attachments.uploader_id and new indexes.
- Fix the channel-type list to text/voice/dm (013 triggers) and correct
  the permission formula to (base & ~deny) | allow to match
  permissions.EffectivePerms.
- Note the sqlc/dbgen layer and link the architecture data-model doc.

Also correct the audit_log_v6 claim (transient rename inside migration
003, not a coexisting table) in the audit and data-model blueprint, and
update the audit/decisions trackers (A-2026-07-03 closed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
This commit is contained in:
Claude
2026-07-19 13:51:44 +00:00
parent 2e7a80171b
commit ca3b0fee3e
6 changed files with 532 additions and 44 deletions
+191 -9
View File
@@ -343,6 +343,103 @@ Disable TOTP for the authenticated user.
---
## User Profile & Sessions
### PATCH /api/v1/users/me
Update the authenticated user's profile (username and/or avatar).
Broadcasts a `user_update` WebSocket message to all clients on success.
**Auth:** Required
**Rate limit:** 10 requests/minute
#### Request
```json
{
"username": "newname",
"avatar": "upload-uuid.png"
}
```
Both fields optional; `avatar` may be `null` to clear it.
#### Response 200 OK
Returns the updated user object (same shape as `GET /api/v1/auth/me`).
---
### PUT /api/v1/users/me/password
Change the authenticated user's password. Verifies the old password, enforces
password strength, and revokes all *other* sessions on success.
**Auth:** Required
**Rate limit:** 5 requests/minute, plus a failed-confirmation lockout on
repeated wrong old passwords
#### Request
```json
{
"old_password": "OldPass!1",
"new_password": "NewStr0ng!Pass"
}
```
#### Response 204 No Content
Password changed and other sessions revoked. If the password change committed
but revoking other sessions failed, the endpoint returns **200 OK** with a
warning body instead (the new password is in effect — do not retry with the
old one).
#### Errors
| Status | Code | Cause |
| ------ | ---- | ----- |
| 400 | `INVALID_INPUT` | Weak new password, or new password equals old |
| 403 | `FORBIDDEN` | Incorrect old password |
| 429 | `RATE_LIMITED` | Too many attempts / lockout |
---
### GET /api/v1/users/me/sessions
List the authenticated user's active sessions.
**Auth:** Required
#### Response 200 OK
```json
{
"sessions": [
{
"id": 12,
"device": "Mozilla/5.0 ...",
"ip": "192.168.1.100",
"created_at": "2026-07-01T10:00:00Z",
"last_used": "2026-07-19T09:00:00Z",
"is_current": true
}
]
}
```
---
### DELETE /api/v1/users/me/sessions/{id}
Revoke one of the authenticated user's sessions by ID.
**Auth:** Required
#### Response 204 No Content
---
## Channel Endpoints
### GET /api/v1/channels
@@ -372,7 +469,7 @@ List all channels the authenticated user has `READ_MESSAGES` permission for. DM
| ----- | ---- | ----------- |
| `id` | int64 | Channel ID |
| `name` | string | Channel name |
| `type` | string | `text`, `voice`, or `announcement` |
| `type` | string | `text` or `voice` (`announcement` is planned; the DB currently rejects it) |
| `topic` | string | Channel topic/description |
| `category` | string | Category grouping |
| `position` | int | Sort order within category |
@@ -492,6 +589,7 @@ Unpin a message from a channel.
Full-text search across messages in channels the user can read. Uses SQLite FTS5 for matching.
**Auth:** Required
**Rate limit:** 30 requests/minute
#### Query Parameters
@@ -598,6 +696,47 @@ Close a DM channel for the authenticated user (hides it from their sidebar). The
---
## User Blocks
Blocking a user prevents DM creation and messaging in both directions
(backed by the `user_blocks` table).
### GET /api/v1/blocks
List the IDs of users the authenticated user has blocked.
**Auth:** Required
#### Response 200 OK
```json
{ "blocked_user_ids": [2, 7] }
```
---
### PUT /api/v1/blocks/{userId}
Block a user.
**Auth:** Required
#### Response 200 OK
```json
{ "message": "user blocked" }
```
---
### DELETE /api/v1/blocks/{userId}
Unblock a user.
**Auth:** Required
---
## Invite Endpoints
All invite endpoints require authentication and the `MANAGE_INVITES` permission.
@@ -667,6 +806,7 @@ Revoke an invite by its code string.
Upload a file as multipart form data.
**Auth:** Required
**Rate limit:** 10 requests/minute
**Body size limit:** 100 MiB
**Content-Type:** `multipart/form-data`
@@ -694,10 +834,12 @@ Files are validated against blocked magic bytes (PE executables, ELF binaries, M
Serve a previously uploaded file by its UUID.
**Auth:** None (URLs are unguessable UUIDs)
**Caching:** `Cache-Control: public, max-age=31536000, immutable`
**Auth:** Required (Bearer token) — downloads are access-controlled
**Caching:** `Cache-Control: private, no-cache` (never stored by shared/proxy caches; browsers must revalidate)
Supports HTTP range requests and conditional requests.
Supports HTTP range requests and conditional requests. MIME types that could
execute under the app origin (HTML, SVG, XML, PDF) are served with
`Content-Disposition: attachment` to force download.
---
@@ -707,12 +849,12 @@ Supports HTTP range requests and conditional requests.
### GET /api/v1/health
Public health check endpoint, no authentication required.
Public health check endpoint, no authentication required. The server version
is deliberately not exposed here (anti-fingerprinting hardening, C-2).
```json
{
"status": "ok",
"version": "1.0.0",
"uptime": 86400,
"online_users": 3
}
@@ -724,14 +866,14 @@ Public health check endpoint, no authentication required.
### GET /api/v1/info
Returns the server name and version.
Returns the server name. The version field was removed from this
unauthenticated endpoint (anti-fingerprinting hardening, C-2).
**Auth:** None
```json
{
"name": "My OwnCord Server",
"version": "1.2.0"
"name": "My OwnCord Server"
}
```
@@ -760,6 +902,46 @@ Runtime server metrics. Restricted to admin-allowed CIDRs.
---
## Plugin Administration
Manage WASM plugins. These endpoints sit behind **both** the admin IP
restriction (allowed CIDRs) **and** admin bearer-token authentication.
Plugin execution additionally requires a server built with `-tags wazero`
and `plugins.enabled: true` in config.
### GET /api/v1/admin/plugins
List installed plugins.
#### Response 200 OK
Array of plugin rows: `ID`, `Name`, `Version`, `Enabled`, `ManifestJSON`,
`InstalledAt`.
### POST /api/v1/admin/plugins/install
Install a plugin from an uploaded zip (multipart form). The archive is
size-capped (16 MiB compressed / 64 MiB uncompressed) and hardened against
zip-slip and symlinks; installation is staged and atomically renamed.
#### Response 201 Created
```json
{ "name": "plugin-name" }
```
### POST /api/v1/admin/plugins/{id}/enable
### POST /api/v1/admin/plugins/{id}/disable
Enable or disable an installed plugin.
### DELETE /api/v1/admin/plugins/{id}
Uninstall a plugin.
---
## LiveKit Endpoints
These endpoints are only registered when LiveKit voice is configured.