forked from claw/flaskpaste
docs: add pow, cli client, and head method documentation
This commit is contained in:
48
README.md
48
README.md
@@ -11,7 +11,9 @@ A lightweight, secure pastebin REST API built with Flask.
|
|||||||
- **Automatic expiry** - Pastes expire after configurable period of inactivity
|
- **Automatic expiry** - Pastes expire after configurable period of inactivity
|
||||||
- **Size limits** - Configurable limits for anonymous and authenticated users
|
- **Size limits** - Configurable limits for anonymous and authenticated users
|
||||||
- **Abuse prevention** - Content-hash deduplication throttles repeated identical submissions
|
- **Abuse prevention** - Content-hash deduplication throttles repeated identical submissions
|
||||||
|
- **Proof-of-work** - Configurable computational puzzle prevents automated spam
|
||||||
- **Security headers** - HSTS, CSP, X-Frame-Options, Cache-Control, and more
|
- **Security headers** - HSTS, CSP, X-Frame-Options, Cache-Control, and more
|
||||||
|
- **CLI client** - Standalone `fpaste` command-line tool included
|
||||||
- **Request tracing** - X-Request-ID support for log correlation
|
- **Request tracing** - X-Request-ID support for log correlation
|
||||||
- **Proxy trust validation** - Optional shared secret for defense-in-depth
|
- **Proxy trust validation** - Optional shared secret for defense-in-depth
|
||||||
- **Minimal dependencies** - Flask only, SQLite built-in
|
- **Minimal dependencies** - Flask only, SQLite built-in
|
||||||
@@ -36,9 +38,12 @@ python run.py
|
|||||||
|--------|----------|-------------|
|
|--------|----------|-------------|
|
||||||
| `GET /` | API information and usage |
|
| `GET /` | API information and usage |
|
||||||
| `GET /health` | Health check (returns DB status) |
|
| `GET /health` | Health check (returns DB status) |
|
||||||
|
| `GET /challenge` | Get proof-of-work challenge |
|
||||||
| `POST /` | Create a new paste |
|
| `POST /` | Create a new paste |
|
||||||
| `GET /<id>` | Retrieve paste metadata |
|
| `GET /<id>` | Retrieve paste metadata |
|
||||||
|
| `HEAD /<id>` | Retrieve paste metadata (headers only) |
|
||||||
| `GET /<id>/raw` | Retrieve raw paste content |
|
| `GET /<id>/raw` | Retrieve raw paste content |
|
||||||
|
| `HEAD /<id>/raw` | Retrieve paste headers (no body) |
|
||||||
| `DELETE /<id>` | Delete paste (requires auth) |
|
| `DELETE /<id>` | Delete paste (requires auth) |
|
||||||
|
|
||||||
## Usage Examples
|
## Usage Examples
|
||||||
@@ -77,6 +82,46 @@ curl -X DELETE \
|
|||||||
http://localhost:5000/abc12345
|
http://localhost:5000/abc12345
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## CLI Client
|
||||||
|
|
||||||
|
A standalone command-line client `fpaste` is included (no external dependencies).
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create paste from file
|
||||||
|
./fpaste create file.txt
|
||||||
|
|
||||||
|
# Create paste from stdin
|
||||||
|
echo "Hello" | ./fpaste
|
||||||
|
|
||||||
|
# Get paste content
|
||||||
|
./fpaste get abc12345
|
||||||
|
|
||||||
|
# Get paste metadata
|
||||||
|
./fpaste get -m abc12345
|
||||||
|
|
||||||
|
# Delete paste (requires auth)
|
||||||
|
./fpaste delete abc12345
|
||||||
|
|
||||||
|
# Show server info
|
||||||
|
./fpaste info
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
Set server URL and authentication via environment or config file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Environment variables
|
||||||
|
export FLASKPASTE_SERVER="https://paste.example.com"
|
||||||
|
export FLASKPASTE_CERT_SHA1="your-cert-fingerprint"
|
||||||
|
|
||||||
|
# Or config file (~/.config/fpaste/config)
|
||||||
|
server = https://paste.example.com
|
||||||
|
cert_sha1 = your-cert-fingerprint
|
||||||
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Configuration via environment variables:
|
Configuration via environment variables:
|
||||||
@@ -92,6 +137,9 @@ Configuration via environment variables:
|
|||||||
| `FLASKPASTE_DEDUP_WINDOW` | `3600` (1 hour) | Dedup throttle window in seconds |
|
| `FLASKPASTE_DEDUP_WINDOW` | `3600` (1 hour) | Dedup throttle window in seconds |
|
||||||
| `FLASKPASTE_DEDUP_MAX` | `3` | Max identical submissions per window |
|
| `FLASKPASTE_DEDUP_MAX` | `3` | Max identical submissions per window |
|
||||||
| `FLASKPASTE_PROXY_SECRET` | (empty) | Shared secret for proxy trust validation |
|
| `FLASKPASTE_PROXY_SECRET` | (empty) | Shared secret for proxy trust validation |
|
||||||
|
| `FLASKPASTE_POW_DIFFICULTY` | `20` | PoW difficulty (leading zero bits, 0=disabled) |
|
||||||
|
| `FLASKPASTE_POW_TTL` | `300` (5 min) | PoW challenge validity period |
|
||||||
|
| `FLASKPASTE_POW_SECRET` | (auto) | Secret for signing PoW challenges |
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,39 @@ The fingerprint must be exactly 40 lowercase hexadecimal characters (SHA1).
|
|||||||
|
|
||||||
## Endpoints
|
## Endpoints
|
||||||
|
|
||||||
|
### GET /challenge
|
||||||
|
|
||||||
|
Get a proof-of-work challenge for paste creation. Required when PoW is enabled.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```http
|
||||||
|
GET /challenge HTTP/1.1
|
||||||
|
Host: localhost:5000
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (PoW disabled):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"enabled": false,
|
||||||
|
"difficulty": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (PoW enabled):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"nonce": "a1b2c3d4e5f6a7b8a1b2c3d4e5f6a7b8",
|
||||||
|
"difficulty": 20,
|
||||||
|
"expires": 1700000300,
|
||||||
|
"token": "a1b2c3d4...:1700000300:20:signature"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The client must find a number `N` such that `SHA256(nonce + ":" + N)` has at least `difficulty` leading zero bits.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### GET /health
|
### GET /health
|
||||||
|
|
||||||
Health check endpoint for load balancers and monitoring.
|
Health check endpoint for load balancers and monitoring.
|
||||||
@@ -112,6 +145,20 @@ Content-Type: application/json
|
|||||||
|
|
||||||
**Request (with Proof-of-Work):**
|
**Request (with Proof-of-Work):**
|
||||||
|
|
||||||
|
When PoW is enabled, include the challenge token and solution:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST / HTTP/1.1
|
||||||
|
Host: localhost:5000
|
||||||
|
Content-Type: text/plain
|
||||||
|
X-PoW-Token: a1b2c3d4...:1700000300:20:signature
|
||||||
|
X-PoW-Solution: 12345678
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (201 Created):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
"id": "abc12345",
|
"id": "abc12345",
|
||||||
"url": "/abc12345",
|
"url": "/abc12345",
|
||||||
"raw": "/abc12345/raw",
|
"raw": "/abc12345/raw",
|
||||||
@@ -128,6 +175,8 @@ Content-Type: application/json
|
|||||||
| 400 | Proof-of-work required (when PoW enabled) |
|
| 400 | Proof-of-work required (when PoW enabled) |
|
||||||
| 400 | Proof-of-work failed (invalid/expired challenge) |
|
| 400 | Proof-of-work failed (invalid/expired challenge) |
|
||||||
| 413 | Paste too large |
|
| 413 | Paste too large |
|
||||||
|
| 429 | Duplicate content rate limit exceeded |
|
||||||
|
|
||||||
**Size Limits:**
|
**Size Limits:**
|
||||||
- Anonymous: 3 MiB (configurable via `FLASKPASTE_MAX_ANON`)
|
- Anonymous: 3 MiB (configurable via `FLASKPASTE_MAX_ANON`)
|
||||||
- Authenticated: 50 MiB (configurable via `FLASKPASTE_MAX_AUTH`)
|
- Authenticated: 50 MiB (configurable via `FLASKPASTE_MAX_AUTH`)
|
||||||
@@ -139,7 +188,9 @@ Content-Type: application/json
|
|||||||
### HEAD /{id}
|
### HEAD /{id}
|
||||||
|
|
||||||
Retrieve paste metadata. HEAD returns headers only (no body).
|
Retrieve paste metadata. HEAD returns headers only (no body).
|
||||||
**Request:**
|
|
||||||
|
**Request:**
|
||||||
|
```http
|
||||||
GET /abc12345 HTTP/1.1
|
GET /abc12345 HTTP/1.1
|
||||||
Host: localhost:5000
|
Host: localhost:5000
|
||||||
```
|
```
|
||||||
@@ -168,7 +219,9 @@ Host: localhost:5000
|
|||||||
### HEAD /{id}/raw
|
### HEAD /{id}/raw
|
||||||
|
|
||||||
Retrieve raw paste content with correct MIME type. HEAD returns headers only (useful for checking MIME type and size without downloading content).
|
Retrieve raw paste content with correct MIME type. HEAD returns headers only (useful for checking MIME type and size without downloading content).
|
||||||
**Request:**
|
|
||||||
|
**Request:**
|
||||||
|
```http
|
||||||
GET /abc12345/raw HTTP/1.1
|
GET /abc12345/raw HTTP/1.1
|
||||||
Host: localhost:5000
|
Host: localhost:5000
|
||||||
```
|
```
|
||||||
@@ -290,6 +343,58 @@ export FLASKPASTE_DEDUP_MAX=3 # Max duplicates per window (default: 3)
|
|||||||
## Proof-of-Work
|
## Proof-of-Work
|
||||||
|
|
||||||
FlaskPaste includes an optional proof-of-work system to prevent automated spam.
|
FlaskPaste includes an optional proof-of-work system to prevent automated spam.
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
1. Client requests a challenge via `GET /challenge`
|
||||||
|
2. Server returns a nonce, difficulty, expiry time, and signed token
|
||||||
|
3. Client computes SHA256 hashes until finding one with enough leading zero bits
|
||||||
|
4. Client submits paste with `X-PoW-Token` and `X-PoW-Solution` headers
|
||||||
|
|
||||||
|
**Algorithm:**
|
||||||
|
```
|
||||||
|
For N = 0, 1, 2, ...:
|
||||||
|
hash = SHA256(nonce + ":" + N)
|
||||||
|
if leading_zero_bits(hash) >= difficulty:
|
||||||
|
return N as solution
|
||||||
|
```
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
```bash
|
||||||
|
export FLASKPASTE_POW_DIFFICULTY=20 # Leading zero bits required (0=disabled)
|
||||||
|
export FLASKPASTE_POW_TTL=300 # Challenge validity in seconds
|
||||||
|
export FLASKPASTE_POW_SECRET="..." # Optional signing key (auto-generated if empty)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Headers:**
|
||||||
|
| Header | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `X-PoW-Token` | The full token from `/challenge` response |
|
||||||
|
| `X-PoW-Solution` | The computed solution number |
|
||||||
|
|
||||||
|
**Error responses:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "Proof-of-work required",
|
||||||
|
"hint": "GET /challenge for a new challenge"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "Proof-of-work failed: Challenge expired"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Difficulty 20 requires approximately 1 million hash attempts on average
|
||||||
|
- Challenges are signed to prevent tampering
|
||||||
|
- Each challenge can only be used once (nonce uniqueness)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Response Format
|
||||||
|
|
||||||
|
All errors return JSON:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user