V0.1.1 release, close to actual release. Bug & security fixes/improvements.

This commit is contained in:
unknown
2026-05-24 19:29:41 +02:00
parent a7b44af91a
commit b004e15948
38 changed files with 3145 additions and 137 deletions

View File

@@ -75,7 +75,7 @@
- `410 Gone` — Content has reached its maximum view count.
- `416 Range Not Satisfiable` — Invalid `Range` header.
- **Notes:**
- The server increments the view counter on successful full-file responses. Range requests and `If-None-Match` (ETag) matches do **not** increment the counter.
- The server increments the view counter on successful full-file responses. Range requests, `If-None-Match` (ETag) matches, and HEAD requests do **not** increment the counter.
- If the incremented view count reaches `max_views`, the server may delete content files (depending on `keep_content` config) and mark the content as `Deleted`, returning `410 Gone`.
- `Accept-Ranges: bytes` is included for `video/*` and `audio/*` MIME types.
- Cache-Control is `private, max-age=60` for unprotected content and `private, no-store, max-age=0` for password-protected content.
@@ -105,6 +105,26 @@
---
### POST /api/content/:cxid/report
- **Description:** Report content for review. Creates a report record and forwards a notification to all configured review groups via the Telegram Bot API.
- **Auth:** None
- **Path params:**
- `cxid` — Content ID string.
- **Body:** JSON object:
```json
{
"reason": "string"
}
```
- **Response formats:**
- `204 No Content` — Report accepted.
- `404 Not Found` — Content does not exist or has been deleted/blacklisted.
- **Notes:**
- The report is recorded with `reporter_user_id = 0` to indicate a web-based report.
- Rate limiting is covered by the general API governor.
---
### POST /api/content/:cxid/verify-password
- **Description:** Explicitly verify a password for password-protected content and receive an authentication cookie.
- **Auth:** None (this is the endpoint used to *obtain* auth).
@@ -135,6 +155,11 @@ The server allows cross-origin requests from its configured `base_url` and commo
- General API routes (`/api/health`, `/api/content/...`) share a per-IP rate limit configured by `requests_per_minute` and `burst`.
- `POST /api/content/:cxid/verify-password` has its own rate limit with a burst of 3 and a separate `password_attempts_per_minute` setting.
### Password Flow
- The `sc` query parameter is checked on both the metadata endpoint (`GET /api/content/:cxid`) and the file endpoints (`GET /api/content/:cxid/file/:file_idx`, `GET /api/content/:cxid/file/:file_idx/raw`). When valid, the server sets an HMAC-signed `cgcx_pw` cookie on the response.
- Passwords can also be provided via the `cgcx_pw` cookie.
- For programmatic verification, use `POST /api/content/:cxid/verify-password`.
### Fallback / Static Assets
- `/assets/*` — Serves static files from `frontend/dist/assets`.
- All other non-`/api` paths — Serves `frontend/dist/index.html` (SPA fallback).

View File

