696 lines
32 KiB
Markdown
696 lines
32 KiB
Markdown
# Authentication API Documentation
|
|
|
|
## Overview
|
|
|
|
The nostr_core_lib unified request validation system provides a comprehensive authentication and authorization framework for Nostr-based applications. It combines Nostr event validation with flexible rule-based authentication in a single API call.
|
|
|
|
## Authentication Flow and Order of Operations
|
|
|
|
### Authentication Flow Diagram
|
|
|
|
```
|
|
┌─────────────────────┐
|
|
│ Request Received │
|
|
└──────────┬──────────┘
|
|
│
|
|
▼
|
|
┌─────────────┐ ╔═══════════════════╗
|
|
│ Input Valid?├─No─►║ REJECT: Invalid ║
|
|
└──────┬──────┘ ║ Input (~1μs) ║
|
|
│Yes ╚═══════════════════╝
|
|
▼
|
|
┌─────────────┐ ╔═══════════════════╗
|
|
│System Init? ├─No─►║ REJECT: Not ║
|
|
└──────┬──────┘ ║ Initialized ║
|
|
│Yes ╚═══════════════════╝
|
|
▼
|
|
┌─────────────┐
|
|
│Auth Header? │
|
|
└──────┬──────┘
|
|
│Yes
|
|
▼ ┌─────────────────────┐
|
|
┌─────────────┐ No │ │
|
|
│Parse Header ├────────────┤ Skip Nostr │
|
|
└──────┬──────┘ │ Validation │
|
|
│ │ │
|
|
▼ └──────────┬──────────┘
|
|
┌─────────────┐ ╔═══════════════════╗ │
|
|
│Valid Base64?├─No─►║ REJECT: Malformed ║ │
|
|
└──────┬──────┘ ║ Header (~10μs) ║ │
|
|
│Yes ╚═══════════════════╝ │
|
|
▼ │
|
|
┌─────────────┐ ╔═══════════════════╗ │
|
|
│Valid JSON? ├─No─►║ REJECT: Invalid ║ │
|
|
└──────┬──────┘ ║ JSON (~50μs) ║ │
|
|
│Yes ╚═══════════════════╝ │
|
|
▼ │
|
|
┌─────────────┐ ╔═══════════════════╗ │
|
|
│Valid Struct?├─No─►║ REJECT: Invalid ║ │
|
|
└──────┬──────┘ ║ Structure (~100μs)║ │
|
|
│Yes ╚═══════════════════╝ │
|
|
▼ │
|
|
┌─────────────┐ ╔═══════════════════╗ │
|
|
│Event Kind? │ ║ ║ │
|
|
└──────┬──────┘ ║ DUAL AUTH MODES ║ │
|
|
│ ╚═══════════════════╝ │
|
|
▼ │
|
|
┌─────────────────────────────────────────────────────┐
|
|
│ │
|
|
▼ ▼ ▼ ▼
|
|
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
│ Kind │ │ Kind │ │ Other │ │ Invalid │
|
|
│ 22242 │ │ 24242 │ │ Kinds │ │ Kind │
|
|
│ (NIP-42) │ │(Blossom) │ │ (Skip) │ │(Reject) │
|
|
└─────┬────┘ └─────┬────┘ └─────┬────┘ └─────┬────┘
|
|
│ │ │ │
|
|
▼ │ ▼ ▼
|
|
┌──────────┐ │ ┌──────────┐ ╔═══════════════════╗
|
|
│ NIP-42 │ │ │ Skip │ ║ REJECT: Invalid ║
|
|
│Challenge │ │ │ Nostr │ ║ Event Kind ║
|
|
│Validate │ │ │Validate │ ╚═══════════════════╝
|
|
│(~500μs) │ │ └─────┬────┘ │
|
|
└─────┬────┘ │ │ │
|
|
│ │ ▼ │
|
|
│ │ ┌──────────┐ │
|
|
│ │ │ Extract │ │
|
|
│ │ │ Context │ │
|
|
│ │ └─────┬────┘ │
|
|
│ │ │ │
|
|
▼ ▼ ▼ ▼
|
|
┌────────────────────────────────────────────────────────────┐
|
|
│ ECDSA SIGNATURE VERIFICATION │
|
|
│ (~2ms) │
|
|
└───────────────────────────┬────────────────────────────────┘
|
|
│Yes
|
|
▼ │
|
|
┌─────────────────┐ ╔═══════════════════╗
|
|
│Operation Match? │No ║ REJECT: Unauth. ║
|
|
│(Kind 24242 only)├──►║ Operation (~200μs)║
|
|
└─────────┬───────┘ ╚═══════════════════╝
|
|
│Yes/Skip(Kind 22242)
|
|
▼ │
|
|
┌─────────────────┐ ╔═══════════════════╗ │
|
|
│Event Expired? │Yes║ REJECT: Expired ║ │
|
|
└─────────┬───────┘ ║ Event (~50μs) ║ │
|
|
│No ╚═══════════════════╝ │
|
|
▼ │
|
|
┌─────────────────┐ │
|
|
│Extract Pubkey │ │
|
|
│& Auth Context │ │
|
|
└─────────┬───────┘ │
|
|
│ │
|
|
▼◄───────────────────────────────────────┘
|
|
┌─────────────────┐ ╔═══════════════════╗
|
|
│Auth Rules │ No ║ ALLOW: Rules ║
|
|
│Enabled? ├────►║ Disabled ║
|
|
└─────────┬───────┘ ╚═══════════════════╝
|
|
│Yes
|
|
▼
|
|
┌─────────────────┐
|
|
│Generate Cache │
|
|
│Key (SHA-256) │
|
|
└─────────┬───────┘
|
|
│
|
|
▼
|
|
┌─────────────────┐ ╔═══════════════════╗
|
|
│Cache Hit? │ Yes ║ RETURN: Cached ║
|
|
│(~100μs lookup) ├────►║ Decision (~100μs) ║
|
|
└─────────┬───────┘ ╚═══════════════════╝
|
|
│No
|
|
▼
|
|
╔═══════════════════════════════════════╗
|
|
║ RULE EVALUATION ENGINE ║
|
|
║ (Priority Order) ║
|
|
╚═══════════════════════════════════════╝
|
|
│
|
|
▼
|
|
┌─────────────────┐ ╔═══════════════════╗
|
|
│1. Pubkey │ Yes ║ DENY: Pubkey ║
|
|
│ Blacklisted? ├────►║ Blocked ║
|
|
└─────────┬───────┘ ╚═══════════════════╝
|
|
│No
|
|
▼
|
|
┌─────────────────┐ ╔═══════════════════╗
|
|
│2. Hash │ Yes ║ DENY: Hash ║
|
|
│ Blacklisted? ├────►║ Blocked ║
|
|
└─────────┬───────┘ ╚═══════════════════╝
|
|
│No
|
|
▼
|
|
┌─────────────────┐ ╔═══════════════════╗
|
|
│3. MIME Type │ Yes ║ DENY: MIME ║
|
|
│ Blacklisted? ├────►║ Blocked ║
|
|
└─────────┬───────┘ ╚═══════════════════╝
|
|
│No
|
|
▼
|
|
┌─────────────────┐ ╔═══════════════════╗
|
|
│4. Size Limit │ Yes ║ DENY: File ║
|
|
│ Exceeded? ├────►║ Too Large ║
|
|
└─────────┬───────┘ ╚═══════════════════╝
|
|
│No
|
|
▼
|
|
┌─────────────────┐ ╔═══════════════════╗
|
|
│5. Pubkey │ Yes ║ ALLOW: Pubkey ║
|
|
│ Whitelisted? ├────►║ Whitelisted ║
|
|
└─────────┬───────┘ ╚═══════════════════╝
|
|
│No
|
|
▼
|
|
┌─────────────────┐ ╔═══════════════════╗
|
|
│6. MIME Type │ Yes ║ ALLOW: MIME ║
|
|
│ Whitelisted? ├────►║ Whitelisted ║
|
|
└─────────┬───────┘ ╚═══════════════════╝
|
|
│No
|
|
▼
|
|
┌─────────────────┐ ╔═══════════════════╗
|
|
│Whitelist Rules │ Yes ║ DENY: Not in ║
|
|
│Exist? ├────►║ Whitelist ║
|
|
└─────────┬───────┘ ╚═══════════════════╝
|
|
│No
|
|
▼
|
|
╔═══════════════════╗
|
|
║ ALLOW: Default ║
|
|
║ Allow Policy ║
|
|
╚═══════════════════╝
|
|
│
|
|
▼
|
|
┌─────────────────┐
|
|
│ Cache Decision │
|
|
│ (5min TTL) │
|
|
└─────────┬───────┘
|
|
│
|
|
▼
|
|
┌─────────────────┐
|
|
│ Return Result │
|
|
│ to Application │
|
|
└─────────────────┘
|
|
```
|
|
|
|
### Authentication Flow (Mermaid)
|
|
|
|
```mermaid
|
|
|
|
flowchart TD
|
|
A[Request Received] --> B{Input Valid?}
|
|
B -->|No| R1[REJECT: Invalid Input ~1μs]
|
|
B -->|Yes| C{System Init?}
|
|
C -->|No| R2[REJECT: Not Initialized]
|
|
C -->|Yes| D{Auth Header?}
|
|
D -->|No| SKIP[Skip Nostr Validation]
|
|
D -->|Yes| E{Valid Base64?}
|
|
E -->|No| R3[REJECT: Malformed Header ~10μs]
|
|
E -->|Yes| F{Valid JSON?}
|
|
F -->|No| R4[REJECT: Invalid JSON ~50μs]
|
|
F -->|Yes| G{Valid Struct?}
|
|
G -->|No| R5[REJECT: Invalid Structure ~100μs]
|
|
G -->|Yes| H{Event Kind?}
|
|
|
|
%% Event Kind Branching
|
|
H --> I[Kind 22242<br/>NIP-42]
|
|
H --> J[Kind 24242<br/>Blossom]
|
|
H --> K[Other Kinds<br/>Skip]
|
|
H --> L[Invalid Kind]
|
|
L --> R6[REJECT: Invalid Event Kind]
|
|
|
|
%% NIP-42 Path
|
|
I --> M[NIP-42 Challenge<br/>Validation ~500μs]
|
|
M --> N[ECDSA Signature<br/>Verification ~2ms]
|
|
|
|
%% Blossom Path
|
|
J --> N
|
|
|
|
%% Skip Path
|
|
K --> SKIP
|
|
SKIP --> O[Extract Context]
|
|
O --> N
|
|
|
|
%% Signature Verification
|
|
N -->|No| R7[REJECT: Invalid Signature ~2ms]
|
|
N -->|Yes| P{Operation Match?<br/>Kind 24242 only}
|
|
P -->|No| R8[REJECT: Unauthorized Operation ~200μs]
|
|
P -->|Yes/Skip 22242| Q{Event Expired?}
|
|
Q -->|Yes| R9[REJECT: Expired Event ~50μs]
|
|
Q -->|No| S[Extract Pubkey & Auth Context]
|
|
|
|
%% Rules Engine
|
|
S --> T{Auth Rules Enabled?}
|
|
T -->|No| ALLOW1[ALLOW: Rules Disabled]
|
|
T -->|Yes| U[Generate Cache Key SHA-256]
|
|
U --> V{Cache Hit?}
|
|
V -->|Yes| CACHED[RETURN: Cached Decision ~100μs]
|
|
V -->|No| W[RULE EVALUATION ENGINE<br/>Priority Order]
|
|
|
|
%% Rule Checks
|
|
W --> X1{1. Pubkey Blacklisted?}
|
|
X1 -->|Yes| DENY1[DENY: Pubkey Blocked]
|
|
X1 -->|No| X2{2. Hash Blacklisted?}
|
|
X2 -->|Yes| DENY2[DENY: Hash Blocked]
|
|
X2 -->|No| X3{3. MIME Blacklisted?}
|
|
X3 -->|Yes| DENY3[DENY: MIME Blocked]
|
|
X3 -->|No| X4{4. Size Limit Exceeded?}
|
|
X4 -->|Yes| DENY4[DENY: File Too Large]
|
|
X4 -->|No| X5{5. Pubkey Whitelisted?}
|
|
X5 -->|Yes| ALLOW2[ALLOW: Pubkey Whitelisted]
|
|
X5 -->|No| X6{6. MIME Whitelisted?}
|
|
X6 -->|Yes| ALLOW3[ALLOW: MIME Whitelisted]
|
|
X6 -->|No| X7{Whitelist Rules Exist?}
|
|
X7 -->|Yes| DENY5[DENY: Not in Whitelist]
|
|
X7 -->|No| ALLOW4[ALLOW: Default Policy]
|
|
|
|
%% Final Steps
|
|
ALLOW2 --> CACHE[Cache Decision 5min TTL]
|
|
ALLOW3 --> CACHE
|
|
ALLOW4 --> CACHE
|
|
DENY1 --> CACHE
|
|
DENY2 --> CACHE
|
|
DENY3 --> CACHE
|
|
DENY4 --> CACHE
|
|
DENY5 --> CACHE
|
|
CACHE --> RESULT[Return Result to Application]
|
|
|
|
%% Styling
|
|
classDef rejectBox fill:#ff4444,stroke:#ffffff,color:#ffffff
|
|
classDef allowBox fill:#44ff44,stroke:#ffffff,color:#000000
|
|
classDef processBox fill:#4444ff,stroke:#ffffff,color:#ffffff
|
|
classDef decisionBox fill:#ffff44,stroke:#000000,color:#000000
|
|
|
|
class R1,R2,R3,R4,R5,R6,R7,R8,R9,DENY1,DENY2,DENY3,DENY4,DENY5 rejectBox
|
|
class ALLOW1,ALLOW2,ALLOW3,ALLOW4,CACHED allowBox
|
|
class A,M,N,S,U,W,CACHE,RESULT,SKIP,O processBox
|
|
class B,C,D,E,F,G,H,P,Q,T,V,X1,X2,X3,X4,X5,X6,X7 decisionBox
|
|
```
|
|
|
|
### Performance Timeline (ASCII)
|
|
|
|
```
|
|
Fast Path (Cache Hit) - Total: ~101μs
|
|
┌─────┬─────────────────────────────────────────────────────────────────┬──────┐
|
|
│ 1μs │ 100μs Cache Lookup │ 1μs │
|
|
└─────┴─────────────────────────────────────────────────────────────────┴──────┘
|
|
Input │ │ Return
|
|
Valid │ SQLite SELECT │ Result
|
|
|
|
Typical Path (Valid Request) - Total: ~2.4ms
|
|
┌──┬───┬────┬─────────────────────────┬────────┬────┬──┐
|
|
│1μ│50μ│100μ│ 2000μs │ 200μs │100μ│1μ│
|
|
└──┴───┴────┴─────────────────────────┴────────┴────┴──┘
|
|
│ │ │ │ │ │ │
|
|
│ │ │ │ ECDSA Signature │ Rule │Cache│Return
|
|
│ │ │ │ Verification │ Eval │Store│Result
|
|
│ │ │ │ (Most Expensive) │ │ │
|
|
│ │ │
|
|
│ │ JSON Parse
|
|
│ Header Parse
|
|
Input Validation
|
|
|
|
Worst Case (Full Validation) - Total: ~2.7ms
|
|
┌──┬───┬────┬─────────────────────────┬─────────┬────┬──┐
|
|
│1μ│50μ│100μ│ 2000μs │ 500μs │100μ│1μ│
|
|
└──┴───┴────┴─────────────────────────┴─────────┴────┴──┘
|
|
│
|
|
All 6 Rule Checks
|
|
(Multiple DB Queries)
|
|
```
|
|
|
|
### Dual Authentication Architecture
|
|
|
|
The system supports **two authentication modes** that can work independently or together:
|
|
|
|
#### **NIP-42 Authentication (Kind 22242)**
|
|
- **Purpose**: Client identity authentication ("who you are")
|
|
- **Use Case**: Relay client authentication, WebSocket connections
|
|
- **Event Structure**: Contains `relay` URL and `challenge` tags
|
|
- **Flow**: Challenge/response pattern with relay-generated challenges
|
|
- **Validation**: Verifies client identity against relay URL and challenge
|
|
- **Database Storage**: Client sessions and challenge tracking
|
|
|
|
#### **Blossom Protocol Authentication (Kind 24242)**
|
|
- **Purpose**: Operation authorization ("what you can do")
|
|
- **Use Case**: File upload/delete/list operations
|
|
- **Event Structure**: Contains operation tag `t=upload|delete|list` and content hash `x=hash`
|
|
- **Flow**: Direct operation authorization with expiration
|
|
- **Validation**: Verifies operation permissions and content integrity
|
|
- **Database Storage**: Operation-specific authentication rules
|
|
|
|
#### **Integration Strategy**
|
|
```
|
|
┌─────────────────┐ ┌─────────────────┐
|
|
│ NIP-42 Auth │ │ Blossom Auth │
|
|
│ (Kind 22242) │ │ (Kind 24242) │
|
|
│ │ │ │
|
|
│ Client Identity │ │ Operation Perms │
|
|
│ Challenge/Resp │ │ File Operations │
|
|
└─────────┬───────┘ └─────────┬───────┘
|
|
│ │
|
|
└──────┬─────────┬─────┘
|
|
│ │
|
|
▼ ▼
|
|
┌─────────────────┐
|
|
│ Unified Rules │
|
|
│ Engine │
|
|
│ │
|
|
│ • Pubkey Rules │
|
|
│ • Hash Rules │
|
|
│ • MIME Rules │
|
|
│ • Size Limits │
|
|
└─────────────────┘
|
|
```
|
|
|
|
#### **Event Kind Processing**
|
|
- **Kind 22242** (NIP-42): Validates against stored challenge + relay URL
|
|
- **Kind 24242** (Blossom): Validates operation tags + content integrity
|
|
- **Other Kinds**: Skip Nostr validation, proceed to rule evaluation
|
|
- **Invalid Kind**: Reject immediately
|
|
|
|
#### **Dual Mode Benefits**
|
|
- **Backwards Compatibility**: Existing Blossom clients continue working
|
|
- **Enhanced Security**: NIP-42 provides cryptographic client identity
|
|
- **Flexible Deployment**: Relays can require either or both methods
|
|
- **Performance**: Separate validation paths optimize for each use case
|
|
|
|
### Request Processing Flow (DDoS-Optimized)
|
|
|
|
The authentication system is designed with **performance and DDoS protection** as primary concerns. Here's the exact order of operations:
|
|
|
|
#### Phase 1: Input Validation (Immediate Rejection)
|
|
1. **Null Pointer Checks** - Reject malformed requests instantly (lines 122-128)
|
|
2. **Initialization Check** - Verify system is properly initialized
|
|
3. **Basic Structure Validation** - Ensure required fields are present
|
|
|
|
#### Phase 2: Nostr Event Validation (CPU Intensive)
|
|
4. **Authorization Header Parsing** (lines 139-148)
|
|
- Extract base64-encoded Nostr event from `Authorization: Nostr <base64>` header
|
|
- Decode base64 to JSON (memory allocation + decoding)
|
|
- **Early exit**: Invalid base64 or malformed header rejected immediately
|
|
|
|
5. **JSON Parsing** (lines 150-156)
|
|
- Parse Nostr event JSON using cJSON
|
|
- **Early exit**: Invalid JSON rejected before signature verification
|
|
|
|
6. **Nostr Event Structure Validation** (lines 159-166)
|
|
- Validate event has required fields (kind, pubkey, sig, etc.)
|
|
- **Early exit**: Invalid structure rejected before expensive crypto operations
|
|
|
|
7. **Event Kind Routing** (NEW - Dual Authentication)
|
|
- **Kind 22242** (NIP-42): Route to NIP-42 challenge validation
|
|
- **Kind 24242** (Blossom): Route to Blossom operation validation
|
|
- **Other Kinds**: Skip Nostr validation, proceed to rules
|
|
- **Invalid Kind**: Reject immediately
|
|
|
|
8. **NIP-42 Challenge Validation** (Kind 22242 Only)
|
|
- Validate `relay` tag matches configured relay URL
|
|
- Verify `challenge` tag exists and matches stored challenge
|
|
- Check challenge expiration and single-use constraints
|
|
- **Performance**: ~500μs additional validation overhead
|
|
|
|
9. **Cryptographic Signature Verification** (Both Paths)
|
|
- **Most CPU-intensive operation** - ECDSA signature verification
|
|
- Validates event authenticity using secp256k1
|
|
- **Early exit**: Invalid signatures rejected before database queries
|
|
|
|
10. **Operation-Specific Validation** (Kind 24242 Only)
|
|
- Verify event authorizes the requested operation (upload/delete/list)
|
|
- Check required tags (t=operation, x=hash, expiration)
|
|
- Validate timestamp and expiration
|
|
- **Early exit**: Expired or mismatched events rejected
|
|
|
|
11. **Public Key Extraction** (Both Paths)
|
|
- Extract validated public key from event for rule evaluation
|
|
- Store authentication context (NIP-42 vs Blossom) for rule processing
|
|
|
|
#### Phase 3: Authentication Rules (Database Queries)
|
|
10. **Rules System Check** (line 191)
|
|
- Quick config check if authentication rules are enabled
|
|
- **Early exit**: If disabled, allow request immediately
|
|
|
|
11. **Cache Lookup** (lines 1051-1054)
|
|
- Generate SHA-256 cache key from request parameters
|
|
- Check SQLite cache for previous decision
|
|
- **Early exit**: Cache hit returns cached decision (5-minute TTL)
|
|
|
|
12. **Rule Evaluation** (Priority Order - lines 1061-1094):
|
|
- **a. Pubkey Blacklist** (highest priority) - Immediate denial if matched
|
|
- **b. Hash Blacklist** - Block specific content hashes
|
|
- **c. MIME Type Blacklist** - Block dangerous file types
|
|
- **d. File Size Limits** - Enforce upload size restrictions
|
|
- **e. Pubkey Whitelist** - Allow specific users (only if not denied above)
|
|
- **f. MIME Type Whitelist** - Allow specific file types
|
|
|
|
13. **Whitelist Default Denial** (lines 1097-1121)
|
|
- If whitelist rules exist but none matched, deny request
|
|
- Prevents whitelist bypass attacks
|
|
|
|
14. **Cache Storage** (line 1124)
|
|
- Store decision in cache for future requests (5-minute TTL)
|
|
|
|
### DDoS Protection Features
|
|
|
|
#### **Fail-Fast Design**
|
|
- **Input validation** happens before any expensive operations
|
|
- **Authorization header parsing** fails fast on malformed data
|
|
- **JSON parsing** rejects invalid data before signature verification
|
|
- **Structure validation** happens before cryptographic operations
|
|
|
|
#### **Expensive Operations Last**
|
|
- **Signature verification** only after structure validation
|
|
- **Database queries** only after successful Nostr validation
|
|
- **Cache prioritized** over database queries
|
|
|
|
#### **Caching Strategy**
|
|
- **SHA-256 cache keys** prevent cache pollution attacks
|
|
- **5-minute TTL** balances performance with rule changes
|
|
- **LRU eviction** prevents memory exhaustion
|
|
- **Per-request caching** includes all parameters (pubkey, operation, hash, MIME, size)
|
|
|
|
#### **Resource Limits**
|
|
- **JSON parsing** limited to 4KB buffer size
|
|
- **Cache entries** limited to prevent memory exhaustion
|
|
- **Database connection pooling** (single connection with proper cleanup)
|
|
- **String length limits** on all inputs
|
|
|
|
#### **Attack Mitigation**
|
|
- **Base64 bombs** - Limited decode buffer size (4KB)
|
|
- **JSON bombs** - cJSON library handles malformed JSON safely
|
|
- **Cache poisoning** - Cryptographic cache keys prevent collisions
|
|
- **Rule bypass** - Whitelist default denial prevents unauthorized access
|
|
- **Replay attacks** - Timestamp and expiration validation
|
|
- **Hash collision attacks** - Full SHA-256 verification
|
|
|
|
### Performance Characteristics
|
|
|
|
#### **Best Case** (Cached Decision):
|
|
1. Input validation: ~1μs
|
|
2. Cache lookup: ~100μs (SQLite SELECT)
|
|
3. **Total: ~101μs**
|
|
|
|
#### **Worst Case** (Full Validation + Rule Evaluation):
|
|
1. Input validation: ~1μs
|
|
2. Base64 decoding: ~50μs
|
|
3. JSON parsing: ~100μs
|
|
4. Signature verification: ~2000μs (ECDSA)
|
|
5. Database queries: ~500μs (6 rule checks)
|
|
6. Cache storage: ~100μs
|
|
7. **Total: ~2751μs (~2.7ms)**
|
|
|
|
#### **Typical Case** (Valid Request, Rules Enabled):
|
|
1. Full validation: ~2200μs
|
|
2. Cache miss, 2-3 rule checks: ~200μs
|
|
3. **Total: ~2400μs (~2.4ms)**
|
|
|
|
### Security Order Rationale
|
|
|
|
The rule evaluation order is specifically designed for security:
|
|
|
|
1. **Blacklists First** - Immediate denial of known bad actors
|
|
2. **Resource Limits** - Prevent resource exhaustion attacks
|
|
3. **Whitelists Last** - Only allow after passing all security checks
|
|
4. **Default Deny** - If whitelists exist but don't match, deny
|
|
|
|
This ensures that even if an attacker bypasses one layer, subsequent layers will catch the attack.
|
|
|
|
## Core API
|
|
|
|
### Primary Function
|
|
|
|
```c
|
|
int nostr_validate_request(nostr_request_t* request, nostr_request_result_t* result);
|
|
```
|
|
|
|
This single function handles:
|
|
- Nostr event signature validation
|
|
- Event structure validation (required fields, timestamps)
|
|
- Authentication rule evaluation
|
|
- Public key extraction and validation
|
|
|
|
### Request Structure
|
|
|
|
```c
|
|
typedef struct {
|
|
const char* event_json; // Raw Nostr event JSON
|
|
const char* app_id; // Application identifier ("ginxsom", "c-relay")
|
|
const char* operation; // Operation type ("upload", "delete", "list")
|
|
const char* content_hash; // SHA-256 hash for file operations (optional)
|
|
const char* mime_type; // MIME type for upload operations (optional)
|
|
size_t content_size; // File size for upload operations (0 if N/A)
|
|
} nostr_request_t;
|
|
```
|
|
|
|
### Result Structure
|
|
|
|
```c
|
|
typedef struct {
|
|
int is_valid; // 1 if request is valid, 0 otherwise
|
|
int error_code; // Specific error code (see Error Codes)
|
|
char error_message[512]; // Human-readable error description
|
|
char pubkey[65]; // Extracted public key (hex, null-terminated)
|
|
time_t timestamp; // Event timestamp
|
|
char event_id[65]; // Event ID (hex, null-terminated)
|
|
} nostr_request_result_t;
|
|
```
|
|
|
|
## Authentication Rules System
|
|
|
|
The system supports priority-based authentication rules that are evaluated in order:
|
|
|
|
### Rule Types
|
|
|
|
1. **NOSTR_AUTH_RULE_PUBKEY_WHITELIST** - Allow specific public keys
|
|
2. **NOSTR_AUTH_RULE_PUBKEY_BLACKLIST** - Block specific public keys
|
|
3. **NOSTR_AUTH_RULE_HASH_BLACKLIST** - Block specific content hashes
|
|
4. **NOSTR_AUTH_RULE_MIME_RESTRICTION** - Restrict allowed MIME types
|
|
5. **NOSTR_AUTH_RULE_SIZE_LIMIT** - Enforce maximum file sizes
|
|
|
|
### Rule Evaluation
|
|
|
|
- Rules are processed by priority (lower numbers = higher priority)
|
|
- First matching rule determines the outcome
|
|
- ALLOW rules permit the request
|
|
- DENY rules reject the request
|
|
- If no rules match, the default action is ALLOW
|
|
|
|
### Rule Caching
|
|
|
|
The system includes an intelligent caching mechanism:
|
|
- LRU (Least Recently Used) eviction policy
|
|
- Configurable cache size (default: 1000 entries)
|
|
- Cache keys based on pubkey + operation + content hash
|
|
- Automatic cache invalidation when rules change
|
|
|
|
## Database Backend
|
|
|
|
### Pluggable Architecture
|
|
|
|
The system uses a pluggable database backend interface:
|
|
|
|
```c
|
|
typedef struct {
|
|
int (*init)(const char* connection_string, void** context);
|
|
int (*get_rules)(void* context, const char* app_id,
|
|
nostr_auth_rule_t** rules, int* count);
|
|
int (*cleanup)(void* context);
|
|
} nostr_db_backend_t;
|
|
```
|
|
|
|
### SQLite Implementation
|
|
|
|
Default implementation uses SQLite with the following schema:
|
|
|
|
```sql
|
|
-- Authentication rules table (per application)
|
|
CREATE TABLE auth_rules_[APP_ID] (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
priority INTEGER NOT NULL,
|
|
rule_type INTEGER NOT NULL,
|
|
action INTEGER NOT NULL,
|
|
pattern TEXT,
|
|
value_int INTEGER,
|
|
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
updated_at INTEGER DEFAULT (strftime('%s', 'now'))
|
|
);
|
|
```
|
|
|
|
## Error Codes
|
|
|
|
The system uses specific error codes for different failure scenarios:
|
|
|
|
### Authentication Rule Errors
|
|
- **-50**: `NOSTR_AUTH_ERROR_INVALID_EVENT` - Malformed Nostr event
|
|
- **-51**: `NOSTR_AUTH_ERROR_INVALID_SIGNATURE` - Invalid event signature
|
|
- **-52**: `NOSTR_AUTH_ERROR_PUBKEY_BLOCKED` - Public key is blacklisted
|
|
- **-53**: `NOSTR_AUTH_ERROR_HASH_BLOCKED` - Content hash is blacklisted
|
|
- **-54**: `NOSTR_AUTH_ERROR_MIME_RESTRICTED` - MIME type not allowed
|
|
- **-55**: `NOSTR_AUTH_ERROR_SIZE_EXCEEDED` - File size limit exceeded
|
|
|
|
### NIP-42 Specific Errors
|
|
- **-200**: `NIP42_ERROR_INVALID_RELAY_URL` - Relay URL mismatch or missing
|
|
- **-201**: `NIP42_ERROR_INVALID_CHALLENGE` - Challenge missing or malformed
|
|
- **-202**: `NIP42_ERROR_CHALLENGE_EXPIRED` - Challenge has expired
|
|
- **-203**: `NIP42_ERROR_CHALLENGE_USED` - Challenge already consumed
|
|
- **-204**: `NIP42_ERROR_CHALLENGE_NOT_FOUND` - Challenge not found in storage
|
|
- **-205**: `NIP42_ERROR_WRONG_EVENT_KIND` - Expected kind 22242 for NIP-42
|
|
- **-206**: `NIP42_ERROR_MISSING_TAGS` - Required relay or challenge tags missing
|
|
- **-207**: `NIP42_ERROR_URL_NORMALIZATION` - Failed to normalize relay URL
|
|
- **-208**: `NIP42_ERROR_VALIDATION_FAILED` - General NIP-42 validation failure
|
|
|
|
## Usage Examples
|
|
|
|
### Basic Validation
|
|
|
|
```c
|
|
#include "nostr_core/request_validator.h"
|
|
|
|
// Initialize the system (once per application)
|
|
int result = nostr_request_validator_init("db/myapp.db", "myapp");
|
|
if (result != 0) {
|
|
fprintf(stderr, "Failed to initialize validator: %d\n", result);
|
|
return -1;
|
|
}
|
|
|
|
// Validate a request
|
|
nostr_request_t request = {
|
|
.event_json = "{\"kind\":24242,\"pubkey\":\"abc123...\",\"sig\":\"def456...\"}",
|
|
.app_id = "myapp",
|
|
.operation = "upload",
|
|
.content_hash = "sha256hash...",
|
|
.mime_type = "text/plain",
|
|
.content_size = 1024
|
|
};
|
|
|
|
nostr_request_result_t result;
|
|
int status = nostr_validate_request(&request, &result);
|
|
|
|
if (result.is_valid) {
|
|
printf("Request authorized for pubkey: %s\n", result.pubkey);
|
|
} else {
|
|
printf("Request denied: %s (code: %d)\n", result.error_message, result.error_code);
|
|
}
|
|
```
|
|
|
|
### Ginxsom Integration
|
|
|
|
The ginxsom application has been updated to use this system:
|
|
|
|
```c
|
|
// Replace old authenticate_request_with_rules() calls with:
|
|
nostr_request_t auth_request = {
|
|
.event_json = event_json,
|
|
.app_id = "ginxsom",
|
|
.operation = "upload", // or "list", "delete"
|
|
.content_hash = calculated_hash,
|
|
.mime_type = detected_mime_type,
|
|
.content_size = file_size
|
|
};
|
|
|
|
nostr_request_result_t auth_result;
|
|
int auth_status = nostr_validate_request(&auth_request, &auth_result);
|
|
|
|
if (!auth_result.is_valid) {
|
|
printf("Status: 403\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Authentication failed\",\"message\":\"%s\"}\n",
|
|
auth_result.error_message);
|
|
return;
|
|
}
|
|
|
|
// Use auth_result.pubkey for the authenticated public key
|
|
```
|
|
|