@@ -4,28 +4,28 @@ This document lists all commands and callback actions implemented in `crates/cgc
---
## Admin Commands (Group-only)
## Admin Commands (Groups & Channels)
All admin commands require the caller to be an **administrator or owner** of the group.
All admin commands require the caller to be an **administrator or owner** of the chat. They work in groups, supergroups, and channels where the bot is present.
| Command | Args | Description |
|---------|------|-------------|
| `/reload` | none | Reload moderation lists from disk. |
| `/blacklist_uid` | `<ID>` | Blacklist a user by Telegram ID globally and set their role to `banned`. **Restricted to configured admin groups.** Shows usage info if the ID argument is missing. |
| `/whitelist_uid` | `<ID>` | Remove a user from the global blacklist and restore their role to `user`. **Restricted to configured admin groups.** Shows usage info if the ID argument is missing. |
| `/help` | none | Show the admin help message listing all admin commands. Properly HTML-escaped. |
| `/get_id` | none | Get the current group chat ID. |
| `/get_id` | `<@username>` | Search administrators in this chat by username. Results are HTML-escaped. |
| `/get_id` | `<displayname>` | Search members in this chat by display name. Results are HTML-escaped. |
| `/blacklist_uid` | `[ID]` | Blacklist a user by Telegram ID globally and set their role to `banned`. **Restricted to configured admin groups; the caller must be an admin there.** Shows usage info (`Usage: /blacklist_uid <user_id>`) if the ID argument is missing. |
| `/whitelist_uid` | `[ID]` | Remove a user from the global blacklist and restore their role to `user`. **Restricted to configured admin groups; the caller must be an admin there.** Shows usage info (`Usage: /whitelist_uid <user_id>`) if the ID argument is missing. |
| `/help` | none | Show the admin help message listing all admin commands. Properly HTML-escaped. Argument placeholders in the help text use `[arg]` format to avoid Telegram HTML parse errors with angle brackets. |
| `/get_id` | none | Get the current chat ID. Works in groups, supergroups, and channels. |
| `/get_id` | `[@username]` | Search administrators in this chat by username. Results are HTML-escaped. |
| `/get_id` | `[displayname]` | Search members in this chat by display name. Results are HTML-escaped. |
| `/create_submit_forward` | `<dest_chat_id> <review_group_id> [forward_message]` | Create a submission forward link. Bot must be admin in both destination and review groups. |
| `/show_c_forward` | `[page]` | List active forward links for this chat with pagination. |
| `/add_blacklist` | `<user_id>` | Blacklist a user in **all active forwards** for this source chat. |
| `/rm_blacklist` | `<user_id>` | Remove a user from the blacklist in **all active forwards** for this source chat. |
| `/sban` | `@user <dur> <unit> [reason]` | Ban a user for a specified duration. |
| `/smute` | `@user <dur> <unit> [reason]` | Mute a user for a specified duration. |
| `/mute` | `@user [reason]` | Mute a user indefinitely. |
| `/pban` | `@user [reason]` | Permanently ban a user. |
| `/kick` | `@user [reason]` | Kick a user from the group. |
| `/sban` | `@user <dur> <unit> [reason]` | Ban a user for a specified duration. **Propagates across all known chats when `global_ban = true`.** |
| `/smute` | `@user <dur> <unit> [reason]` | Mute a user for a specified duration. **Propagates across all known chats when `global_ban = true`.** |
| `/mute` | `@user [reason]` | Mute a user indefinitely. **Propagates across all known chats when `global_ban = true`.** |
| `/pban` | `@user [reason]` | Permanently ban a user. **Propagates across all known chats when `global_ban = true`.** |
| `/kick` | `@user [reason]` | Kick a user from the group. **Propagates across all known chats when `global_ban = true`.** |
| `/rmute` | `@user` | Revoke an active mute and restore the user's chat permissions. |
| `/rban` | `@user` | Revoke an active ban and unban the user. |
@@ -106,3 +106,11 @@ Callbacks use the format `v1:<namespace>:<action>[:<id>]`.
| `v1:fwd:banblk:{submission_id}` | Ban + blacklist the submitter in one action. |
| `v1:fwd:revoke:{forward_id}` | Revoke a forward link. |
| `v1:fwd:page:{page}` | Navigate forward link list pages. |
**Review Message Buttons**
When a submission is sent to the review group, the inline keyboard includes:
- Row 1: `[ Approve ]`, `[ Ignore ]`
- Row 2: `[ Blackl. ]`, `[ Ban ]`, `[ Ban/BL u. ]`
These correspond to the callbacks above and allow moderators to take action directly from the review message.

View File

@@ -152,7 +152,7 @@ This is used both for:
## Global Ban Configuration
Under `[groups]` in the config, the optional `global_ban` flag (default `false`) controls whether punishment commands (`/sban`, `/smute`, `/mute`, `/pban`) are propagated across all known chats where the bot is an administrator.
The `[groups]` section in the config contains the optional `global_ban` flag (default `false`). When enabled, punishment commands (`/sban`, `/smute`, `/mute`, `/pban`, `/kick`) are propagated across all known chats where the bot is an administrator.
```toml
[groups]
@@ -161,7 +161,57 @@ review_group_ids = [-1009876543210]
global_ban = false
```
- When `global_ban = true`, issuing a punishment in any admin group is intended to apply the same action to every known chat (source chats, destination chats, review groups, and configured `admin_group_ids` / `review_group_ids`) where it has admin rights.
- When `global_ban = true`, issuing a punishment in any admin group applies the same action to every known chat (source chats, destination chats, review groups, and configured `admin_group_ids` / `review_group_ids`) where the bot has admin rights.
- When `global_ban = false` (default), punishments are local to the group where the command was issued.
**Note:** When `global_ban = true`, the bot propagates the punishment to every configured `admin_group_ids`, `review_group_ids`, and all active forward chats (source, destination, and review groups) where it has administrator rights. Each propagated action is recorded as a separate `punishments` row.
### Propagation Behavior
Each propagated punishment is recorded as a **separate row** in the `punishments` table, with its own `chat_id`. This means:
- The background expiration task naturally revokes each per-chat punishment independently.
- Manual `/rmute` or `/rban` only affects the chat where the revoke command was issued.
- The bot skips any chat where it is not an administrator and logs a warning.
---
## Hash Blacklist
Migration `007_hash_blacklist.sql` creates the `hash_blacklist` table:
```sql
CREATE TABLE hash_blacklist (
hash BLOB PRIMARY KEY,
created_at DATETIME NOT NULL DEFAULT (datetime('now')),
reason TEXT
);
```
The `HashBlacklistRepo` (in `crates/cgcx-db/src/repos.rs`) provides:
- `insert(hash, reason)` — Adds a hash to the blacklist (ignored if already present).
- `contains(hash)` — Returns `true` if the hash is blacklisted.
During file ingestion (`crates/cgcx-file-pipeline/src/lib.rs`), the pipeline computes a **plaintext BLAKE3 hash** and checks it against `hash_blacklist` **before** deduplication and persistence. If the hash is blocked, ingestion is rejected with a `BlockedHash` error and the temporary file is discarded.
---
## Username Tracking
The bot can log username changes to a JSON file for audit and moderation purposes.
### Configuration
Set `uname_changes_path` at the top level of the config (default: `"data/uname_changes.json"`):
```toml
database_path = "data/db.sqlite"
uname_changes_path = "data/uname_changes.json"
```
### How It Works
On every message and callback interaction, the bot calls `UserRepo::ensure_exists(...)`, passing the configured path. If the user's stored username differs from the current one, a JSON line is appended to the file:
```json
{"timestamp":"2026-05-24T12:34:56Z","user_id":123456789,"chat_id":-1001234567890,"old_username":"old_name","new_username":"new_name"}
```
The file is opened in append mode and created automatically if it does not exist.