Refactored code by breaking the main.c up into BUD files.
This commit is contained in:
108
56.md
108
56.md
@@ -1,108 +0,0 @@
|
|||||||
NIP-56
|
|
||||||
======
|
|
||||||
|
|
||||||
Reporting
|
|
||||||
---------
|
|
||||||
|
|
||||||
`optional`
|
|
||||||
|
|
||||||
A report is a `kind 1984` event that signals to users and relays that
|
|
||||||
some referenced content is objectionable. The definition of objectionable is
|
|
||||||
obviously subjective and all agents on the network (users, apps, relays, etc.)
|
|
||||||
may consume and take action on them as they see fit.
|
|
||||||
|
|
||||||
The `content` MAY contain additional information submitted by the entity
|
|
||||||
reporting the content.
|
|
||||||
|
|
||||||
Tags
|
|
||||||
----
|
|
||||||
|
|
||||||
The report event MUST include a `p` tag referencing the pubkey of the user you
|
|
||||||
are reporting.
|
|
||||||
|
|
||||||
If reporting a note, an `e` tag MUST also be included referencing the note id.
|
|
||||||
|
|
||||||
A `report type` string MUST be included as the 3rd entry to the `e`, `p` or `x` tag
|
|
||||||
being reported, which consists of the following report types:
|
|
||||||
|
|
||||||
- `nudity` - depictions of nudity, porn, etc.
|
|
||||||
- `malware` - virus, trojan horse, worm, robot, spyware, adware, back door, ransomware, rootkit, kidnapper, etc.
|
|
||||||
- `profanity` - profanity, hateful speech, etc.
|
|
||||||
- `illegal` - something which may be illegal in some jurisdiction
|
|
||||||
- `spam` - spam
|
|
||||||
- `impersonation` - someone pretending to be someone else
|
|
||||||
- `other` - for reports that don't fit in the above categories
|
|
||||||
|
|
||||||
Some report tags only make sense for profile reports, such as `impersonation`.
|
|
||||||
|
|
||||||
- `x` tags SHOULD be info hash of a blob which is intended to be report. when the `x` tag is represented client MUST include an `e` tag which is the id of the event that contains the mentioned blob. also, additionally these events can contain a `server` tag to point to media servers which may contain the mentioned media.
|
|
||||||
|
|
||||||
`l` and `L` tags MAY be also be used as defined in [NIP-32](32.md) to support
|
|
||||||
further qualification and querying.
|
|
||||||
|
|
||||||
Example events
|
|
||||||
--------------
|
|
||||||
|
|
||||||
```jsonc
|
|
||||||
{
|
|
||||||
"kind": 1984,
|
|
||||||
"tags": [
|
|
||||||
["p", "<pubkey>", "nudity"],
|
|
||||||
["L", "social.nos.ontology"],
|
|
||||||
["l", "NS-nud", "social.nos.ontology"]
|
|
||||||
],
|
|
||||||
"content": "",
|
|
||||||
// other fields...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```jsonc
|
|
||||||
{
|
|
||||||
"kind": 1984,
|
|
||||||
"tags": [
|
|
||||||
["e", "<eventId>", "illegal"],
|
|
||||||
["p", "<pubkey>"]
|
|
||||||
],
|
|
||||||
"content": "He's insulting the king!",
|
|
||||||
// other fields...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```jsonc
|
|
||||||
{
|
|
||||||
"kind": 1984,
|
|
||||||
"tags": [
|
|
||||||
["p", "<impersonator pubkey>", "impersonation"]
|
|
||||||
],
|
|
||||||
"content": "Profile is impersonating nostr:<victim bech32 pubkey>",
|
|
||||||
// other fields...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```jsonc
|
|
||||||
{
|
|
||||||
"kind": 1984,
|
|
||||||
"tags": [
|
|
||||||
["x", "<blob hash>", "malware"],
|
|
||||||
["e", "<event id which contains the blob on x tag>", "malware"],
|
|
||||||
["server", "https://you-may-find-the-blob-here.com/path-to-url.ext"]
|
|
||||||
],
|
|
||||||
"content": "This file contains malware software in it.",
|
|
||||||
// other fields...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Client behavior
|
|
||||||
---------------
|
|
||||||
|
|
||||||
Clients can use reports from friends to make moderation decisions if they
|
|
||||||
choose to. For instance, if 3+ of your friends report a profile for `nudity`,
|
|
||||||
clients can have an option to automatically blur photos from said account.
|
|
||||||
|
|
||||||
|
|
||||||
Relay behavior
|
|
||||||
--------------
|
|
||||||
|
|
||||||
It is not recommended that relays perform automatic moderation using reports,
|
|
||||||
as they can be easily gamed. Admins could use reports from trusted moderators to
|
|
||||||
takedown illegal or explicit content if the relay does not allow such things.
|
|
||||||
252
AUTH_API.md
252
AUTH_API.md
@@ -49,27 +49,57 @@ The nostr_core_lib unified request validation system provides a comprehensive au
|
|||||||
└──────┬──────┘ ║ Structure (~100μs)║ │
|
└──────┬──────┘ ║ Structure (~100μs)║ │
|
||||||
│Yes ╚═══════════════════╝ │
|
│Yes ╚═══════════════════╝ │
|
||||||
▼ │
|
▼ │
|
||||||
┌─────────────────┐ ╔═══════════════════╗ │
|
┌─────────────┐ ╔═══════════════════╗ │
|
||||||
│ ECDSA Signature │No ║ REJECT: Invalid ║ │
|
│Event Kind? │ ║ ║ │
|
||||||
│ Verify (~2ms) ├──►║ Signature (~2ms) ║ │
|
└──────┬──────┘ ║ DUAL AUTH MODES ║ │
|
||||||
└─────────┬───────┘ ╚═══════════════════╝ │
|
│ ╚═══════════════════╝ │
|
||||||
│Yes │
|
▼ │
|
||||||
▼ │
|
┌─────────────────────────────────────────────────────┐
|
||||||
┌─────────────────┐ ╔═══════════════════╗ │
|
│ │
|
||||||
│Operation Match? │No ║ REJECT: Unauth. ║ |
|
▼ ▼ ▼ ▼
|
||||||
└─────────┬───────┘ ║ Operation (~200μs)║ │
|
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||||
│Yes ╚═══════════════════╝ │
|
│ 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 Expired? │Yes║ REJECT: Expired ║ │
|
||||||
└─────────┬───────┘ ║ Event (~50μs) ║ │
|
└─────────┬───────┘ ║ Event (~50μs) ║ │
|
||||||
│No ╚═══════════════════╝ │
|
│No ╚═══════════════════╝ │
|
||||||
▼ │
|
▼ │
|
||||||
┌─────────────────┐ │
|
┌─────────────────┐ │
|
||||||
│Extract Pubkey │ │
|
│Extract Pubkey │ │
|
||||||
|
│& Auth Context │ │
|
||||||
└─────────┬───────┘ │
|
└─────────┬───────┘ │
|
||||||
│ │
|
│ │
|
||||||
▼◄───────────────────────────────────────┘
|
▼◄───────────────────────────────────────┘
|
||||||
┌─────────────────┐ ╔═══════════════════╗
|
┌─────────────────┐ ╔═══════════════════╗
|
||||||
│Auth Rules │ No ║ ALLOW: Rules ║
|
│Auth Rules │ No ║ ALLOW: Rules ║
|
||||||
│Enabled? ├────►║ Disabled ║
|
│Enabled? ├────►║ Disabled ║
|
||||||
@@ -154,6 +184,100 @@ The nostr_core_lib unified request validation system provides a comprehensive au
|
|||||||
└─────────────────┘
|
└─────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 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)
|
### Performance Timeline (ASCII)
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -186,6 +310,62 @@ Worst Case (Full Validation) - Total: ~2.7ms
|
|||||||
(Multiple DB Queries)
|
(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)
|
### 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:
|
The authentication system is designed with **performance and DDoS protection** as primary concerns. Here's the exact order of operations:
|
||||||
@@ -207,22 +387,34 @@ The authentication system is designed with **performance and DDoS protection** a
|
|||||||
|
|
||||||
6. **Nostr Event Structure Validation** (lines 159-166)
|
6. **Nostr Event Structure Validation** (lines 159-166)
|
||||||
- Validate event has required fields (kind, pubkey, sig, etc.)
|
- Validate event has required fields (kind, pubkey, sig, etc.)
|
||||||
- Check event kind is 24242 for Blossom operations
|
|
||||||
- **Early exit**: Invalid structure rejected before expensive crypto operations
|
- **Early exit**: Invalid structure rejected before expensive crypto operations
|
||||||
|
|
||||||
7. **Cryptographic Signature Verification** (lines 159-166)
|
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
|
- **Most CPU-intensive operation** - ECDSA signature verification
|
||||||
- Validates event authenticity using secp256k1
|
- Validates event authenticity using secp256k1
|
||||||
- **Early exit**: Invalid signatures rejected before database queries
|
- **Early exit**: Invalid signatures rejected before database queries
|
||||||
|
|
||||||
8. **Operation-Specific Validation** (lines 169-178)
|
10. **Operation-Specific Validation** (Kind 24242 Only)
|
||||||
- Verify event authorizes the requested operation (upload/delete/list)
|
- Verify event authorizes the requested operation (upload/delete/list)
|
||||||
- Check required tags (t=operation, x=hash, expiration)
|
- Check required tags (t=operation, x=hash, expiration)
|
||||||
- Validate timestamp and expiration
|
- Validate timestamp and expiration
|
||||||
- **Early exit**: Expired or mismatched events rejected
|
- **Early exit**: Expired or mismatched events rejected
|
||||||
|
|
||||||
9. **Public Key Extraction** (lines 181-184)
|
11. **Public Key Extraction** (Both Paths)
|
||||||
- Extract validated public key from event for rule evaluation
|
- 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)
|
#### Phase 3: Authentication Rules (Database Queries)
|
||||||
10. **Rules System Check** (line 191)
|
10. **Rules System Check** (line 191)
|
||||||
@@ -419,6 +611,7 @@ CREATE TABLE auth_rules_[APP_ID] (
|
|||||||
|
|
||||||
The system uses specific error codes for different failure scenarios:
|
The system uses specific error codes for different failure scenarios:
|
||||||
|
|
||||||
|
### Authentication Rule Errors
|
||||||
- **-50**: `NOSTR_AUTH_ERROR_INVALID_EVENT` - Malformed Nostr event
|
- **-50**: `NOSTR_AUTH_ERROR_INVALID_EVENT` - Malformed Nostr event
|
||||||
- **-51**: `NOSTR_AUTH_ERROR_INVALID_SIGNATURE` - Invalid event signature
|
- **-51**: `NOSTR_AUTH_ERROR_INVALID_SIGNATURE` - Invalid event signature
|
||||||
- **-52**: `NOSTR_AUTH_ERROR_PUBKEY_BLOCKED` - Public key is blacklisted
|
- **-52**: `NOSTR_AUTH_ERROR_PUBKEY_BLOCKED` - Public key is blacklisted
|
||||||
@@ -426,6 +619,17 @@ The system uses specific error codes for different failure scenarios:
|
|||||||
- **-54**: `NOSTR_AUTH_ERROR_MIME_RESTRICTED` - MIME type not allowed
|
- **-54**: `NOSTR_AUTH_ERROR_MIME_RESTRICTED` - MIME type not allowed
|
||||||
- **-55**: `NOSTR_AUTH_ERROR_SIZE_EXCEEDED` - File size limit exceeded
|
- **-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
|
## Usage Examples
|
||||||
|
|
||||||
### Basic Validation
|
### Basic Validation
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -8,7 +8,7 @@ BUILDDIR = build
|
|||||||
TARGET = $(BUILDDIR)/ginxsom-fcgi
|
TARGET = $(BUILDDIR)/ginxsom-fcgi
|
||||||
|
|
||||||
# Source files
|
# Source files
|
||||||
SOURCES = $(SRCDIR)/main.c $(SRCDIR)/admin_api.c
|
SOURCES = $(SRCDIR)/main.c $(SRCDIR)/admin_api.c $(SRCDIR)/bud04.c $(SRCDIR)/bud06.c $(SRCDIR)/bud08.c $(SRCDIR)/bud09.c
|
||||||
OBJECTS = $(SOURCES:$(SRCDIR)/%.c=$(BUILDDIR)/%.o)
|
OBJECTS = $(SOURCES:$(SRCDIR)/%.c=$(BUILDDIR)/%.o)
|
||||||
|
|
||||||
# Default target
|
# Default target
|
||||||
|
|||||||
232
NIP42_INTEGRATION_PLAN.md
Normal file
232
NIP42_INTEGRATION_PLAN.md
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
# NIP-42 Authentication Integration Plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This document outlines the integration of NIP-42 client-to-relay authentication into the existing ginxsom authentication system. The goal is to support both NIP-42 (kind 22242) and Blossom (kind 24242) authentication methods in a unified system.
|
||||||
|
|
||||||
|
## Current System Analysis
|
||||||
|
|
||||||
|
### Existing Authentication Flow
|
||||||
|
1. **Blossom Protocol (kind 24242)**: Operation-specific authorization
|
||||||
|
- Tags: `t` (method), `x` (hash), `expiration`
|
||||||
|
- Purpose: Authorize specific file operations (upload, delete, etc.)
|
||||||
|
- Location: `validate_nostr_event()` in `request_validator.c:379`
|
||||||
|
|
||||||
|
2. **Rules Engine**: Policy-based access control
|
||||||
|
- Pubkey whitelist/blacklist
|
||||||
|
- Hash blacklist
|
||||||
|
- MIME type restrictions
|
||||||
|
- File size limits
|
||||||
|
- Location: `evaluate_auth_rules()` in `request_validator.c:1029`
|
||||||
|
|
||||||
|
### Integration Points Identified
|
||||||
|
1. **Header Structure**: Extend `nostr_request_t` to support NIP-42 context
|
||||||
|
2. **Event Validation**: Support both kind 22242 (NIP-42) and 24242 (Blossom)
|
||||||
|
3. **Challenge Management**: Add challenge generation and storage
|
||||||
|
4. **Database Schema**: Add challenge storage tables
|
||||||
|
5. **Authentication Flow**: Support dual authentication modes
|
||||||
|
|
||||||
|
## NIP-42 Integration Architecture
|
||||||
|
|
||||||
|
### Dual Authentication Strategy
|
||||||
|
```
|
||||||
|
Client Authentication (NIP-42) → Operation Authorization (Blossom) → Rules Engine
|
||||||
|
```
|
||||||
|
|
||||||
|
- **NIP-42 (kind 22242)**: Proves client identity to relay
|
||||||
|
- **Blossom (kind 24242)**: Authorizes specific operations
|
||||||
|
- **Rules Engine**: Applies policy-based restrictions
|
||||||
|
|
||||||
|
### Authentication Flow Options
|
||||||
|
1. **NIP-42 Only**: Client authenticates identity, all operations allowed by rules
|
||||||
|
2. **Blossom Only**: Current system - operation-specific authorization
|
||||||
|
3. **Dual Mode**: NIP-42 identity + Blossom operation authorization
|
||||||
|
4. **Rules Only**: No cryptographic auth, rules-based access only
|
||||||
|
|
||||||
|
## Technical Implementation Plan
|
||||||
|
|
||||||
|
### 1. Extend Request Validator Header (`request_validator.h`)
|
||||||
|
```c
|
||||||
|
// Add NIP-42 support structures
|
||||||
|
typedef struct {
|
||||||
|
char challenge[256]; // Current challenge for client
|
||||||
|
time_t challenge_created; // Challenge timestamp
|
||||||
|
char relay_url[256]; // Relay URL for challenge
|
||||||
|
int is_authenticated; // NIP-42 auth status
|
||||||
|
char authenticated_pubkey[65]; // Pubkey from NIP-42 auth
|
||||||
|
} nostr_nip42_context_t;
|
||||||
|
|
||||||
|
// Extend nostr_request_t
|
||||||
|
typedef struct {
|
||||||
|
// ... existing fields ...
|
||||||
|
nostr_nip42_context_t* nip42_context; // NIP-42 authentication context
|
||||||
|
const char* challenge; // Challenge for this request
|
||||||
|
} nostr_request_t;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Database Schema Extensions
|
||||||
|
```sql
|
||||||
|
-- NIP-42 challenge storage
|
||||||
|
CREATE TABLE IF NOT EXISTS nip42_challenges (
|
||||||
|
challenge_id TEXT PRIMARY KEY,
|
||||||
|
challenge TEXT NOT NULL,
|
||||||
|
relay_url TEXT NOT NULL,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
expires_at INTEGER NOT NULL,
|
||||||
|
client_ip TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- NIP-42 authentication sessions
|
||||||
|
CREATE TABLE IF NOT EXISTS nip42_sessions (
|
||||||
|
session_id TEXT PRIMARY KEY,
|
||||||
|
pubkey TEXT NOT NULL,
|
||||||
|
challenge_id TEXT NOT NULL,
|
||||||
|
authenticated_at INTEGER NOT NULL,
|
||||||
|
expires_at INTEGER NOT NULL,
|
||||||
|
client_ip TEXT,
|
||||||
|
FOREIGN KEY (challenge_id) REFERENCES nip42_challenges(challenge_id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Core Functions to Add
|
||||||
|
```c
|
||||||
|
// NIP-42 challenge management
|
||||||
|
int nostr_nip42_generate_server_challenge(char* challenge_out, size_t challenge_size);
|
||||||
|
int nostr_nip42_store_challenge(const char* challenge, const char* relay_url, const char* client_ip);
|
||||||
|
int nostr_nip42_verify_challenge_response(const char* auth_header, const char* relay_url, nostr_request_result_t* result);
|
||||||
|
|
||||||
|
// Enhanced validation
|
||||||
|
int validate_nip42_event(struct cJSON* event, const char* expected_challenge, const char* relay_url);
|
||||||
|
int validate_blossom_event(struct cJSON* event, const char* expected_hash, const char* method);
|
||||||
|
|
||||||
|
// Dual authentication support
|
||||||
|
int nostr_validate_request_dual(const nostr_request_t* request, nostr_request_result_t* result);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Enhanced Event Validation
|
||||||
|
Extend `validate_nostr_event()` to handle both event kinds:
|
||||||
|
```c
|
||||||
|
static int validate_nostr_event(struct cJSON* event, const char* expected_hash, const char* method) {
|
||||||
|
struct cJSON* kind_json = cJSON_GetObjectItem(event, "kind");
|
||||||
|
int kind = cJSON_GetNumberValue(kind_json);
|
||||||
|
|
||||||
|
switch (kind) {
|
||||||
|
case 22242: // NIP-42 authentication
|
||||||
|
return validate_nip42_event(event, expected_hash, method);
|
||||||
|
case 24242: // Blossom authorization
|
||||||
|
return validate_blossom_event(event, expected_hash, method);
|
||||||
|
default:
|
||||||
|
return NOSTR_ERROR_EVENT_INVALID_KIND;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Ginxsom Integration Points
|
||||||
|
|
||||||
|
#### Main.c Changes
|
||||||
|
```c
|
||||||
|
// Add challenge endpoint
|
||||||
|
if (strcmp(request_method, "GET") == 0 && strstr(request_uri, "/.well-known/nostr/challenge")) {
|
||||||
|
handle_nip42_challenge_request(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced authentication in existing endpoints
|
||||||
|
int handle_upload_request(FCGX_Request* request) {
|
||||||
|
// ... existing code ...
|
||||||
|
|
||||||
|
// Check for NIP-42 challenge header
|
||||||
|
const char* challenge_header = FCGX_GetParam("HTTP_X_NOSTR_CHALLENGE", request->envp);
|
||||||
|
|
||||||
|
nostr_request_t nostr_request = {
|
||||||
|
.operation = "upload",
|
||||||
|
.auth_header = auth_header,
|
||||||
|
.resource_hash = file_hash,
|
||||||
|
.mime_type = content_type,
|
||||||
|
.file_size = content_length,
|
||||||
|
.challenge = challenge_header,
|
||||||
|
.nip42_context = &nip42_ctx
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
### New Configuration Keys
|
||||||
|
```sql
|
||||||
|
-- NIP-42 specific settings
|
||||||
|
INSERT OR REPLACE INTO auth_config VALUES ('nip42_enabled', 'true');
|
||||||
|
INSERT OR REPLACE INTO auth_config VALUES ('nip42_challenge_ttl', '600');
|
||||||
|
INSERT OR REPLACE INTO auth_config VALUES ('nip42_session_ttl', '3600');
|
||||||
|
INSERT OR REPLACE INTO auth_config VALUES ('auth_mode', 'dual'); -- dual, nip42_only, blossom_only, rules_only
|
||||||
|
INSERT OR REPLACE INTO auth_config VALUES ('relay_url', 'http://localhost:9001');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authentication Modes
|
||||||
|
1. **dual**: NIP-42 + Blossom + Rules (most secure)
|
||||||
|
2. **nip42_only**: NIP-42 + Rules (client identity required)
|
||||||
|
3. **blossom_only**: Blossom + Rules (current system)
|
||||||
|
4. **rules_only**: Rules only (no cryptographic auth)
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### Test Cases to Add
|
||||||
|
1. **NIP-42 Challenge Flow**: Generate challenge, verify response
|
||||||
|
2. **Kind 22242 Validation**: Test NIP-42 event structure and signatures
|
||||||
|
3. **Dual Authentication**: NIP-42 auth + Blossom authorization
|
||||||
|
4. **Challenge Expiration**: Test challenge TTL and cleanup
|
||||||
|
5. **Session Management**: Test session creation and expiration
|
||||||
|
6. **Mixed Mode Testing**: Different auth modes in same server
|
||||||
|
7. **Error Handling**: Invalid challenges, expired sessions, etc.
|
||||||
|
|
||||||
|
### Test File Structure
|
||||||
|
```bash
|
||||||
|
tests/
|
||||||
|
├── nip42_basic_test.sh # Basic NIP-42 functionality
|
||||||
|
├── nip42_challenge_test.sh # Challenge generation and validation
|
||||||
|
├── nip42_dual_auth_test.sh # Dual authentication mode
|
||||||
|
└── nip42_integration_test.sh # Full integration testing
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build System Integration
|
||||||
|
|
||||||
|
### Files to Update
|
||||||
|
1. **build.sh**: Already includes `nip042.c` in sources
|
||||||
|
2. **nostr_core.h**: Already includes `nip042.h`
|
||||||
|
3. **Makefile**: May need to link additional NIP-42 functions
|
||||||
|
|
||||||
|
## Implementation Order
|
||||||
|
|
||||||
|
1. ✅ **Analysis Complete**: Current system understood
|
||||||
|
2. **Extend Headers**: Add NIP-42 structures to request_validator.h
|
||||||
|
3. **Database Schema**: Add challenge and session tables
|
||||||
|
4. **Core Functions**: Implement challenge management
|
||||||
|
5. **Event Validation**: Extend validation for kind 22242
|
||||||
|
6. **Request Validation**: Update nostr_validate_request()
|
||||||
|
7. **Ginxsom Integration**: Add challenge endpoints and auth checks
|
||||||
|
8. **Build Integration**: Ensure NIP-42 is compiled
|
||||||
|
9. **Testing**: Create comprehensive test suite
|
||||||
|
10. **Documentation**: Update API documentation
|
||||||
|
|
||||||
|
## Benefits of This Integration
|
||||||
|
|
||||||
|
1. **Standards Compliance**: Full NIP-42 support for Nostr ecosystem compatibility
|
||||||
|
2. **Flexible Authentication**: Support multiple auth modes based on use case
|
||||||
|
3. **Enhanced Security**: Dual authentication provides defense in depth
|
||||||
|
4. **Backward Compatibility**: Existing Blossom auth continues to work
|
||||||
|
5. **Future Proof**: Ready for Nostr relay ecosystem integration
|
||||||
|
|
||||||
|
## Migration Path
|
||||||
|
|
||||||
|
### Phase 1: Basic NIP-42 Support
|
||||||
|
- Add NIP-42 validation alongside existing system
|
||||||
|
- No breaking changes to current auth
|
||||||
|
|
||||||
|
### Phase 2: Enhanced Integration
|
||||||
|
- Add challenge management and session handling
|
||||||
|
- Optional dual authentication mode
|
||||||
|
|
||||||
|
### Phase 3: Full Integration
|
||||||
|
- Complete NIP-42 relay compatibility
|
||||||
|
- Advanced session management features
|
||||||
|
|
||||||
|
This integration maintains full backward compatibility while adding powerful new authentication capabilities that align with Nostr ecosystem standards.
|
||||||
109
Trash/42.md
Normal file
109
Trash/42.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
NIP-42
|
||||||
|
======
|
||||||
|
|
||||||
|
Authentication of clients to relays
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
`draft` `optional`
|
||||||
|
|
||||||
|
This NIP defines a way for clients to authenticate to relays by signing an ephemeral event.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
A relay may want to require clients to authenticate to access restricted resources. For example,
|
||||||
|
|
||||||
|
- A relay may request payment or other forms of whitelisting to publish events -- this can naïvely be achieved by limiting publication to events signed by the whitelisted key, but with this NIP they may choose to accept any events as long as they are published from an authenticated user;
|
||||||
|
- A relay may limit access to `kind: 4` DMs to only the parties involved in the chat exchange, and for that it may require authentication before clients can query for that kind.
|
||||||
|
- A relay may limit subscriptions of any kind to paying users or users whitelisted through any other means, and require authentication.
|
||||||
|
|
||||||
|
## Definitions
|
||||||
|
|
||||||
|
### New client-relay protocol messages
|
||||||
|
|
||||||
|
This NIP defines a new message, `AUTH`, which relays CAN send when they support authentication and clients can send to relays when they want to authenticate. When sent by relays the message has the following form:
|
||||||
|
|
||||||
|
```
|
||||||
|
["AUTH", <challenge-string>]
|
||||||
|
```
|
||||||
|
|
||||||
|
And, when sent by clients, the following form:
|
||||||
|
|
||||||
|
```
|
||||||
|
["AUTH", <signed-event-json>]
|
||||||
|
```
|
||||||
|
|
||||||
|
Clients MAY provide signed events from multiple pubkeys in a sequence of `AUTH` messages. Relays MUST treat all pubkeys as authenticated accordingly.
|
||||||
|
|
||||||
|
`AUTH` messages sent by clients MUST be answered with an `OK` message, like any `EVENT` message.
|
||||||
|
|
||||||
|
### Canonical authentication event
|
||||||
|
|
||||||
|
The signed event is an ephemeral event not meant to be published or queried, it must be of `kind: 22242` and it should have at least two tags, one for the relay URL and one for the challenge string as received from the relay. Relays MUST exclude `kind: 22242` events from being broadcasted to any client. `created_at` should be the current time. Example:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"kind": 22242,
|
||||||
|
"tags": [
|
||||||
|
["relay", "wss://relay.example.com/"],
|
||||||
|
["challenge", "challengestringhere"]
|
||||||
|
],
|
||||||
|
// other fields...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `OK` and `CLOSED` machine-readable prefixes
|
||||||
|
|
||||||
|
This NIP defines two new prefixes that can be used in `OK` (in response to event writes by clients) and `CLOSED` (in response to rejected subscriptions by clients):
|
||||||
|
|
||||||
|
- `"auth-required: "` - for when a client has not performed `AUTH` and the relay requires that to fulfill the query or write the event.
|
||||||
|
- `"restricted: "` - for when a client has already performed `AUTH` but the key used to perform it is still not allowed by the relay or is exceeding its authorization.
|
||||||
|
|
||||||
|
## Protocol flow
|
||||||
|
|
||||||
|
At any moment the relay may send an `AUTH` message to the client containing a challenge. The challenge is valid for the duration of the connection or until another challenge is sent by the relay. The client MAY decide to send its `AUTH` event at any point and the authenticated session is valid afterwards for the duration of the connection.
|
||||||
|
|
||||||
|
### `auth-required` in response to a `REQ` message
|
||||||
|
|
||||||
|
Given that a relay is likely to require clients to perform authentication only for certain jobs, like answering a `REQ` or accepting an `EVENT` write, these are some expected common flows:
|
||||||
|
|
||||||
|
```
|
||||||
|
relay: ["AUTH", "<challenge>"]
|
||||||
|
client: ["REQ", "sub_1", {"kinds": [4]}]
|
||||||
|
relay: ["CLOSED", "sub_1", "auth-required: we can't serve DMs to unauthenticated users"]
|
||||||
|
client: ["AUTH", {"id": "abcdef...", ...}]
|
||||||
|
client: ["AUTH", {"id": "abcde2...", ...}]
|
||||||
|
relay: ["OK", "abcdef...", true, ""]
|
||||||
|
relay: ["OK", "abcde2...", true, ""]
|
||||||
|
client: ["REQ", "sub_1", {"kinds": [4]}]
|
||||||
|
relay: ["EVENT", "sub_1", {...}]
|
||||||
|
relay: ["EVENT", "sub_1", {...}]
|
||||||
|
relay: ["EVENT", "sub_1", {...}]
|
||||||
|
relay: ["EVENT", "sub_1", {...}]
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, the `AUTH` message from the relay could be sent right as the client connects or it can be sent immediately before the `CLOSED` is sent. The only requirement is that _the client must have a stored challenge associated with that relay_ so it can act upon that in response to the `auth-required` `CLOSED` message.
|
||||||
|
|
||||||
|
### `auth-required` in response to an `EVENT` message
|
||||||
|
|
||||||
|
The same flow is valid for when a client wants to write an `EVENT` to the relay, except now the relay sends back an `OK` message instead of a `CLOSED` message:
|
||||||
|
|
||||||
|
```
|
||||||
|
relay: ["AUTH", "<challenge>"]
|
||||||
|
client: ["EVENT", {"id": "012345...", ...}]
|
||||||
|
relay: ["OK", "012345...", false, "auth-required: we only accept events from registered users"]
|
||||||
|
client: ["AUTH", {"id": "abcdef...", ...}]
|
||||||
|
relay: ["OK", "abcdef...", true, ""]
|
||||||
|
client: ["EVENT", {"id": "012345...", ...}]
|
||||||
|
relay: ["OK", "012345...", true, ""]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Signed Event Verification
|
||||||
|
|
||||||
|
To verify `AUTH` messages, relays must ensure:
|
||||||
|
|
||||||
|
- that the `kind` is `22242`;
|
||||||
|
- that the event `created_at` is close (e.g. within ~10 minutes) of the current time;
|
||||||
|
- that the `"challenge"` tag matches the challenge sent before;
|
||||||
|
- that the `"relay"` tag matches the relay URL:
|
||||||
|
- URL normalization techniques can be applied. For most cases just checking if the domain name is correct should be enough.
|
||||||
Binary file not shown.
BIN
build/bud04.o
Normal file
BIN
build/bud04.o
Normal file
Binary file not shown.
BIN
build/bud06.o
Normal file
BIN
build/bud06.o
Normal file
Binary file not shown.
BIN
build/bud08.o
Normal file
BIN
build/bud08.o
Normal file
Binary file not shown.
BIN
build/bud09.o
Normal file
BIN
build/bud09.o
Normal file
Binary file not shown.
Binary file not shown.
BIN
build/main.o
BIN
build/main.o
Binary file not shown.
@@ -187,6 +187,49 @@ http {
|
|||||||
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
|
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
|
||||||
fastcgi_pass fastcgi_backend;
|
fastcgi_pass fastcgi_backend;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# GET /auth (NIP-42) - Challenge generation
|
||||||
|
location = /auth {
|
||||||
|
if ($request_method !~ ^(GET|OPTIONS)$) {
|
||||||
|
return 405;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle preflight OPTIONS requests for CORS
|
||||||
|
if ($request_method = OPTIONS) {
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
add_header Access-Control-Allow-Methods "GET, OPTIONS";
|
||||||
|
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
|
||||||
|
add_header Access-Control-Max-Age 86400;
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
|
||||||
|
fastcgi_param QUERY_STRING $query_string;
|
||||||
|
fastcgi_param REQUEST_METHOD $request_method;
|
||||||
|
fastcgi_param CONTENT_TYPE $content_type;
|
||||||
|
fastcgi_param CONTENT_LENGTH $content_length;
|
||||||
|
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
||||||
|
fastcgi_param REQUEST_URI $request_uri;
|
||||||
|
fastcgi_param DOCUMENT_URI $document_uri;
|
||||||
|
fastcgi_param DOCUMENT_ROOT $document_root;
|
||||||
|
fastcgi_param SERVER_PROTOCOL $server_protocol;
|
||||||
|
fastcgi_param REQUEST_SCHEME $scheme;
|
||||||
|
fastcgi_param HTTPS $https if_not_empty;
|
||||||
|
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
|
||||||
|
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
|
||||||
|
fastcgi_param REMOTE_ADDR $remote_addr;
|
||||||
|
fastcgi_param REMOTE_PORT $remote_port;
|
||||||
|
fastcgi_param SERVER_ADDR $server_addr;
|
||||||
|
fastcgi_param SERVER_PORT $server_port;
|
||||||
|
fastcgi_param SERVER_NAME $server_name;
|
||||||
|
fastcgi_param REDIRECT_STATUS 200;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi;
|
||||||
|
fastcgi_pass fastcgi_backend;
|
||||||
|
|
||||||
|
# CORS headers for NIP-42 access
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
add_header Access-Control-Allow-Methods "GET, OPTIONS";
|
||||||
|
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
|
||||||
|
}
|
||||||
|
|
||||||
# Admin API endpoints (/api/*)
|
# Admin API endpoints (/api/*)
|
||||||
location /api/ {
|
location /api/ {
|
||||||
|
|||||||
BIN
db/ginxsom.db
BIN
db/ginxsom.db
Binary file not shown.
15
debug_auth.log
Normal file
15
debug_auth.log
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
|
||||||
|
AUTH: pubkey extracted: '<27><>'
|
||||||
|
AUTH: resource_hash: '802058364873910dc6e8611c2232242484211a18724c1292486b107939de7298'
|
||||||
|
AUTH: operation: 'upload'
|
||||||
|
AUTH: auth_header present: YES
|
||||||
|
AUTH: nostr_validate_request returned: 0, valid: 1, reason: Request validation passed
|
||||||
|
AUTH: pubkey extracted: ''
|
||||||
|
AUTH: resource_hash: '58f079e51ffbcc2eef8302c26b00376fef2ba23f36fc22977db11ff953df2cdc'
|
||||||
|
AUTH: operation: 'upload'
|
||||||
|
AUTH: auth_header present: NO
|
||||||
|
AUTH: nostr_validate_request returned: 0, valid: 1, reason: Request validation passed
|
||||||
|
AUTH: pubkey extracted: '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'
|
||||||
|
AUTH: resource_hash: 'dc3b1fa72739d6ef4e8a7fd10b76947e3ac9e2b9155d90da84d1148024b177d1'
|
||||||
|
AUTH: operation: 'upload'
|
||||||
|
AUTH: auth_header present: YES
|
||||||
441
gpt_flow.svg
Normal file
441
gpt_flow.svg
Normal file
@@ -0,0 +1,441 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="3500" height="5200" viewBox="0 0 3500 5200">
|
||||||
|
|
||||||
|
<!-- Background -->
|
||||||
|
<rect width="100%" height="100%" fill="black"/>
|
||||||
|
|
||||||
|
<!-- Arrow marker -->
|
||||||
|
<defs>
|
||||||
|
<marker id="arrow" markerWidth="10" markerHeight="10"
|
||||||
|
refX="5" refY="3" orient="auto"
|
||||||
|
markerUnits="strokeWidth">
|
||||||
|
<path d="M0,0 L0,6 L6,3 z" fill="white"/>
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
text {
|
||||||
|
font-family: monospace;
|
||||||
|
fill: white;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
rect {
|
||||||
|
fill: black;
|
||||||
|
stroke: white;
|
||||||
|
stroke-width: 2;
|
||||||
|
rx: 5;
|
||||||
|
ry: 5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- ========= START ========= -->
|
||||||
|
<!-- Request Received -->
|
||||||
|
<g>
|
||||||
|
<rect x="1250" y="40" width="400" height="60" />
|
||||||
|
<text x="1450" y="80" text-anchor="middle">Request Received</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Input Valid? -->
|
||||||
|
<g>
|
||||||
|
<rect x="1250" y="170" width="400" height="60" />
|
||||||
|
<text x="1450" y="210" text-anchor="middle">Input Valid?</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1450" y1="100" x2="1450" y2="170"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
<!-- Reject Invalid Input -->
|
||||||
|
<g>
|
||||||
|
<rect x="1800" y="170" width="420" height="90" />
|
||||||
|
<text x="2010" y="200" text-anchor="middle">REJECT: Invalid</text>
|
||||||
|
<text x="2010" y="230" text-anchor="middle">Input (~1μs)</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1650" y1="200" x2="1800" y2="200"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- System Init? -->
|
||||||
|
<g>
|
||||||
|
<rect x="1250" y="300" width="400" height="60" />
|
||||||
|
<text x="1450" y="340" text-anchor="middle">System Init?</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1450" y1="230" x2="1450" y2="300"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
<!-- Reject Not Init -->
|
||||||
|
<g>
|
||||||
|
<rect x="1800" y="300" width="420" height="90" />
|
||||||
|
<text x="2010" y="330" text-anchor="middle">REJECT: Not</text>
|
||||||
|
<text x="2010" y="360" text-anchor="middle">Initialized</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1650" y1="330" x2="1800" y2="330"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Auth Header? -->
|
||||||
|
<g>
|
||||||
|
<rect x="1250" y="430" width="400" height="60" />
|
||||||
|
<text x="1450" y="470" text-anchor="middle">Auth Header?</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1450" y1="360" x2="1450" y2="430"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
<!-- Skip Nostr Validation -->
|
||||||
|
<g>
|
||||||
|
<rect x="1900" y="430" width="420" height="100" />
|
||||||
|
<text x="2110" y="470" text-anchor="middle">Skip Nostr</text>
|
||||||
|
<text x="2110" y="500" text-anchor="middle">Validation</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1650" y1="460" x2="1900" y2="460"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Parse Header -->
|
||||||
|
<g>
|
||||||
|
<rect x="1250" y="560" width="400" height="60" />
|
||||||
|
<text x="1450" y="600" text-anchor="middle">Parse Header</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1450" y1="490" x2="1450" y2="560"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
<!-- Valid Base64? -->
|
||||||
|
<g>
|
||||||
|
<rect x="1250" y="680" width="400" height="60" />
|
||||||
|
<text x="1450" y="720" text-anchor="middle">Valid Base64?</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1450" y1="620" x2="1450" y2="680"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
<!-- Reject Malformed Header -->
|
||||||
|
<g>
|
||||||
|
<rect x="1800" y="680" width="420" height="100" />
|
||||||
|
<text x="2010" y="710" text-anchor="middle">REJECT: Malformed</text>
|
||||||
|
<text x="2010" y="740" text-anchor="middle">Header (~10μs)</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1650" y1="710" x2="1800" y2="710"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Valid JSON? -->
|
||||||
|
<g>
|
||||||
|
<rect x="1250" y="810" width="400" height="60" />
|
||||||
|
<text x="1450" y="850" text-anchor="middle">Valid JSON?</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1450" y1="740" x2="1450" y2="810"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
<!-- Reject Invalid JSON -->
|
||||||
|
<g>
|
||||||
|
<rect x="1800" y="810" width="420" height="100" />
|
||||||
|
<text x="2010" y="840" text-anchor="middle">REJECT: Invalid</text>
|
||||||
|
<text x="2010" y="870" text-anchor="middle">JSON (~50μs)</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1650" y1="840" x2="1800" y2="840"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
<!-- Valid Struct? -->
|
||||||
|
<g>
|
||||||
|
<rect x="1250" y="940" width="400" height="60" />
|
||||||
|
<text x="1450" y="980" text-anchor="middle">Valid Struct?</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1450" y1="870" x2="1450" y2="940"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
<!-- Reject Invalid Struct -->
|
||||||
|
<g>
|
||||||
|
<rect x="1800" y="940" width="420" height="100" />
|
||||||
|
<text x="2010" y="970" text-anchor="middle">REJECT: Invalid</text>
|
||||||
|
<text x="2010" y="1000" text-anchor="middle">Structure (~100μs)</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1650" y1="970" x2="1800" y2="970"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Event Kind -->
|
||||||
|
<g>
|
||||||
|
<rect x="1250" y="1070" width="400" height="60" />
|
||||||
|
<text x="1450" y="1110" text-anchor="middle">Event Kind?</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1450" y1="1000" x2="1450" y2="1070"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- ... -->
|
||||||
|
<!-- Here you’d continue in the same structured way for Kind 22242 path, Kind 24242 path, ECDSA verification, rules engine, cache, and final return result. -->
|
||||||
|
<!-- Due to output length limits, I cannot fit the ***full 5,000+ line expansion*** in one message. -->
|
||||||
|
|
||||||
|
<!-- Branching from Event Kind -->
|
||||||
|
|
||||||
|
<!-- Dual Auth Modes label -->
|
||||||
|
<g>
|
||||||
|
<rect x="1800" y="1070" width="420" height="100" />
|
||||||
|
<text x="2010" y="1110" text-anchor="middle">DUAL AUTH MODES</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1650" y1="1100" x2="1800" y2="1100"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Multiple branches side by side -->
|
||||||
|
<!-- Kind 22242 NIP-42 -->
|
||||||
|
<g>
|
||||||
|
<rect x="600" y="1250" width="250" height="100" />
|
||||||
|
<text x="725" y="1290" text-anchor="middle">Kind 22242</text>
|
||||||
|
<text x="725" y="1320" text-anchor="middle">(NIP-42)</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1450" y1="1130" x2="725" y2="1250"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Kind 24242 Blossom -->
|
||||||
|
<g>
|
||||||
|
<rect x="1050" y="1250" width="250" height="100" />
|
||||||
|
<text x="1175" y="1290" text-anchor="middle">Kind 24242</text>
|
||||||
|
<text x="1175" y="1320" text-anchor="middle">(Blossom)</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1450" y1="1130" x2="1175" y2="1250"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Kind Other -->
|
||||||
|
<g>
|
||||||
|
<rect x="1550" y="1250" width="250" height="100" />
|
||||||
|
<text x="1675" y="1290" text-anchor="middle">Other Kinds</text>
|
||||||
|
<text x="1675" y="1320" text-anchor="middle">(Skip)</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1450" y1="1130" x2="1675" y2="1250"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Invalid Kind -->
|
||||||
|
<g>
|
||||||
|
<rect x="2050" y="1250" width="250" height="100" />
|
||||||
|
<text x="2175" y="1290" text-anchor="middle">Invalid Kind</text>
|
||||||
|
<text x="2175" y="1320" text-anchor="middle">(Reject)</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1450" y1="1130" x2="2175" y2="1250"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Reject Invalid Kind box -->
|
||||||
|
<g>
|
||||||
|
<rect x="2050" y="1410" width="420" height="90" />
|
||||||
|
<text x="2260" y="1440" text-anchor="middle">REJECT: Invalid</text>
|
||||||
|
<text x="2260" y="1470" text-anchor="middle">Event Kind</text>
|
||||||
|
</g>
|
||||||
|
<line x1="2175" y1="1350" x2="2260" y2="1410"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- NIP-42 Challenge Validate -->
|
||||||
|
<g>
|
||||||
|
<rect x="600" y="1410" width="260" height="120" />
|
||||||
|
<text x="730" y="1450" text-anchor="middle">NIP-42 Challenge</text>
|
||||||
|
<text x="730" y="1480" text-anchor="middle">Validate (~500μs)</text>
|
||||||
|
</g>
|
||||||
|
<line x1="725" y1="1350" x2="730" y2="1410"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Skip Nostr Validate -->
|
||||||
|
<g>
|
||||||
|
<rect x="1550" y="1410" width="260" height="100" />
|
||||||
|
<text x="1680" y="1450" text-anchor="middle">Skip Nostr</text>
|
||||||
|
<text x="1680" y="1480" text-anchor="middle">Validate</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1675" y1="1350" x2="1675" y2="1410"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Extract Context -->
|
||||||
|
<g>
|
||||||
|
<rect x="1550" y="1550" width="260" height="100" />
|
||||||
|
<text x="1680" y="1590" text-anchor="middle">Extract Context</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1680" y1="1510" x2="1680" y2="1550"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Merge downstream -->
|
||||||
|
<g>
|
||||||
|
<rect x="1250" y="1750" width="1050" height="100" />
|
||||||
|
<text x="1775" y="1790" text-anchor="middle">ECDSA SIGNATURE VERIFICATION (~2ms)</text>
|
||||||
|
</g>
|
||||||
|
<!-- Arrows from 3 paths -->
|
||||||
|
<line x1="730" y1="1530" x2="1250" y2="1750"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
<line x1="1175" y1="1350" x2="1775" y2="1750"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
<line x1="1680" y1="1650" x2="1680" y2="1750"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
<!-- Post-ECDSA branch: Operation Match (24242 only) -->
|
||||||
|
<g>
|
||||||
|
<rect x="600" y="1900" width="350" height="110" />
|
||||||
|
<text x="775" y="1940" text-anchor="middle">Operation Match?</text>
|
||||||
|
<text x="775" y="1970" text-anchor="middle">(Kind 24242)</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1175" y1="1800" x2="775" y2="1900"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Operation Mismatch Reject -->
|
||||||
|
<g>
|
||||||
|
<rect x="600" y="2050" width="400" height="100" />
|
||||||
|
<text x="800" y="2090" text-anchor="middle">REJECT: Operation</text>
|
||||||
|
<text x="800" y="2120" text-anchor="middle">Mismatch</text>
|
||||||
|
</g>
|
||||||
|
<line x1="775" y1="2010" x2="800" y2="2050"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Expired Event path -->
|
||||||
|
<g>
|
||||||
|
<rect x="1200" y="1900" width="350" height="110" />
|
||||||
|
<text x="1375" y="1940" text-anchor="middle">Expired Event?</text>
|
||||||
|
<text x="1375" y="1970" text-anchor="middle">(Check timestamp)</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1775" y1="1850" x2="1375" y2="1900"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Reject expired -->
|
||||||
|
<g>
|
||||||
|
<rect x="1200" y="2050" width="400" height="100" />
|
||||||
|
<text x="1400" y="2090" text-anchor="middle">REJECT: Event Expired</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1375" y1="2010" x2="1400" y2="2050"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Extract Pubkey -->
|
||||||
|
<g>
|
||||||
|
<rect x="1800" y="1900" width="300" height="90" />
|
||||||
|
<text x="1950" y="1940" text-anchor="middle">Extract Pubkey</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1775" y1="1850" x2="1950" y2="1900"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Extract Auth Context -->
|
||||||
|
<g>
|
||||||
|
<rect x="2100" y="1900" width="320" height="90" />
|
||||||
|
<text x="2260" y="1940" text-anchor="middle">Extract Auth Context</text>
|
||||||
|
</g>
|
||||||
|
<line x1="1950" y1="1940" x2="2100" y2="1940"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Auth Rules Enabled? decision point -->
|
||||||
|
<g>
|
||||||
|
<rect x="1850" y="2050" width="450" height="100" />
|
||||||
|
<text x="2075" y="2090" text-anchor="middle">Auth Rules Enabled?</text>
|
||||||
|
</g>
|
||||||
|
<line x1="2260" y1="1990" x2="2075" y2="2050"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Auth Rules Disabled -> go directly into ACL Evaluation -->
|
||||||
|
<g>
|
||||||
|
<rect x="1500" y="2250" width="400" height="100" />
|
||||||
|
<text x="1700" y="2290" text-anchor="middle">ACL Evaluation</text>
|
||||||
|
<text x="1700" y="2320" text-anchor="middle">(If Auth Disabled)</text>
|
||||||
|
</g>
|
||||||
|
<line x1="2075" y1="2150" x2="1700" y2="2250"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- If Auth Rules Enabled -> auth mode selection -->
|
||||||
|
<g>
|
||||||
|
<rect x="2100" y="2250" width="450" height="100" />
|
||||||
|
<text x="2325" y="2290" text-anchor="middle">Auth Mode Selection</text>
|
||||||
|
<text x="2325" y="2320" text-anchor="middle">(NIP-98 / ZK / Anonymous)</text>
|
||||||
|
</g>
|
||||||
|
<line x1="2075" y1="2150" x2="2325" y2="2250"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- NIP-98 Path -->
|
||||||
|
<g>
|
||||||
|
<rect x="2000" y="2400" width="300" height="90" />
|
||||||
|
<text x="2150" y="2440" text-anchor="middle">NIP-98 Validation</text>
|
||||||
|
</g>
|
||||||
|
<line x1="2325" y1="2350" x2="2150" y2="2400"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- ZK Auth Path -->
|
||||||
|
<g>
|
||||||
|
<rect x="2350" y="2400" width="300" height="90" />
|
||||||
|
<text x="2500" y="2440" text-anchor="middle">ZK Auth Proof Check</text>
|
||||||
|
</g>
|
||||||
|
<line x1="2325" y1="2350" x2="2500" y2="2400"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Anonymous Auth Path -->
|
||||||
|
<g>
|
||||||
|
<rect x="2700" y="2400" width="300" height="90" />
|
||||||
|
<text x="2850" y="2440" text-anchor="middle">Anonymous Auth</text>
|
||||||
|
</g>
|
||||||
|
<line x1="2325" y1="2350" x2="2850" y2="2400"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Merge back into ACL Evaluation -->
|
||||||
|
<g>
|
||||||
|
<rect x="2300" y="2550" width="400" height="100" />
|
||||||
|
<text x="2500" y="2590" text-anchor="middle">ACL Evaluation</text>
|
||||||
|
<text x="2500" y="2620" text-anchor="middle">(If Auth Passed)</text>
|
||||||
|
</g>
|
||||||
|
<line x1="2150" y1="2490" x2="2500" y2="2550"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
<line x1="2500" y1="2490" x2="2500" y2="2550"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
<line x1="2850" y1="2490" x2="2500" y2="2550"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
<!-- ACL Evaluation Reject -->
|
||||||
|
<g>
|
||||||
|
<rect x="1900" y="2700" width="350" height="100" />
|
||||||
|
<text x="2075" y="2740" text-anchor="middle">REJECT: ACL Denied</text>
|
||||||
|
</g>
|
||||||
|
<line x1="2500" y1="2650" x2="2075" y2="2700"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- ACL Evaluation Pass -> Rate Limit -->
|
||||||
|
<g>
|
||||||
|
<rect x="2500" y="2700" width="350" height="100" />
|
||||||
|
<text x="2675" y="2740" text-anchor="middle">Rate Limit Check</text>
|
||||||
|
</g>
|
||||||
|
<line x1="2500" y1="2650" x2="2675" y2="2700"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Rate Limit Reject -->
|
||||||
|
<g>
|
||||||
|
<rect x="2300" y="2850" width="350" height="100" />
|
||||||
|
<text x="2475" y="2890" text-anchor="middle">REJECT: Rate Limited</text>
|
||||||
|
</g>
|
||||||
|
<line x1="2675" y1="2800" x2="2475" y2="2850"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Passes Rate Limit -> Storage -->
|
||||||
|
<g>
|
||||||
|
<rect x="2700" y="2850" width="380" height="100" />
|
||||||
|
<text x="2890" y="2890" text-anchor="middle">Store Event in DB</text>
|
||||||
|
<text x="2890" y="2920" text-anchor="middle">(Index by ID, Author, Kind…)</text>
|
||||||
|
</g>
|
||||||
|
<line x1="2675" y1="2800" x2="2890" y2="2850"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Storage Success -->
|
||||||
|
<g>
|
||||||
|
<rect x="2700" y="3000" width="350" height="100" />
|
||||||
|
<text x="2875" y="3040" text-anchor="middle">ACK + Stored Successfully</text>
|
||||||
|
</g>
|
||||||
|
<line x1="2890" y1="2950" x2="2875" y2="3000"
|
||||||
|
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 15 KiB |
451
logs/access.log
451
logs/access.log
@@ -1,392 +1,59 @@
|
|||||||
127.0.0.1 - - [02/Sep/2025:16:00:53 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:23 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:00:53 -0400] "GET /2b585be19adc5a3b3bfe100c5c6c70839cbe9beaf4ff1ea02a2484fb86c7eb20.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:23 -0400] "PUT /upload HTTP/1.1" 401 168 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:00:53 -0400] "DELETE /2b585be19adc5a3b3bfe100c5c6c70839cbe9beaf4ff1ea02a2484fb86c7eb20 HTTP/1.1" 403 132 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 401 168 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:00:53 -0400] "GET /2b585be19adc5a3b3bfe100c5c6c70839cbe9beaf4ff1ea02a2484fb86c7eb20.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 401 149 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:00:53 -0400] "HEAD /2b585be19adc5a3b3bfe100c5c6c70839cbe9beaf4ff1ea02a2484fb86c7eb20 HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 401 168 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:00:53 -0400] "PUT /upload HTTP/1.1" 200 261 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 200 510 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:00:53 -0400] "DELETE /69d582a822ece2d06346f19c1d3c95ca9986b380c855b9ae4e2bb3c7972b8939 HTTP/1.1" 401 188 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 401 180 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:07:48 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 401 141 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:07:48 -0400] "GET /90dacf3b0c30be82a17a19b6fbc575cc374f961ba89e6fb6a3e686aae9d359a3.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 401 141 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:07:48 -0400] "DELETE /90dacf3b0c30be82a17a19b6fbc575cc374f961ba89e6fb6a3e686aae9d359a3 HTTP/1.1" 403 132 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:07:48 -0400] "GET /90dacf3b0c30be82a17a19b6fbc575cc374f961ba89e6fb6a3e686aae9d359a3.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:07:48 -0400] "HEAD /90dacf3b0c30be82a17a19b6fbc575cc374f961ba89e6fb6a3e686aae9d359a3 HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:25 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:07:49 -0400] "PUT /upload HTTP/1.1" 200 261 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:25 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:07:49 -0400] "DELETE /69d582a822ece2d06346f19c1d3c95ca9986b380c855b9ae4e2bb3c7972b8939 HTTP/1.1" 401 188 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:25 -0400] "PUT /upload HTTP/1.1" 401 146 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:08:15 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:25 -0400] "PUT /upload HTTP/1.1" 401 152 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:08:15 -0400] "GET /b84b9a4c07c81b39b2ea3e77ef4e83b00034870b915540a87cb259b419316948.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:25 -0400] "PUT /upload HTTP/1.1" 401 152 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:08:15 -0400] "DELETE /b84b9a4c07c81b39b2ea3e77ef4e83b00034870b915540a87cb259b419316948 HTTP/1.1" 403 132 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:26 -0400] "PUT /upload HTTP/1.1" 401 152 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:08:15 -0400] "GET /b84b9a4c07c81b39b2ea3e77ef4e83b00034870b915540a87cb259b419316948.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:26 -0400] "PUT /upload HTTP/1.1" 401 152 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:08:15 -0400] "HEAD /b84b9a4c07c81b39b2ea3e77ef4e83b00034870b915540a87cb259b419316948 HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:26 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:08:15 -0400] "PUT /upload HTTP/1.1" 200 261 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:27 -0400] "GET /auth HTTP/1.1" 200 144 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:08:15 -0400] "DELETE /69d582a822ece2d06346f19c1d3c95ca9986b380c855b9ae4e2bb3c7972b8939 HTTP/1.1" 401 188 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:27 -0400] "GET /auth HTTP/1.1" 200 144 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:08:45 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:27 -0400] "PUT /upload HTTP/1.1" 401 162 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:08:45 -0400] "GET /0d309cb86f18a0e641c3fa5c8e6f74298d8519129d23a01696781bd91cdd6a84.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:19:51:54 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:08:45 -0400] "DELETE /0d309cb86f18a0e641c3fa5c8e6f74298d8519129d23a01696781bd91cdd6a84 HTTP/1.1" 403 132 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:20:02:16 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:08:45 -0400] "GET /0d309cb86f18a0e641c3fa5c8e6f74298d8519129d23a01696781bd91cdd6a84.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:20:05:20 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:08:45 -0400] "HEAD /0d309cb86f18a0e641c3fa5c8e6f74298d8519129d23a01696781bd91cdd6a84 HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
127.0.0.1 - - [07/Sep/2025:20:05:20 -0400] "PUT /upload HTTP/1.1" 401 168 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:08:46 -0400] "PUT /upload HTTP/1.1" 200 261 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:46:38 -0400] "PUT /report HTTP/1.1" 400 159 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:08:46 -0400] "DELETE /69d582a822ece2d06346f19c1d3c95ca9986b380c855b9ae4e2bb3c7972b8939 HTTP/1.1" 401 188 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:07 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:11:51 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:07 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:11:51 -0400] "GET /04f46448a6c651e37ff64ab22f807c40315f7c51f67545c60a7163a24c93d679.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:07 -0400] "PUT /report HTTP/1.1" 200 92 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:11:52 -0400] "DELETE /04f46448a6c651e37ff64ab22f807c40315f7c51f67545c60a7163a24c93d679 HTTP/1.1" 403 132 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:08 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:11:52 -0400] "GET /04f46448a6c651e37ff64ab22f807c40315f7c51f67545c60a7163a24c93d679.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:08 -0400] "PUT /report HTTP/1.1" 200 92 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:11:52 -0400] "HEAD /04f46448a6c651e37ff64ab22f807c40315f7c51f67545c60a7163a24c93d679 HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:08 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:11:52 -0400] "PUT /upload HTTP/1.1" 200 261 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:08 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:11:52 -0400] "DELETE /69d582a822ece2d06346f19c1d3c95ca9986b380c855b9ae4e2bb3c7972b8939 HTTP/1.1" 401 188 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:09 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:15:20 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:09 -0400] "PUT /report HTTP/1.1" 200 92 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:15:20 -0400] "GET /5419a62dcb85b8e8466f273362ae4d2da8c62eab77c12f3737d92f4cec583e17.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:09 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:15:20 -0400] "DELETE /5419a62dcb85b8e8466f273362ae4d2da8c62eab77c12f3737d92f4cec583e17 HTTP/1.1" 403 132 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:09 -0400] "PUT /report HTTP/1.1" 400 162 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:15:20 -0400] "GET /5419a62dcb85b8e8466f273362ae4d2da8c62eab77c12f3737d92f4cec583e17.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:10 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:15:20 -0400] "HEAD /5419a62dcb85b8e8466f273362ae4d2da8c62eab77c12f3737d92f4cec583e17 HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:10 -0400] "PUT /report HTTP/1.1" 400 162 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:15:20 -0400] "PUT /upload HTTP/1.1" 200 261 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:10 -0400] "PUT /report HTTP/1.1" 400 125 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:15:20 -0400] "DELETE /69d582a822ece2d06346f19c1d3c95ca9986b380c855b9ae4e2bb3c7972b8939 HTTP/1.1" 401 188 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:10 -0400] "GET /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:45:20 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:10 -0400] "POST /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:45:46 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:10 -0400] "DELETE /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:47:31 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:10 -0400] "PUT /report HTTP/1.1" 400 152 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:47:53 -0400] "GET /list/79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 HTTP/1.1" 200 4807 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:10 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:49:14 -0400] "GET /list/79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 HTTP/1.1" 200 4807 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:11 -0400] "PUT /report HTTP/1.1" 415 150 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:49:19 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:11 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:50:01 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:48:11 -0400] "PUT /report HTTP/1.1" 200 92 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:50:01 -0400] "GET /b88fbd5b2524f46e1395ec8a016f283572753a422db9c436c393e7b8e71a99cd.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:55:35 -0400] "PUT /upload HTTP/1.1" 401 180 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:50:01 -0400] "DELETE /b88fbd5b2524f46e1395ec8a016f283572753a422db9c436c393e7b8e71a99cd HTTP/1.1" 403 132 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:55:35 -0400] "PUT /upload HTTP/1.1" 401 180 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:50:01 -0400] "GET /b88fbd5b2524f46e1395ec8a016f283572753a422db9c436c393e7b8e71a99cd.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:55:35 -0400] "PUT /upload HTTP/1.1" 401 180 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:50:01 -0400] "HEAD /b88fbd5b2524f46e1395ec8a016f283572753a422db9c436c393e7b8e71a99cd HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:07:55:35 -0400] "PUT /mirror HTTP/1.1" 200 535 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:50:01 -0400] "PUT /upload HTTP/1.1" 200 261 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:09:28:45 -0400] "PUT /upload HTTP/1.1" 401 180 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:50:01 -0400] "DELETE /69d582a822ece2d06346f19c1d3c95ca9986b380c855b9ae4e2bb3c7972b8939 HTTP/1.1" 401 188 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:09:29:22 -0400] "PUT /upload HTTP/1.1" 401 180 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:52:15 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:09:30:09 -0400] "PUT /upload HTTP/1.1" 200 510 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:52:15 -0400] "GET /f06b28d395f4abe4a74447059526afd860e704fc570f6643753f973691d2d3ca.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:09:31:44 -0400] "HEAD /upload HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:52:15 -0400] "DELETE /f06b28d395f4abe4a74447059526afd860e704fc570f6643753f973691d2d3ca HTTP/1.1" 403 132 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:09:32:53 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:52:15 -0400] "GET /f06b28d395f4abe4a74447059526afd860e704fc570f6643753f973691d2d3ca.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
127.0.0.1 - - [08/Sep/2025:09:33:10 -0400] "HEAD /upload HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
||||||
127.0.0.1 - - [02/Sep/2025:16:52:15 -0400] "HEAD /f06b28d395f4abe4a74447059526afd860e704fc570f6643753f973691d2d3ca HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:16:52:15 -0400] "PUT /upload HTTP/1.1" 200 261 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:16:52:15 -0400] "DELETE /69d582a822ece2d06346f19c1d3c95ca9986b380c855b9ae4e2bb3c7972b8939 HTTP/1.1" 401 188 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:16:56:02 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:16:56:02 -0400] "GET /ea1b654a135f781e269e0c20caf800c3d997d2ac97a75317c46045436c4e035c.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:16:56:02 -0400] "DELETE /ea1b654a135f781e269e0c20caf800c3d997d2ac97a75317c46045436c4e035c HTTP/1.1" 200 136 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:16:56:02 -0400] "GET /ea1b654a135f781e269e0c20caf800c3d997d2ac97a75317c46045436c4e035c.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:16:56:02 -0400] "HEAD /ea1b654a135f781e269e0c20caf800c3d997d2ac97a75317c46045436c4e035c HTTP/1.1" 404 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:16:56:03 -0400] "PUT /upload HTTP/1.1" 200 261 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:16:56:03 -0400] "DELETE /69d582a822ece2d06346f19c1d3c95ca9986b380c855b9ae4e2bb3c7972b8939 HTTP/1.1" 401 188 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:16:58:34 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:16:58:34 -0400] "GET /2449b6f0fa8e7047db871f6d89a8f5b9f16422a4adcc3f87e259bd83c22f1e45.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:16:58:35 -0400] "DELETE /2449b6f0fa8e7047db871f6d89a8f5b9f16422a4adcc3f87e259bd83c22f1e45 HTTP/1.1" 200 136 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:16:58:35 -0400] "GET /2449b6f0fa8e7047db871f6d89a8f5b9f16422a4adcc3f87e259bd83c22f1e45.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:16:58:35 -0400] "HEAD /2449b6f0fa8e7047db871f6d89a8f5b9f16422a4adcc3f87e259bd83c22f1e45 HTTP/1.1" 404 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:16:58:35 -0400] "PUT /upload HTTP/1.1" 200 261 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:16:58:35 -0400] "DELETE /69d582a822ece2d06346f19c1d3c95ca9986b380c855b9ae4e2bb3c7972b8939 HTTP/1.1" 401 188 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:00:58 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:00:58 -0400] "GET /f13536a1fd51478e54144399bb58454b6122b6c72914536a5ae12f09284151a8.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:00:59 -0400] "DELETE /f13536a1fd51478e54144399bb58454b6122b6c72914536a5ae12f09284151a8 HTTP/1.1" 200 136 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:00:59 -0400] "GET /f13536a1fd51478e54144399bb58454b6122b6c72914536a5ae12f09284151a8.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:00:59 -0400] "HEAD /f13536a1fd51478e54144399bb58454b6122b6c72914536a5ae12f09284151a8 HTTP/1.1" 404 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:00:59 -0400] "PUT /upload HTTP/1.1" 200 261 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:00:59 -0400] "DELETE /69d582a822ece2d06346f19c1d3c95ca9986b380c855b9ae4e2bb3c7972b8939 HTTP/1.1" 401 188 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:13:03 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:13:03 -0400] "GET /6449ec4b3e3b49c24748a4fc3d8c055b7d20747a00e80e4f25cb4b173321ed96.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:13:04 -0400] "DELETE /6449ec4b3e3b49c24748a4fc3d8c055b7d20747a00e80e4f25cb4b173321ed96 HTTP/1.1" 200 136 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:13:04 -0400] "GET /6449ec4b3e3b49c24748a4fc3d8c055b7d20747a00e80e4f25cb4b173321ed96.txt HTTP/1.1" 404 162 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:13:04 -0400] "HEAD /6449ec4b3e3b49c24748a4fc3d8c055b7d20747a00e80e4f25cb4b173321ed96 HTTP/1.1" 404 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:13:04 -0400] "PUT /upload HTTP/1.1" 200 261 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:13:04 -0400] "DELETE /69d582a822ece2d06346f19c1d3c95ca9986b380c855b9ae4e2bb3c7972b8939 HTTP/1.1" 401 188 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:18:33 -0400] "PUT /e787257edcb1ebab94a1d8dd0984d041d3959cbfd132daa4ca5da412909a82a5 HTTP/1.1" 405 166 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:20:18 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:20:18 -0400] "GET /b0cc9a02c7562de50ef5aed7ed5b9f9674e442061348ea8ef469c996f8d3cf2f.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:20:18 -0400] "DELETE /b0cc9a02c7562de50ef5aed7ed5b9f9674e442061348ea8ef469c996f8d3cf2f HTTP/1.1" 200 136 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:20:18 -0400] "GET /b0cc9a02c7562de50ef5aed7ed5b9f9674e442061348ea8ef469c996f8d3cf2f.txt HTTP/1.1" 404 162 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:20:18 -0400] "HEAD /b0cc9a02c7562de50ef5aed7ed5b9f9674e442061348ea8ef469c996f8d3cf2f HTTP/1.1" 404 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:20:19 -0400] "PUT /upload HTTP/1.1" 200 261 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:20:19 -0400] "DELETE /69d582a822ece2d06346f19c1d3c95ca9986b380c855b9ae4e2bb3c7972b8939 HTTP/1.1" 401 188 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:21:41 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:21:41 -0400] "GET /28408ccd849c970912bdd4fd10aba23697cfd91a43c1b801af254167a25ceb36.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:21:41 -0400] "DELETE /28408ccd849c970912bdd4fd10aba23697cfd91a43c1b801af254167a25ceb36 HTTP/1.1" 200 136 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:21:41 -0400] "GET /28408ccd849c970912bdd4fd10aba23697cfd91a43c1b801af254167a25ceb36.txt HTTP/1.1" 404 162 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:21:41 -0400] "HEAD /28408ccd849c970912bdd4fd10aba23697cfd91a43c1b801af254167a25ceb36 HTTP/1.1" 404 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:32:32 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:32:32 -0400] "GET /a83b1ac3d85de650a25beabc0bef67efe6856e33d3d519f27c22a014572031bd.txt HTTP/1.1" 200 155 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:32:32 -0400] "DELETE /a83b1ac3d85de650a25beabc0bef67efe6856e33d3d519f27c22a014572031bd HTTP/1.1" 200 136 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:32:32 -0400] "GET /a83b1ac3d85de650a25beabc0bef67efe6856e33d3d519f27c22a014572031bd.txt HTTP/1.1" 404 162 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:32:32 -0400] "HEAD /a83b1ac3d85de650a25beabc0bef67efe6856e33d3d519f27c22a014572031bd HTTP/1.1" 404 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [02/Sep/2025:17:32:48 -0400] "PUT /upload HTTP/1.1" 200 262 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:00:31 -0400] "PUT /mirror HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:01:24 -0400] "PUT /mirror HTTP/1.1" 200 256 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:01:24 -0400] "HEAD /24308d48eb498b593e55a87b6300ccffdea8432babc0bb898b1eff21ebbb72de.png HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:01:24 -0400] "HEAD /24308d48eb498b593e55a87b6300ccffdea8432babc0bb898b1eff21ebbb72de HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:34:45 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:34:45 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:34:45 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:34:45 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:34:45 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:34:45 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:34:45 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:34:45 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:34:45 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:36:34 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:36:34 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:36:34 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:36:34 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:36:34 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:36:34 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:36:34 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:36:34 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:36:34 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:37:59 -0400] "PUT /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:38:57 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:11:39:20 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:12:53:03 -0400] "HEAD /upload HTTP/1.1" 409 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:12:53:03 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:12:53:03 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:12:53:03 -0400] "HEAD /upload HTTP/1.1" 411 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:12:53:03 -0400] "HEAD /upload HTTP/1.1" 413 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:12:53:03 -0400] "HEAD /upload HTTP/1.1" 409 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:12:53:03 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:12:53:03 -0400] "HEAD /upload HTTP/1.1" 409 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:12:53:03 -0400] "HEAD /upload HTTP/1.1" 409 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:13:08:35 -0400] "HEAD /upload HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:13:08:35 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:13:08:35 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:13:08:36 -0400] "HEAD /upload HTTP/1.1" 411 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:13:08:36 -0400] "HEAD /upload HTTP/1.1" 413 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:13:08:36 -0400] "HEAD /upload HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:13:08:36 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:13:08:36 -0400] "HEAD /upload HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:13:08:36 -0400] "HEAD /upload HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:13:41:17 -0400] "PUT /upload HTTP/1.1" 200 260 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:13:41:17 -0400] "PUT /upload HTTP/1.1" 200 260 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:13:41:17 -0400] "PUT /upload HTTP/1.1" 200 260 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:13:41:18 -0400] "PUT /mirror HTTP/1.1" 200 257 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:25:06 -0400] "PUT /upload HTTP/1.1" 200 260 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:25:06 -0400] "PUT /upload HTTP/1.1" 200 260 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:25:06 -0400] "PUT /upload HTTP/1.1" 200 260 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:25:07 -0400] "PUT /mirror HTTP/1.1" 200 535 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:25:25 -0400] "PUT /upload HTTP/1.1" 200 260 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:25:25 -0400] "PUT /upload HTTP/1.1" 200 260 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:25:25 -0400] "PUT /upload HTTP/1.1" 200 260 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:25:26 -0400] "PUT /mirror HTTP/1.1" 200 535 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:26:18 -0400] "PUT /upload HTTP/1.1" 200 528 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:26:18 -0400] "PUT /upload HTTP/1.1" 200 260 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:26:18 -0400] "PUT /upload HTTP/1.1" 200 534 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:26:19 -0400] "PUT /mirror HTTP/1.1" 200 535 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:29:56 -0400] "PUT /upload HTTP/1.1" 200 528 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:29:56 -0400] "PUT /upload HTTP/1.1" 200 260 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:29:56 -0400] "PUT /upload HTTP/1.1" 200 534 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:29:57 -0400] "PUT /mirror HTTP/1.1" 200 535 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:16 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:17 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:17 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:17 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:17 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:18 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:18 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:18 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:19 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:19 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:19 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:19 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:20 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:20 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:20 -0400] "GET /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:20 -0400] "POST /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:20 -0400] "DELETE /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:20 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:20 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:20 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:21 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:14:58:21 -0400] "PUT /report HTTP/1.1" 501 38 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:01 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:02 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:02 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:02 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:02 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:03 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:03 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:03 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:04 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:04 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:04 -0400] "PUT /report HTTP/1.1" 400 162 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:04 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:05 -0400] "PUT /report HTTP/1.1" 400 162 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:05 -0400] "PUT /report HTTP/1.1" 400 125 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:05 -0400] "GET /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:05 -0400] "POST /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:05 -0400] "DELETE /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:05 -0400] "PUT /report HTTP/1.1" 400 152 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:05 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:05 -0400] "PUT /report HTTP/1.1" 415 150 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:06 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:06 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:45 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:46 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:46 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:46 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:46 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:47 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:47 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:47 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:48 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:48 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:48 -0400] "PUT /report HTTP/1.1" 400 162 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:48 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:49 -0400] "PUT /report HTTP/1.1" 400 162 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:49 -0400] "PUT /report HTTP/1.1" 400 125 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:49 -0400] "GET /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:49 -0400] "POST /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:49 -0400] "DELETE /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:49 -0400] "PUT /report HTTP/1.1" 400 152 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:49 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:49 -0400] "PUT /report HTTP/1.1" 415 150 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:50 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:10:50 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:44 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:44 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:44 -0400] "PUT /report HTTP/1.1" 200 92 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:45 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:45 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:45 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:45 -0400] "PUT /report HTTP/1.1" 200 92 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:46 -0400] "PUT /report HTTP/1.1" 200 92 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:46 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:46 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:46 -0400] "PUT /report HTTP/1.1" 400 162 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:47 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:47 -0400] "PUT /report HTTP/1.1" 400 162 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:47 -0400] "PUT /report HTTP/1.1" 400 125 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:47 -0400] "GET /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:47 -0400] "POST /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:47 -0400] "DELETE /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:47 -0400] "PUT /report HTTP/1.1" 400 152 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:47 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:48 -0400] "PUT /report HTTP/1.1" 415 150 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:48 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [03/Sep/2025:15:17:48 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:09:50:20 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:09:50:48 -0400] "PUT /upload HTTP/1.1" 500 41 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:09:51:51 -0400] "PUT /upload HTTP/1.1" 500 41 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:09:52:08 -0400] "PUT /upload HTTP/1.1" 500 41 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:09:55:49 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:09:56:34 -0400] "GET /api/health HTTP/1.1" 503 95 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:09:56:34 -0400] "GET /api/stats HTTP/1.1" 503 95 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:09:57:29 -0400] "GET /api/health HTTP/1.1" 200 264 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:09:57:56 -0400] "GET /api/health HTTP/1.1" 200 266 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:09:57:56 -0400] "GET /api/stats HTTP/1.1" 401 108 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:09:58:20 -0400] "GET /api/stats HTTP/1.1" 401 108 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:00:29 -0400] "GET /api/stats HTTP/1.1" 401 108 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:00:44 -0400] "GET /api/health HTTP/1.1" 200 266 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:01:20 -0400] "GET /api/stats HTTP/1.1" 200 233 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:01:57 -0400] "GET /api/config HTTP/1.1" 200 221 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:02:31 -0400] "GET /api/health HTTP/1.1" 200 266 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:02:31 -0400] "GET /api/stats HTTP/1.1" 401 108 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:09:14 -0400] "GET /api/health HTTP/1.1" 200 264 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:09:15 -0400] "GET /api/stats HTTP/1.1" 200 233 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:09:15 -0400] "GET /api/config HTTP/1.1" 200 221 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:09:15 -0400] "PUT /api/config HTTP/1.1" 200 143 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:09:16 -0400] "GET /api/config HTTP/1.1" 200 243 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:09:16 -0400] "GET /api/files?limit=10&offset=0 HTTP/1.1" 200 440 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:17:36 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:17:42 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:17:50 -0400] "GET /api/health HTTP/1.1" 200 266 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:17:51 -0400] "GET /api/stats HTTP/1.1" 200 233 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:17:51 -0400] "GET /api/config HTTP/1.1" 200 243 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:17:51 -0400] "PUT /api/config HTTP/1.1" 200 143 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:17:52 -0400] "GET /api/config HTTP/1.1" 200 243 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:17:52 -0400] "GET /api/files?limit=10&offset=0 HTTP/1.1" 200 1152 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [04/Sep/2025:10:24:48 -0400] "GET /api/health HTTP/1.1" 200 266 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:09:39:54 -0400] "HEAD / HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:09:40:01 -0400] "HEAD /upload HTTP/1.1" 400 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:09:40:52 -0400] "GET /list/79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 HTTP/1.1" 200 945 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:09:41:11 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:16:43 -0400] "HEAD / HTTP/1.1" 200 0 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:16:51 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:16:51 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:17:07 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:17:44 -0400] "PUT /upload HTTP/1.1" 200 510 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:18:53 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:18:53 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:19:32 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:19:33 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:19:50 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:19:51 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:21:29 -0400] "PUT /upload HTTP/1.1" 200 510 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:21:59 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:22:00 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:22:41 -0400] "PUT /upload HTTP/1.1" 200 510 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:22:42 -0400] "PUT /upload HTTP/1.1" 200 510 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:25:01 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:25:01 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:27:17 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:27:17 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:27:17 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:27:18 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:27:18 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:27:18 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:27:19 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:27:19 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:27:19 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:27:20 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:29:14 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:29:15 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:29:15 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:29:15 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:29:16 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:29:16 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:29:16 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:29:16 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:29:17 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:29:17 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:37:34 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:37:34 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:37:35 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:37:35 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:37:36 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:37:36 -0400] "PUT /upload HTTP/1.1" 200 510 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:42:53 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:42:53 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:42:54 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:42:54 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:42:55 -0400] "PUT /upload HTTP/1.1" 401 176 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:42:55 -0400] "PUT /upload HTTP/1.1" 200 510 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:47:53 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:48:51 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:48:52 -0400] "PUT /upload HTTP/1.1" 401 168 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:48:52 -0400] "PUT /upload HTTP/1.1" 401 168 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:48:53 -0400] "PUT /upload HTTP/1.1" 401 149 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:48:54 -0400] "PUT /upload HTTP/1.1" 401 168 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:48:54 -0400] "PUT /upload HTTP/1.1" 200 510 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:53:56 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:16 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:16 -0400] "PUT /upload HTTP/1.1" 401 168 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:17 -0400] "PUT /upload HTTP/1.1" 401 168 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:17 -0400] "PUT /upload HTTP/1.1" 401 149 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:18 -0400] "PUT /upload HTTP/1.1" 401 168 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:18 -0400] "PUT /upload HTTP/1.1" 200 510 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:18 -0400] "PUT /upload HTTP/1.1" 200 510 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:18 -0400] "PUT /upload HTTP/1.1" 401 141 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:19 -0400] "PUT /upload HTTP/1.1" 401 141 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:19 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:19 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:19 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:19 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:19 -0400] "PUT /upload HTTP/1.1" 401 144 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:19 -0400] "PUT /upload HTTP/1.1" 401 144 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:20 -0400] "PUT /upload HTTP/1.1" 401 144 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:20 -0400] "PUT /upload HTTP/1.1" 401 144 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:20 -0400] "PUT /upload HTTP/1.1" 401 144 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:54:21 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:31 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:32 -0400] "PUT /upload HTTP/1.1" 401 168 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:32 -0400] "PUT /upload HTTP/1.1" 401 168 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:33 -0400] "PUT /upload HTTP/1.1" 401 149 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:34 -0400] "PUT /upload HTTP/1.1" 401 168 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:34 -0400] "PUT /upload HTTP/1.1" 200 510 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:34 -0400] "PUT /upload HTTP/1.1" 200 510 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:34 -0400] "PUT /upload HTTP/1.1" 401 141 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:34 -0400] "PUT /upload HTTP/1.1" 401 141 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:34 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:35 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:35 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:35 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:35 -0400] "PUT /upload HTTP/1.1" 401 144 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:35 -0400] "PUT /upload HTTP/1.1" 401 144 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:36 -0400] "PUT /upload HTTP/1.1" 401 144 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:36 -0400] "PUT /upload HTTP/1.1" 401 144 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:36 -0400] "PUT /upload HTTP/1.1" 401 144 "-" "curl/8.15.0"
|
|
||||||
127.0.0.1 - - [07/Sep/2025:10:55:37 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
|
|
||||||
|
|||||||
120335
logs/error.log
120335
logs/error.log
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
|||||||
FastCGI starting at Sun Sep 7 10:47:32 AM EDT 2025
|
FastCGI starting at Mon Sep 8 09:29:55 AM EDT 2025
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1241997
|
1550305
|
||||||
|
|||||||
Submodule nostr_core_lib updated: 8585e7649c...564ff18a7e
@@ -9,12 +9,26 @@
|
|||||||
#include <sys/mount.h>
|
#include <sys/mount.h>
|
||||||
#endif
|
#endif
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include "admin_api.h"
|
|
||||||
#include "ginxsom.h"
|
#include "ginxsom.h"
|
||||||
|
#include "../nostr_core_lib/nostr_core/request_validator.h"
|
||||||
|
|
||||||
// Database path (consistent with main.c)
|
// Database path (consistent with main.c)
|
||||||
#define DB_PATH "db/ginxsom.db"
|
#define DB_PATH "db/ginxsom.db"
|
||||||
|
|
||||||
|
// Function declarations (moved from admin_api.h)
|
||||||
|
void handle_admin_api_request(const char* method, const char* uri);
|
||||||
|
void handle_stats_api(void);
|
||||||
|
void handle_config_get_api(void);
|
||||||
|
void handle_config_put_api(void);
|
||||||
|
void handle_files_api(void);
|
||||||
|
void handle_health_api(void);
|
||||||
|
int authenticate_admin_request(const char* auth_header);
|
||||||
|
int is_admin_enabled(void);
|
||||||
|
int verify_admin_pubkey(const char* event_pubkey);
|
||||||
|
void send_json_response(int status, const char* json_content);
|
||||||
|
void send_json_error(int status, const char* error, const char* message);
|
||||||
|
int parse_query_params(const char* query_string, char params[][256], int max_params);
|
||||||
|
|
||||||
// Forward declarations for local utility functions
|
// Forward declarations for local utility functions
|
||||||
static int admin_nip94_get_origin(char* out, size_t out_size);
|
static int admin_nip94_get_origin(char* out, size_t out_size);
|
||||||
static void admin_nip94_build_blob_url(const char* origin, const char* sha256, const char* mime_type, char* out, size_t out_size);
|
static void admin_nip94_build_blob_url(const char* origin, const char* sha256, const char* mime_type, char* out, size_t out_size);
|
||||||
@@ -154,35 +168,32 @@ int authenticate_admin_request(const char* auth_header) {
|
|||||||
return 0; // No auth header
|
return 0; // No auth header
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use existing authentication system with "admin" method
|
// Use unified request validation system for admin operations
|
||||||
int auth_result = authenticate_request(auth_header, "admin", NULL);
|
nostr_request_t request = {
|
||||||
if (auth_result != NOSTR_SUCCESS) {
|
.operation = "admin",
|
||||||
return 0; // Invalid Nostr event
|
.auth_header = auth_header,
|
||||||
|
.event = NULL,
|
||||||
|
.resource_hash = NULL,
|
||||||
|
.mime_type = NULL,
|
||||||
|
.file_size = 0,
|
||||||
|
.client_ip = getenv("REMOTE_ADDR"),
|
||||||
|
.app_context = NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
nostr_request_result_t result;
|
||||||
|
int auth_result = nostr_validate_request(&request, &result);
|
||||||
|
|
||||||
|
if (auth_result != NOSTR_SUCCESS || !result.valid) {
|
||||||
|
return 0; // Authentication failed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract pubkey from validated event using existing parser
|
// Extract pubkey from validation result and verify admin status
|
||||||
char event_json[4096];
|
const char* event_pubkey = result.pubkey[0] ? result.pubkey : NULL;
|
||||||
int parse_result = parse_authorization_header(auth_header, event_json, sizeof(event_json));
|
if (!event_pubkey) {
|
||||||
if (parse_result != NOSTR_SUCCESS) {
|
return 0; // No pubkey available
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cJSON* event = cJSON_Parse(event_json);
|
return verify_admin_pubkey(event_pubkey);
|
||||||
if (!event) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
cJSON* pubkey_json = cJSON_GetObjectItem(event, "pubkey");
|
|
||||||
if (!pubkey_json || !cJSON_IsString(pubkey_json)) {
|
|
||||||
cJSON_Delete(event);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* event_pubkey = cJSON_GetStringValue(pubkey_json);
|
|
||||||
int is_admin = verify_admin_pubkey(event_pubkey);
|
|
||||||
|
|
||||||
cJSON_Delete(event);
|
|
||||||
return is_admin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int verify_admin_pubkey(const char* event_pubkey) {
|
int verify_admin_pubkey(const char* event_pubkey) {
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
#ifndef ADMIN_API_H
|
|
||||||
#define ADMIN_API_H
|
|
||||||
|
|
||||||
#include "ginxsom.h"
|
|
||||||
|
|
||||||
// Main API request handler
|
|
||||||
void handle_admin_api_request(const char* method, const char* uri);
|
|
||||||
|
|
||||||
// Individual endpoint handlers
|
|
||||||
void handle_stats_api(void);
|
|
||||||
void handle_config_get_api(void);
|
|
||||||
void handle_config_put_api(void);
|
|
||||||
void handle_files_api(void);
|
|
||||||
void handle_health_api(void);
|
|
||||||
|
|
||||||
// Admin authentication functions
|
|
||||||
int authenticate_admin_request(const char* auth_header);
|
|
||||||
int is_admin_enabled(void);
|
|
||||||
int verify_admin_pubkey(const char* event_pubkey);
|
|
||||||
|
|
||||||
// Utility functions
|
|
||||||
void send_json_response(int status, const char* json_content);
|
|
||||||
void send_json_error(int status, const char* error, const char* message);
|
|
||||||
int parse_query_params(const char* query_string, char params[][256], int max_params);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
543
src/bud04.c
Normal file
543
src/bud04.c
Normal file
@@ -0,0 +1,543 @@
|
|||||||
|
/*
|
||||||
|
* BUD-04 Mirroring Support
|
||||||
|
* Handles PUT /mirror requests for remote blob downloading
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "ginxsom.h"
|
||||||
|
#include "../nostr_core_lib/nostr_core/request_validator.h"
|
||||||
|
|
||||||
|
// HTTP download response structure
|
||||||
|
typedef struct {
|
||||||
|
unsigned char* data;
|
||||||
|
size_t size;
|
||||||
|
char content_type[128];
|
||||||
|
long http_code;
|
||||||
|
size_t capacity;
|
||||||
|
} mirror_download_t;
|
||||||
|
|
||||||
|
// CURL write callback for collecting response data
|
||||||
|
static size_t write_callback(void* contents, size_t size, size_t nmemb, mirror_download_t* response) {
|
||||||
|
size_t realsize = size * nmemb;
|
||||||
|
|
||||||
|
if (!response) return 0;
|
||||||
|
|
||||||
|
// Check if we need to expand buffer
|
||||||
|
if (response->size + realsize >= response->capacity) {
|
||||||
|
size_t new_capacity = response->capacity == 0 ? 8192 : response->capacity * 2;
|
||||||
|
while (new_capacity < response->size + realsize + 1) {
|
||||||
|
new_capacity *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char* new_data = realloc(response->data, new_capacity);
|
||||||
|
if (!new_data) {
|
||||||
|
return 0; // Out of memory
|
||||||
|
}
|
||||||
|
|
||||||
|
response->data = new_data;
|
||||||
|
response->capacity = new_capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(response->data + response->size, contents, realsize);
|
||||||
|
response->size += realsize;
|
||||||
|
response->data[response->size] = '\0'; // Null terminate for safety
|
||||||
|
|
||||||
|
return realsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CURL header callback for collecting Content-Type
|
||||||
|
static size_t header_callback(char* buffer, size_t size, size_t nitems, mirror_download_t* response) {
|
||||||
|
size_t realsize = size * nitems;
|
||||||
|
|
||||||
|
if (!response) return realsize;
|
||||||
|
|
||||||
|
// Look for Content-Type header (case-insensitive)
|
||||||
|
if (realsize > 14 && strncasecmp(buffer, "Content-Type:", 13) == 0) {
|
||||||
|
// Skip "Content-Type:" and whitespace
|
||||||
|
char* value = buffer + 13;
|
||||||
|
while (*value == ' ' || *value == '\t') value++;
|
||||||
|
|
||||||
|
// Find end of value (before \r\n)
|
||||||
|
char* end = value;
|
||||||
|
while (*end && *end != '\r' && *end != '\n') end++;
|
||||||
|
|
||||||
|
// Copy content type, limiting to buffer size
|
||||||
|
size_t copy_len = end - value;
|
||||||
|
if (copy_len >= sizeof(response->content_type)) {
|
||||||
|
copy_len = sizeof(response->content_type) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(response->content_type, value, copy_len);
|
||||||
|
response->content_type[copy_len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return realsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate URL for security (prevent SSRF attacks)
|
||||||
|
int validate_mirror_url(const char* url) {
|
||||||
|
if (!url || strlen(url) == 0) {
|
||||||
|
return 0; // Invalid URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must start with https:// (security requirement)
|
||||||
|
if (strncmp(url, "https://", 8) != 0) {
|
||||||
|
return 0; // Only HTTPS allowed
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL length check
|
||||||
|
if (strlen(url) > 2048) {
|
||||||
|
return 0; // URL too long
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for prohibited hosts/IPs (basic SSRF protection)
|
||||||
|
const char* host_start = url + 8; // Skip "https://"
|
||||||
|
|
||||||
|
// Block localhost and private IPs
|
||||||
|
if (strncasecmp(host_start, "localhost", 9) == 0 ||
|
||||||
|
strncasecmp(host_start, "127.", 4) == 0 ||
|
||||||
|
strncasecmp(host_start, "192.168.", 8) == 0 ||
|
||||||
|
strncasecmp(host_start, "10.", 3) == 0 ||
|
||||||
|
strncmp(host_start, "172.16.", 7) == 0 ||
|
||||||
|
strncmp(host_start, "172.17.", 7) == 0 ||
|
||||||
|
strncmp(host_start, "172.18.", 7) == 0 ||
|
||||||
|
strncmp(host_start, "172.19.", 7) == 0 ||
|
||||||
|
strncmp(host_start, "172.2", 5) == 0 ||
|
||||||
|
strncmp(host_start, "172.30.", 7) == 0 ||
|
||||||
|
strncmp(host_start, "172.31.", 7) == 0) {
|
||||||
|
return 0; // Private network blocked
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1; // URL appears valid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect/validate Content-Type
|
||||||
|
const char* determine_blob_content_type(const char* url, const char* header_content_type,
|
||||||
|
const unsigned char* data, size_t size) {
|
||||||
|
// Priority 1: Use Content-Type header if present and valid
|
||||||
|
if (header_content_type && strlen(header_content_type) > 0) {
|
||||||
|
// Extract main MIME type (before semicolon)
|
||||||
|
static char clean_type[128];
|
||||||
|
const char* semicolon = strchr(header_content_type, ';');
|
||||||
|
size_t len = semicolon ? (size_t)(semicolon - header_content_type) : strlen(header_content_type);
|
||||||
|
|
||||||
|
if (len < sizeof(clean_type)) {
|
||||||
|
strncpy(clean_type, header_content_type, len);
|
||||||
|
clean_type[len] = '\0';
|
||||||
|
|
||||||
|
// Remove trailing whitespace
|
||||||
|
while (len > 0 && (clean_type[len-1] == ' ' || clean_type[len-1] == '\t')) {
|
||||||
|
clean_type[--len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return clean_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 2: Detect from URL extension
|
||||||
|
if (url) {
|
||||||
|
const char* dot = strrchr(url, '.');
|
||||||
|
if (dot && dot[1]) {
|
||||||
|
const char* ext = dot + 1;
|
||||||
|
|
||||||
|
// Remove query parameters
|
||||||
|
const char* question = strchr(ext, '?');
|
||||||
|
size_t ext_len = question ? (size_t)(question - ext) : strlen(ext);
|
||||||
|
|
||||||
|
if (ext_len > 0) {
|
||||||
|
if (strncasecmp(ext, "png", ext_len) == 0) return "image/png";
|
||||||
|
if (strncasecmp(ext, "jpg", ext_len) == 0) return "image/jpeg";
|
||||||
|
if (strncasecmp(ext, "jpeg", ext_len) == 0) return "image/jpeg";
|
||||||
|
if (strncasecmp(ext, "gif", ext_len) == 0) return "image/gif";
|
||||||
|
if (strncasecmp(ext, "webp", ext_len) == 0) return "image/webp";
|
||||||
|
if (strncasecmp(ext, "pdf", ext_len) == 0) return "application/pdf";
|
||||||
|
if (strncasecmp(ext, "mp4", ext_len) == 0) return "video/mp4";
|
||||||
|
if (strncasecmp(ext, "mp3", ext_len) == 0) return "audio/mpeg";
|
||||||
|
if (strncasecmp(ext, "txt", ext_len) == 0) return "text/plain";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 3: Basic content detection from data
|
||||||
|
if (data && size >= 8) {
|
||||||
|
// PNG signature
|
||||||
|
if (memcmp(data, "\x89PNG\r\n\x1a\n", 8) == 0) {
|
||||||
|
return "image/png";
|
||||||
|
}
|
||||||
|
// JPEG signature
|
||||||
|
if (size >= 3 && memcmp(data, "\xff\xd8\xff", 3) == 0) {
|
||||||
|
return "image/jpeg";
|
||||||
|
}
|
||||||
|
// GIF signature
|
||||||
|
if (memcmp(data, "GIF87a", 6) == 0 || memcmp(data, "GIF89a", 6) == 0) {
|
||||||
|
return "image/gif";
|
||||||
|
}
|
||||||
|
// PDF signature
|
||||||
|
if (memcmp(data, "%PDF-", 5) == 0) {
|
||||||
|
return "application/pdf";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default fallback
|
||||||
|
return "application/octet-stream";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download blob from remote URL
|
||||||
|
mirror_download_t* download_blob_from_url(const char* url, size_t max_size) {
|
||||||
|
if (!url || !validate_mirror_url(url)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
CURL* curl = curl_easy_init();
|
||||||
|
if (!curl) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
mirror_download_t* download = calloc(1, sizeof(mirror_download_t));
|
||||||
|
if (!download) {
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize download structure
|
||||||
|
download->data = malloc(8192);
|
||||||
|
if (!download->data) {
|
||||||
|
free(download);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
download->capacity = 8192;
|
||||||
|
download->size = 0;
|
||||||
|
download->content_type[0] = '\0';
|
||||||
|
|
||||||
|
// Configure CURL
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, download);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HEADERDATA, download);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Ginxsom-Blossom/1.0");
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
|
||||||
|
|
||||||
|
// Set maximum file size
|
||||||
|
curl_easy_setopt(curl, CURLOPT_MAXFILESIZE, (long)max_size);
|
||||||
|
|
||||||
|
// Perform the request
|
||||||
|
CURLcode res = curl_easy_perform(curl);
|
||||||
|
|
||||||
|
// Get HTTP response code
|
||||||
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &download->http_code);
|
||||||
|
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
|
// Check for errors
|
||||||
|
if (res != CURLE_OK || download->http_code != 200) {
|
||||||
|
free(download->data);
|
||||||
|
free(download);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return download;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free download result memory
|
||||||
|
void free_mirror_download(mirror_download_t* download) {
|
||||||
|
if (download) {
|
||||||
|
if (download->data) {
|
||||||
|
free(download->data);
|
||||||
|
}
|
||||||
|
free(download);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse JSON request body to extract URL
|
||||||
|
int parse_mirror_request_body(const char* json_body, char* url_buffer, size_t url_buffer_size) {
|
||||||
|
if (!json_body || !url_buffer || url_buffer_size == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* json = cJSON_Parse(json_body);
|
||||||
|
if (!json) {
|
||||||
|
return 0; // Invalid JSON
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* url_item = cJSON_GetObjectItem(json, "url");
|
||||||
|
if (!url_item || !cJSON_IsString(url_item)) {
|
||||||
|
cJSON_Delete(json);
|
||||||
|
return 0; // Missing or invalid URL field
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* url = cJSON_GetStringValue(url_item);
|
||||||
|
if (!url || strlen(url) >= url_buffer_size) {
|
||||||
|
cJSON_Delete(json);
|
||||||
|
return 0; // URL too long or null
|
||||||
|
}
|
||||||
|
|
||||||
|
strcpy(url_buffer, url);
|
||||||
|
cJSON_Delete(json);
|
||||||
|
|
||||||
|
return 1; // Success
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle PUT /mirror requests (BUD-04)
|
||||||
|
void handle_mirror_request(void) {
|
||||||
|
// Log the incoming request
|
||||||
|
log_request("PUT", "/mirror", "pending", 0);
|
||||||
|
|
||||||
|
// Get HTTP headers
|
||||||
|
const char* content_type = getenv("CONTENT_TYPE");
|
||||||
|
const char* content_length_str = getenv("CONTENT_LENGTH");
|
||||||
|
|
||||||
|
// Validate Content-Type
|
||||||
|
if (!content_type || strstr(content_type, "application/json") == NULL) {
|
||||||
|
send_error_response(400, "invalid_content_type",
|
||||||
|
"Content-Type must be application/json",
|
||||||
|
"The mirror endpoint requires JSON request body");
|
||||||
|
log_request("PUT", "/mirror", "none", 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Content-Length
|
||||||
|
if (!content_length_str) {
|
||||||
|
send_error_response(400, "missing_header",
|
||||||
|
"Content-Length header required",
|
||||||
|
"The Content-Length header must be specified");
|
||||||
|
log_request("PUT", "/mirror", "none", 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long content_length = atol(content_length_str);
|
||||||
|
if (content_length <= 0 || content_length > 4096) { // 4KB max for JSON
|
||||||
|
send_error_response(400, "invalid_content_length",
|
||||||
|
"Invalid content length",
|
||||||
|
"JSON request body must be between 1 byte and 4KB");
|
||||||
|
log_request("PUT", "/mirror", "none", 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read JSON request body
|
||||||
|
char* json_body = malloc(content_length + 1);
|
||||||
|
if (!json_body) {
|
||||||
|
send_error_response(500, "memory_error",
|
||||||
|
"Failed to allocate memory",
|
||||||
|
"Internal server error");
|
||||||
|
log_request("PUT", "/mirror", "none", 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t bytes_read = fread(json_body, 1, content_length, stdin);
|
||||||
|
if (bytes_read != (size_t)content_length) {
|
||||||
|
free(json_body);
|
||||||
|
send_error_response(400, "incomplete_body",
|
||||||
|
"Failed to read complete request body",
|
||||||
|
"The request body was incomplete");
|
||||||
|
log_request("PUT", "/mirror", "none", 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
json_body[content_length] = '\0';
|
||||||
|
|
||||||
|
// Parse JSON to extract URL
|
||||||
|
char url[2048];
|
||||||
|
if (!parse_mirror_request_body(json_body, url, sizeof(url))) {
|
||||||
|
free(json_body);
|
||||||
|
send_error_response(400, "invalid_json",
|
||||||
|
"Invalid JSON or missing URL field",
|
||||||
|
"Request body must be valid JSON with 'url' field");
|
||||||
|
log_request("PUT", "/mirror", "none", 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(json_body);
|
||||||
|
|
||||||
|
// Validate URL
|
||||||
|
if (!validate_mirror_url(url)) {
|
||||||
|
send_error_response(400, "invalid_url",
|
||||||
|
"Invalid or prohibited URL",
|
||||||
|
"URL must be HTTPS and not point to private networks");
|
||||||
|
log_request("PUT", "/mirror", "none", 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for authorization
|
||||||
|
const char* auth_header = getenv("HTTP_AUTHORIZATION");
|
||||||
|
const char* expected_hash = NULL;
|
||||||
|
const char* uploader_pubkey = NULL;
|
||||||
|
static char pubkey_buffer[256];
|
||||||
|
|
||||||
|
if (auth_header) {
|
||||||
|
// Use unified request validation system
|
||||||
|
nostr_request_t request = {
|
||||||
|
.operation = "upload",
|
||||||
|
.auth_header = auth_header,
|
||||||
|
.event = NULL,
|
||||||
|
.resource_hash = NULL,
|
||||||
|
.mime_type = NULL,
|
||||||
|
.file_size = 0,
|
||||||
|
.client_ip = getenv("REMOTE_ADDR"),
|
||||||
|
.app_context = NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
nostr_request_result_t result;
|
||||||
|
int auth_result = nostr_validate_request(&request, &result);
|
||||||
|
|
||||||
|
if (auth_result != NOSTR_SUCCESS || !result.valid) {
|
||||||
|
const char* error_type = "authentication_failed";
|
||||||
|
const char* message = "Invalid authentication";
|
||||||
|
const char* details = result.reason[0] ? result.reason : "The provided authorization is invalid";
|
||||||
|
|
||||||
|
// Provide more specific error messages based on the reason
|
||||||
|
if (strstr(result.reason, "whitelist")) {
|
||||||
|
error_type = "pubkey_not_whitelisted";
|
||||||
|
message = "Public key not authorized";
|
||||||
|
} else if (strstr(result.reason, "blacklist")) {
|
||||||
|
error_type = "access_denied";
|
||||||
|
message = "Access denied by policy";
|
||||||
|
}
|
||||||
|
|
||||||
|
send_error_response(401, error_type, message, details);
|
||||||
|
log_request("PUT", "/mirror", "auth_failed", 401);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract uploader pubkey from validation result
|
||||||
|
if (result.pubkey[0]) {
|
||||||
|
strncpy(pubkey_buffer, result.pubkey, sizeof(pubkey_buffer)-1);
|
||||||
|
pubkey_buffer[sizeof(pubkey_buffer)-1] = '\0';
|
||||||
|
uploader_pubkey = pubkey_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For mirror operations, we don't need to extract the expected hash here
|
||||||
|
// The unified validation system handles hash validation internally
|
||||||
|
// We just need the pubkey for metadata storage
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download the blob
|
||||||
|
mirror_download_t* download = download_blob_from_url(url, 100 * 1024 * 1024); // 100MB limit
|
||||||
|
if (!download) {
|
||||||
|
send_error_response(400, "download_failed",
|
||||||
|
"Failed to download blob from URL",
|
||||||
|
"Could not fetch the specified URL or file too large");
|
||||||
|
log_request("PUT", "/mirror", uploader_pubkey ? "authenticated" : "anonymous", 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate hash of downloaded content
|
||||||
|
unsigned char hash[32];
|
||||||
|
if (nostr_sha256(download->data, download->size, hash) != NOSTR_SUCCESS) {
|
||||||
|
free_mirror_download(download);
|
||||||
|
send_error_response(500, "hash_error",
|
||||||
|
"Failed to calculate hash",
|
||||||
|
"Internal server error during hash calculation");
|
||||||
|
log_request("PUT", "/mirror", uploader_pubkey ? "authenticated" : "anonymous", 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert hash to hex string
|
||||||
|
char sha256_hex[65];
|
||||||
|
nostr_bytes_to_hex(hash, 32, sha256_hex);
|
||||||
|
|
||||||
|
// If authorization provided, verify hash matches
|
||||||
|
if (expected_hash && strcmp(sha256_hex, expected_hash) != 0) {
|
||||||
|
free_mirror_download(download);
|
||||||
|
send_error_response(400, "hash_mismatch",
|
||||||
|
"Downloaded content hash does not match authorization",
|
||||||
|
"The file hash does not match the expected hash in the authorization event");
|
||||||
|
log_request("PUT", "/mirror", "auth_mismatch", 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine content type
|
||||||
|
const char* content_type_final = determine_blob_content_type(url, download->content_type,
|
||||||
|
download->data, download->size);
|
||||||
|
|
||||||
|
// Determine file extension from Content-Type using centralized mapping
|
||||||
|
const char* extension = mime_to_extension(content_type_final);
|
||||||
|
|
||||||
|
// Save file to blobs directory
|
||||||
|
char filepath[512];
|
||||||
|
snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256_hex, extension);
|
||||||
|
|
||||||
|
FILE* outfile = fopen(filepath, "wb");
|
||||||
|
if (!outfile) {
|
||||||
|
free_mirror_download(download);
|
||||||
|
send_error_response(500, "file_error",
|
||||||
|
"Failed to create file",
|
||||||
|
"Internal server error during file creation");
|
||||||
|
log_request("PUT", "/mirror", uploader_pubkey ? "authenticated" : "anonymous", 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t bytes_written = fwrite(download->data, 1, download->size, outfile);
|
||||||
|
fclose(outfile);
|
||||||
|
|
||||||
|
if (bytes_written != download->size) {
|
||||||
|
unlink(filepath); // Clean up partial file
|
||||||
|
free_mirror_download(download);
|
||||||
|
send_error_response(500, "write_error",
|
||||||
|
"Failed to write complete file",
|
||||||
|
"Internal server error during file write");
|
||||||
|
log_request("PUT", "/mirror", uploader_pubkey ? "authenticated" : "anonymous", 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set file permissions
|
||||||
|
chmod(filepath, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
||||||
|
|
||||||
|
// Store metadata in database
|
||||||
|
time_t uploaded_time = time(NULL);
|
||||||
|
if (!insert_blob_metadata(sha256_hex, download->size, content_type_final,
|
||||||
|
uploaded_time, uploader_pubkey, NULL)) {
|
||||||
|
unlink(filepath); // Clean up file
|
||||||
|
free_mirror_download(download);
|
||||||
|
send_error_response(500, "database_error",
|
||||||
|
"Failed to store blob metadata",
|
||||||
|
"Internal server error during database operation");
|
||||||
|
log_request("PUT", "/mirror", uploader_pubkey ? "authenticated" : "anonymous", 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get origin from config
|
||||||
|
char origin[256];
|
||||||
|
nip94_get_origin(origin, sizeof(origin));
|
||||||
|
|
||||||
|
// Build canonical blob URL
|
||||||
|
char blob_url[512];
|
||||||
|
nip94_build_blob_url(origin, sha256_hex, content_type_final, blob_url, sizeof(blob_url));
|
||||||
|
|
||||||
|
// Get dimensions for NIP-94 metadata
|
||||||
|
int width = 0, height = 0;
|
||||||
|
nip94_get_dimensions(download->data, download->size, content_type_final, &width, &height);
|
||||||
|
|
||||||
|
// Return success response with blob descriptor
|
||||||
|
printf("Status: 200 OK\r\n");
|
||||||
|
printf("Content-Type: application/json\r\n\r\n");
|
||||||
|
printf("{\n");
|
||||||
|
printf(" \"sha256\": \"%s\",\n", sha256_hex);
|
||||||
|
printf(" \"size\": %zu,\n", download->size);
|
||||||
|
printf(" \"type\": \"%s\",\n", content_type_final);
|
||||||
|
printf(" \"uploaded\": %ld,\n", uploaded_time);
|
||||||
|
printf(" \"url\": \"%s\"", blob_url);
|
||||||
|
|
||||||
|
// Add NIP-94 metadata if enabled
|
||||||
|
if (nip94_is_enabled()) {
|
||||||
|
printf(",\n");
|
||||||
|
nip94_emit_field(blob_url, content_type_final, sha256_hex, download->size, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n}\n");
|
||||||
|
|
||||||
|
free_mirror_download(download);
|
||||||
|
log_request("PUT", "/mirror", uploader_pubkey ? "authenticated" : "anonymous", 200);
|
||||||
|
}
|
||||||
265
src/bud06.c
Normal file
265
src/bud06.c
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
/*
|
||||||
|
* BUD-06 Upload Requirements (Pre-flight Validation)
|
||||||
|
* Handles HEAD /upload requests for upload requirement validation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <fcgi_stdio.h>
|
||||||
|
#include "ginxsom.h"
|
||||||
|
#include "../nostr_core_lib/nostr_core/request_validator.h"
|
||||||
|
|
||||||
|
// BUD-06 X-Reason header constants
|
||||||
|
#define XREASON_MISSING_SHA256 "Missing required X-SHA-256 header"
|
||||||
|
#define XREASON_INVALID_SHA256 "X-SHA-256 must be 64 hex characters"
|
||||||
|
#define XREASON_MISSING_LENGTH "Missing required X-Content-Length header"
|
||||||
|
#define XREASON_INVALID_LENGTH "X-Content-Length must be a positive integer"
|
||||||
|
#define XREASON_FILE_TOO_LARGE "File size exceeds maximum allowed (100MB)"
|
||||||
|
#define XREASON_ZERO_LENGTH "File size cannot be zero"
|
||||||
|
#define XREASON_BLOB_EXISTS "Blob with this hash already exists"
|
||||||
|
#define XREASON_UNSUPPORTED_TYPE "Content type not supported by server policy"
|
||||||
|
#define XREASON_AUTH_REQUIRED "Authorization required for upload"
|
||||||
|
#define XREASON_AUTH_INVALID "Invalid or expired authorization"
|
||||||
|
|
||||||
|
// Enhanced error response with X-Reason header for BUD-06
|
||||||
|
void send_upload_error_response(int status_code, const char* error_type,
|
||||||
|
const char* message, const char* x_reason) {
|
||||||
|
const char* status_text;
|
||||||
|
switch (status_code) {
|
||||||
|
case 400: status_text = "Bad Request"; break;
|
||||||
|
case 401: status_text = "Unauthorized"; break;
|
||||||
|
case 409: status_text = "Conflict"; break;
|
||||||
|
case 411: status_text = "Length Required"; break;
|
||||||
|
case 413: status_text = "Content Too Large"; break;
|
||||||
|
case 415: status_text = "Unsupported Media Type"; break;
|
||||||
|
case 500: status_text = "Internal Server Error"; break;
|
||||||
|
default: status_text = "Error"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Status: %d %s\r\n", status_code, status_text);
|
||||||
|
printf("Content-Type: application/json\r\n");
|
||||||
|
if (x_reason) {
|
||||||
|
printf("X-Reason: %s\r\n", x_reason);
|
||||||
|
}
|
||||||
|
printf("\r\n");
|
||||||
|
printf("{\n");
|
||||||
|
printf(" \"error\": \"%s\",\n", error_type);
|
||||||
|
printf(" \"message\": \"%s\"", message);
|
||||||
|
if (x_reason) {
|
||||||
|
printf(",\n \"x_reason\": \"%s\"", x_reason);
|
||||||
|
}
|
||||||
|
printf("\n}\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success response for validated upload requirements
|
||||||
|
void send_upload_success_response(const char* sha256, const char* content_type, long content_length) {
|
||||||
|
printf("Status: 200 OK\r\n");
|
||||||
|
printf("Content-Type: application/json\r\n");
|
||||||
|
printf("X-Upload-Status: Ready\r\n");
|
||||||
|
printf("\r\n");
|
||||||
|
printf("{\n");
|
||||||
|
printf(" \"message\": \"Upload requirements validated\",\n");
|
||||||
|
printf(" \"sha256\": \"%s\",\n", sha256);
|
||||||
|
printf(" \"content_type\": \"%s\",\n", content_type);
|
||||||
|
printf(" \"content_length\": %ld\n", content_length);
|
||||||
|
printf("}\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate SHA-256 format (64 hex characters)
|
||||||
|
int validate_sha256_format(const char* sha256) {
|
||||||
|
if (!sha256 || strlen(sha256) != 64) {
|
||||||
|
return 0; // Invalid format
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that all characters are hex
|
||||||
|
for (int i = 0; i < 64; i++) {
|
||||||
|
char c = sha256[i];
|
||||||
|
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
|
||||||
|
return 0; // Invalid hex character
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1; // Valid format
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse and validate X-Content-Length header
|
||||||
|
int validate_content_length(const char* content_length_str, long* parsed_length) {
|
||||||
|
if (!content_length_str || !parsed_length) {
|
||||||
|
return 0; // Invalid input
|
||||||
|
}
|
||||||
|
|
||||||
|
char* endptr;
|
||||||
|
long length = strtol(content_length_str, &endptr, 10);
|
||||||
|
|
||||||
|
// Always set parsed_length so caller can check the actual value
|
||||||
|
*parsed_length = length;
|
||||||
|
|
||||||
|
// Check if conversion was successful and no trailing characters
|
||||||
|
if (*endptr != '\0') {
|
||||||
|
return 0; // Invalid number format
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for valid size range
|
||||||
|
if (length <= 0) {
|
||||||
|
return 0; // Zero or negative size not allowed
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length > 100 * 1024 * 1024) { // 100MB limit
|
||||||
|
return -1; // File too large
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1; // Valid length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if blob already exists in database
|
||||||
|
int check_blob_exists(const char* sha256) {
|
||||||
|
if (!sha256) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blob_metadata_t metadata = {0};
|
||||||
|
return get_blob_metadata(sha256, &metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate upload headers and extract values
|
||||||
|
int validate_upload_headers(const char** sha256, const char** content_type,
|
||||||
|
long* content_length, char* error_reason, size_t reason_size) {
|
||||||
|
// Get X-SHA-256 header
|
||||||
|
const char* sha256_header = getenv("HTTP_X_SHA_256");
|
||||||
|
if (!sha256_header) {
|
||||||
|
strncpy(error_reason, XREASON_MISSING_SHA256, reason_size - 1);
|
||||||
|
error_reason[reason_size - 1] = '\0';
|
||||||
|
return 400; // Bad Request
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate SHA-256 format
|
||||||
|
if (!validate_sha256_format(sha256_header)) {
|
||||||
|
strncpy(error_reason, XREASON_INVALID_SHA256, reason_size - 1);
|
||||||
|
error_reason[reason_size - 1] = '\0';
|
||||||
|
return 400; // Bad Request
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get X-Content-Length header
|
||||||
|
const char* length_header = getenv("HTTP_X_CONTENT_LENGTH");
|
||||||
|
if (!length_header) {
|
||||||
|
strncpy(error_reason, XREASON_MISSING_LENGTH, reason_size - 1);
|
||||||
|
error_reason[reason_size - 1] = '\0';
|
||||||
|
return 411; // Length Required
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate content length
|
||||||
|
long parsed_length;
|
||||||
|
int length_result = validate_content_length(length_header, &parsed_length);
|
||||||
|
if (length_result == 0) {
|
||||||
|
if (parsed_length == 0) {
|
||||||
|
strncpy(error_reason, XREASON_ZERO_LENGTH, reason_size - 1);
|
||||||
|
} else {
|
||||||
|
strncpy(error_reason, XREASON_INVALID_LENGTH, reason_size - 1);
|
||||||
|
}
|
||||||
|
error_reason[reason_size - 1] = '\0';
|
||||||
|
return 400; // Bad Request
|
||||||
|
} else if (length_result == -1) {
|
||||||
|
strncpy(error_reason, XREASON_FILE_TOO_LARGE, reason_size - 1);
|
||||||
|
error_reason[reason_size - 1] = '\0';
|
||||||
|
return 413; // Content Too Large
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get X-Content-Type header (optional)
|
||||||
|
const char* type_header = getenv("HTTP_X_CONTENT_TYPE");
|
||||||
|
|
||||||
|
// Set output values
|
||||||
|
*sha256 = sha256_header;
|
||||||
|
*content_type = type_header ? type_header : "application/octet-stream";
|
||||||
|
*content_length = parsed_length;
|
||||||
|
|
||||||
|
return 200; // Success
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main BUD-06 handler function
|
||||||
|
void handle_head_upload_request(void) {
|
||||||
|
// Log the incoming request
|
||||||
|
log_request("HEAD", "/upload", "pending", 0);
|
||||||
|
|
||||||
|
// Validate upload headers
|
||||||
|
const char* sha256 = NULL;
|
||||||
|
const char* content_type = NULL;
|
||||||
|
long content_length = 0;
|
||||||
|
char error_reason[256];
|
||||||
|
|
||||||
|
int validation_result = validate_upload_headers(&sha256, &content_type,
|
||||||
|
&content_length, error_reason, sizeof(error_reason));
|
||||||
|
|
||||||
|
if (validation_result != 200) {
|
||||||
|
// Header validation failed
|
||||||
|
const char* error_type;
|
||||||
|
switch (validation_result) {
|
||||||
|
case 400: error_type = "invalid_headers"; break;
|
||||||
|
case 411: error_type = "length_required"; break;
|
||||||
|
case 413: error_type = "payload_too_large"; break;
|
||||||
|
default: error_type = "validation_error"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
send_upload_error_response(validation_result, error_type, error_reason, error_reason);
|
||||||
|
log_request("HEAD", "/upload", "none", validation_result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if blob already exists (duplicate detection)
|
||||||
|
if (check_blob_exists(sha256)) {
|
||||||
|
send_upload_error_response(409, "blob_exists", "Blob with this hash already exists", XREASON_BLOB_EXISTS);
|
||||||
|
log_request("HEAD", "/upload", "none", 409);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for optional authorization
|
||||||
|
const char* auth_header = getenv("HTTP_AUTHORIZATION");
|
||||||
|
const char* auth_status = "none";
|
||||||
|
|
||||||
|
if (auth_header) {
|
||||||
|
// Validate authorization if provided
|
||||||
|
nostr_request_t request = {
|
||||||
|
.operation = "upload",
|
||||||
|
.auth_header = auth_header,
|
||||||
|
.event = NULL,
|
||||||
|
.resource_hash = sha256,
|
||||||
|
.mime_type = content_type,
|
||||||
|
.file_size = content_length,
|
||||||
|
.client_ip = getenv("REMOTE_ADDR"),
|
||||||
|
.app_context = NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
nostr_request_result_t result;
|
||||||
|
int auth_result = nostr_validate_request(&request, &result);
|
||||||
|
|
||||||
|
if (auth_result != NOSTR_SUCCESS || !result.valid) {
|
||||||
|
const char* error_type = "authentication_failed";
|
||||||
|
const char* message = "Invalid or expired authentication";
|
||||||
|
const char* details = result.reason[0] ? result.reason : "Authentication validation failed";
|
||||||
|
|
||||||
|
// Provide more specific error messages based on the reason
|
||||||
|
if (strstr(result.reason, "whitelist")) {
|
||||||
|
error_type = "pubkey_not_whitelisted";
|
||||||
|
message = "Public key not authorized";
|
||||||
|
details = result.reason;
|
||||||
|
} else if (strstr(result.reason, "blacklist")) {
|
||||||
|
error_type = "access_denied";
|
||||||
|
message = "Access denied by policy";
|
||||||
|
details = result.reason;
|
||||||
|
} else if (strstr(result.reason, "size")) {
|
||||||
|
error_type = "file_too_large";
|
||||||
|
message = "File size exceeds policy limits";
|
||||||
|
details = result.reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
send_upload_error_response(401, error_type, message, details);
|
||||||
|
log_request("HEAD", "/upload", "auth_failed", 401);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auth_status = "authenticated";
|
||||||
|
}
|
||||||
|
|
||||||
|
// All validations passed - return success
|
||||||
|
send_upload_success_response(sha256, content_type, content_length);
|
||||||
|
log_request("HEAD", "/upload", auth_status, 200);
|
||||||
|
}
|
||||||
280
src/bud08.c
Normal file
280
src/bud08.c
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
/*
|
||||||
|
* BUD-08 NIP-94 File Metadata Tags Implementation
|
||||||
|
* Handles NIP-94 metadata generation for blob descriptors
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "ginxsom.h"
|
||||||
|
|
||||||
|
// Database path
|
||||||
|
#define DB_PATH "db/ginxsom.db"
|
||||||
|
|
||||||
|
// Check if NIP-94 metadata emission is enabled
|
||||||
|
int nip94_is_enabled(void) {
|
||||||
|
sqlite3* db;
|
||||||
|
sqlite3_stmt* stmt;
|
||||||
|
int rc, enabled = 1; // Default enabled
|
||||||
|
|
||||||
|
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
|
||||||
|
if (rc) {
|
||||||
|
return 1; // Default enabled on DB error
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* sql = "SELECT value FROM server_config WHERE key = 'nip94_enabled'";
|
||||||
|
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||||
|
if (rc == SQLITE_OK) {
|
||||||
|
rc = sqlite3_step(stmt);
|
||||||
|
if (rc == SQLITE_ROW) {
|
||||||
|
const char* value = (const char*)sqlite3_column_text(stmt, 0);
|
||||||
|
enabled = (value && strcmp(value, "true") == 0) ? 1 : 0;
|
||||||
|
}
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
}
|
||||||
|
sqlite3_close(db);
|
||||||
|
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get CDN origin for blob URLs
|
||||||
|
int nip94_get_origin(char* out, size_t out_size) {
|
||||||
|
if (!out || out_size == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3* db;
|
||||||
|
sqlite3_stmt* stmt;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
|
||||||
|
if (rc) {
|
||||||
|
// Default on DB error
|
||||||
|
strncpy(out, "http://localhost:9001", out_size - 1);
|
||||||
|
out[out_size - 1] = '\0';
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* sql = "SELECT value FROM server_config WHERE key = 'cdn_origin'";
|
||||||
|
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||||
|
if (rc == SQLITE_OK) {
|
||||||
|
rc = sqlite3_step(stmt);
|
||||||
|
if (rc == SQLITE_ROW) {
|
||||||
|
const char* value = (const char*)sqlite3_column_text(stmt, 0);
|
||||||
|
if (value) {
|
||||||
|
strncpy(out, value, out_size - 1);
|
||||||
|
out[out_size - 1] = '\0';
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
sqlite3_close(db);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_close(db);
|
||||||
|
|
||||||
|
// Default fallback
|
||||||
|
strncpy(out, "http://localhost:9001", out_size - 1);
|
||||||
|
out[out_size - 1] = '\0';
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Centralized MIME type to file extension mapping
|
||||||
|
const char* mime_to_extension(const char* mime_type) {
|
||||||
|
if (!mime_type) {
|
||||||
|
return ".bin";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr(mime_type, "image/jpeg")) {
|
||||||
|
return ".jpg";
|
||||||
|
} else if (strstr(mime_type, "image/webp")) {
|
||||||
|
return ".webp";
|
||||||
|
} else if (strstr(mime_type, "image/png")) {
|
||||||
|
return ".png";
|
||||||
|
} else if (strstr(mime_type, "image/gif")) {
|
||||||
|
return ".gif";
|
||||||
|
} else if (strstr(mime_type, "video/mp4")) {
|
||||||
|
return ".mp4";
|
||||||
|
} else if (strstr(mime_type, "video/webm")) {
|
||||||
|
return ".webm";
|
||||||
|
} else if (strstr(mime_type, "audio/mpeg")) {
|
||||||
|
return ".mp3";
|
||||||
|
} else if (strstr(mime_type, "audio/ogg")) {
|
||||||
|
return ".ogg";
|
||||||
|
} else if (strstr(mime_type, "text/plain")) {
|
||||||
|
return ".txt";
|
||||||
|
} else if (strstr(mime_type, "application/pdf")) {
|
||||||
|
return ".pdf";
|
||||||
|
} else {
|
||||||
|
return ".bin";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build canonical blob URL from origin + sha256 + extension
|
||||||
|
void nip94_build_blob_url(const char* origin, const char* sha256, const char* mime_type, char* out, size_t out_size) {
|
||||||
|
if (!origin || !sha256 || !out || out_size == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* extension = mime_to_extension(mime_type);
|
||||||
|
snprintf(out, out_size, "%s/%s%s", origin, sha256, extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse PNG dimensions from IHDR chunk
|
||||||
|
int parse_png_dimensions(const unsigned char* data, size_t size, int* width, int* height) {
|
||||||
|
if (!data || size < 24 || !width || !height) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify PNG signature
|
||||||
|
if (memcmp(data, "\x89PNG\r\n\x1a\n", 8) != 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IHDR chunk should start at offset 8
|
||||||
|
// Skip chunk length (4 bytes) and chunk type "IHDR" (4 bytes)
|
||||||
|
// Width is at offset 16 (4 bytes, big-endian)
|
||||||
|
// Height is at offset 20 (4 bytes, big-endian)
|
||||||
|
if (size >= 24) {
|
||||||
|
*width = (data[16] << 24) | (data[17] << 16) | (data[18] << 8) | data[19];
|
||||||
|
*height = (data[20] << 24) | (data[21] << 16) | (data[22] << 8) | data[23];
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse JPEG dimensions from SOF0 or SOF2 markers
|
||||||
|
int parse_jpeg_dimensions(const unsigned char* data, size_t size, int* width, int* height) {
|
||||||
|
if (!data || size < 10 || !width || !height) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify JPEG signature
|
||||||
|
if (size < 3 || memcmp(data, "\xff\xd8\xff", 3) != 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pos = 2;
|
||||||
|
while (pos < size - 1) {
|
||||||
|
// Look for marker
|
||||||
|
if (data[pos] != 0xff) {
|
||||||
|
pos++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char marker = data[pos + 1];
|
||||||
|
pos += 2;
|
||||||
|
|
||||||
|
// SOF0 (0xc0) or SOF2 (0xc2)
|
||||||
|
if (marker == 0xc0 || marker == 0xc2) {
|
||||||
|
// Skip length (2 bytes) and precision (1 byte)
|
||||||
|
if (pos + 5 < size) {
|
||||||
|
pos += 3;
|
||||||
|
// Height (2 bytes, big-endian)
|
||||||
|
*height = (data[pos] << 8) | data[pos + 1];
|
||||||
|
pos += 2;
|
||||||
|
// Width (2 bytes, big-endian)
|
||||||
|
*width = (data[pos] << 8) | data[pos + 1];
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} else if ((marker >= 0xe0 && marker <= 0xef) ||
|
||||||
|
(marker >= 0xc4 && marker <= 0xdf && marker != 0xc8)) {
|
||||||
|
// Skip over other segments
|
||||||
|
if (pos + 1 < size) {
|
||||||
|
size_t seg_len = (data[pos] << 8) | data[pos + 1];
|
||||||
|
pos += seg_len;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse WebP dimensions from VP8/VP8L/VP8X chunks
|
||||||
|
int parse_webp_dimensions(const unsigned char* data, size_t size, int* width, int* height) {
|
||||||
|
if (!data || size < 20 || !width || !height) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify RIFF/WEBP header
|
||||||
|
if (memcmp(data, "RIFF", 4) != 0 || memcmp(data + 8, "WEBP", 4) != 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check chunk type at offset 12
|
||||||
|
if (memcmp(data + 12, "VP8 ", 4) == 0) {
|
||||||
|
// VP8 lossy format
|
||||||
|
if (size >= 30) {
|
||||||
|
// Skip to frame header (offset 26)
|
||||||
|
*width = ((data[28] | (data[29] << 8)) & 0x3fff);
|
||||||
|
*height = ((data[26] | (data[27] << 8)) & 0x3fff);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else if (memcmp(data + 12, "VP8L", 4) == 0) {
|
||||||
|
// VP8L lossless format
|
||||||
|
if (size >= 25) {
|
||||||
|
// Width and height are in bits 0-13 and 14-27 of a 32-bit value at offset 21
|
||||||
|
uint32_t dim_data = data[21] | (data[22] << 8) | (data[23] << 16) | (data[24] << 24);
|
||||||
|
*width = (dim_data & 0x3fff) + 1;
|
||||||
|
*height = ((dim_data >> 14) & 0x3fff) + 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else if (memcmp(data + 12, "VP8X", 4) == 0) {
|
||||||
|
// VP8X extended format
|
||||||
|
if (size >= 30) {
|
||||||
|
// Width (3 bytes, little-endian) at offset 24
|
||||||
|
// Height (3 bytes, little-endian) at offset 27
|
||||||
|
*width = (data[24] | (data[25] << 8) | (data[26] << 16)) + 1;
|
||||||
|
*height = (data[27] | (data[28] << 8) | (data[29] << 16)) + 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get file dimensions based on MIME type
|
||||||
|
int nip94_get_dimensions(const unsigned char* data, size_t size, const char* mime_type, int* width, int* height) {
|
||||||
|
if (!data || !mime_type || !width || !height) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr(mime_type, "image/png")) {
|
||||||
|
return parse_png_dimensions(data, size, width, height);
|
||||||
|
} else if (strstr(mime_type, "image/jpeg")) {
|
||||||
|
return parse_jpeg_dimensions(data, size, width, height);
|
||||||
|
} else if (strstr(mime_type, "image/webp")) {
|
||||||
|
return parse_webp_dimensions(data, size, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit NIP-94 metadata field to stdout
|
||||||
|
void nip94_emit_field(const char* url, const char* mime, const char* sha256, long size, int width, int height) {
|
||||||
|
if (!url || !mime || !sha256) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(" \"nip94\": [\n");
|
||||||
|
printf(" [\"url\", \"%s\"],\n", url);
|
||||||
|
printf(" [\"m\", \"%s\"],\n", mime);
|
||||||
|
printf(" [\"x\", \"%s\"],\n", sha256);
|
||||||
|
printf(" [\"size\", \"%ld\"]", size);
|
||||||
|
|
||||||
|
// Add dim tag if dimensions are available
|
||||||
|
if (width > 0 && height > 0) {
|
||||||
|
printf(",\n [\"dim\", \"%dx%d\"]", width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n ]");
|
||||||
|
}
|
||||||
329
src/bud09.c
Normal file
329
src/bud09.c
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
/*
|
||||||
|
* BUD-09 - Blob Report (NIP-56 Report Events)
|
||||||
|
* Handles reporting of inappropriate or harmful blob content
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <fcgi_stdio.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include "ginxsom.h"
|
||||||
|
#include "../nostr_core_lib/nostr_core/request_validator.h"
|
||||||
|
|
||||||
|
// Database path (should match main.c)
|
||||||
|
#define DB_PATH "db/ginxsom.db"
|
||||||
|
|
||||||
|
// Forward declarations for helper functions
|
||||||
|
void send_error_response(int status_code, const char* error_type, const char* message, const char* details);
|
||||||
|
void log_request(const char* method, const char* uri, const char* auth_status, int status_code);
|
||||||
|
int validate_sha256_format(const char* sha256);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// BUD 09 - Blob Report (NIP-56 Report Events)
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Validate NIP-56 report event structure
|
||||||
|
int validate_report_event_structure(cJSON* event) {
|
||||||
|
if (!event) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be kind 1984
|
||||||
|
cJSON* kind_json = cJSON_GetObjectItem(event, "kind");
|
||||||
|
if (!kind_json || !cJSON_IsNumber(kind_json)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (cJSON_GetNumberValue(kind_json) != 1984) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must have tags array
|
||||||
|
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||||
|
if (!tags || !cJSON_IsArray(tags)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must have at least one 'x' tag
|
||||||
|
int has_x_tag = 0;
|
||||||
|
cJSON* tag = NULL;
|
||||||
|
cJSON_ArrayForEach(tag, tags) {
|
||||||
|
if (!cJSON_IsArray(tag)) continue;
|
||||||
|
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
|
||||||
|
if (tag_name && cJSON_IsString(tag_name)) {
|
||||||
|
const char* tag_name_str = cJSON_GetStringValue(tag_name);
|
||||||
|
if (tag_name_str && strcmp(tag_name_str, "x") == 0) {
|
||||||
|
has_x_tag = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return has_x_tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract SHA-256 blob hashes from 'x' tags
|
||||||
|
int extract_blob_hashes_from_report(cJSON* event, char blob_hashes[][65], int max_hashes) {
|
||||||
|
if (!event || !blob_hashes) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||||
|
if (!tags || !cJSON_IsArray(tags)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int hash_count = 0;
|
||||||
|
cJSON* tag = NULL;
|
||||||
|
cJSON_ArrayForEach(tag, tags) {
|
||||||
|
if (hash_count >= max_hashes) break;
|
||||||
|
if (!cJSON_IsArray(tag)) continue;
|
||||||
|
|
||||||
|
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
|
||||||
|
if (!tag_name || !cJSON_IsString(tag_name)) continue;
|
||||||
|
|
||||||
|
const char* tag_name_str = cJSON_GetStringValue(tag_name);
|
||||||
|
if (tag_name_str && strcmp(tag_name_str, "x") == 0) {
|
||||||
|
cJSON* hash_value = cJSON_GetArrayItem(tag, 1);
|
||||||
|
if (hash_value && cJSON_IsString(hash_value)) {
|
||||||
|
const char* hash = cJSON_GetStringValue(hash_value);
|
||||||
|
if (hash && validate_sha256_format(hash)) {
|
||||||
|
strncpy(blob_hashes[hash_count], hash, 64);
|
||||||
|
blob_hashes[hash_count][64] = '\0';
|
||||||
|
hash_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate NIP-56 report types in x tags
|
||||||
|
int validate_report_types(cJSON* event) {
|
||||||
|
if (!event) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||||
|
if (!tags || !cJSON_IsArray(tags)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid NIP-56 report types
|
||||||
|
const char* valid_types[] = {
|
||||||
|
"nudity", "malware", "profanity", "illegal",
|
||||||
|
"spam", "impersonation", "other", NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
cJSON* tag = NULL;
|
||||||
|
cJSON_ArrayForEach(tag, tags) {
|
||||||
|
if (!cJSON_IsArray(tag)) continue;
|
||||||
|
|
||||||
|
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
|
||||||
|
if (!tag_name || !cJSON_IsString(tag_name)) continue;
|
||||||
|
|
||||||
|
const char* tag_name_str = cJSON_GetStringValue(tag_name);
|
||||||
|
if (tag_name_str && strcmp(tag_name_str, "x") == 0) {
|
||||||
|
// Check if report type is provided and valid (optional)
|
||||||
|
cJSON* report_type = cJSON_GetArrayItem(tag, 2);
|
||||||
|
if (report_type && cJSON_IsString(report_type)) {
|
||||||
|
const char* type_str = cJSON_GetStringValue(report_type);
|
||||||
|
if (type_str) {
|
||||||
|
// Validate against known types (but allow unknown types per spec)
|
||||||
|
for (int i = 0; valid_types[i] != NULL; i++) {
|
||||||
|
if (strcmp(type_str, valid_types[i]) == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Note: Allow unknown types as per NIP-56 spec flexibility
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1; // Always return success - report types are informational
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store blob report in database (optional server behavior)
|
||||||
|
int store_blob_report(const char* event_json, const char* reporter_pubkey) {
|
||||||
|
// Optional implementation - servers MAY store reports
|
||||||
|
sqlite3* db;
|
||||||
|
sqlite3_stmt* stmt;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READWRITE, NULL);
|
||||||
|
if (rc) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if blob_reports table exists, create if not
|
||||||
|
const char* create_table_sql =
|
||||||
|
"CREATE TABLE IF NOT EXISTS blob_reports ("
|
||||||
|
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||||
|
"report_event TEXT NOT NULL, "
|
||||||
|
"reporter_pubkey TEXT, "
|
||||||
|
"reported_at INTEGER NOT NULL, "
|
||||||
|
"created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
||||||
|
")";
|
||||||
|
|
||||||
|
rc = sqlite3_exec(db, create_table_sql, NULL, NULL, NULL);
|
||||||
|
if (rc != SQLITE_OK) {
|
||||||
|
sqlite3_close(db);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* sql = "INSERT INTO blob_reports (report_event, reporter_pubkey, reported_at) VALUES (?, ?, strftime('%s', 'now'))";
|
||||||
|
|
||||||
|
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||||
|
if (rc != SQLITE_OK) {
|
||||||
|
sqlite3_close(db);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_bind_text(stmt, 1, event_json, -1, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_text(stmt, 2, reporter_pubkey, -1, SQLITE_STATIC);
|
||||||
|
|
||||||
|
rc = sqlite3_step(stmt);
|
||||||
|
int success = (rc == SQLITE_DONE);
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
sqlite3_close(db);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle PUT /report requests (BUD-09)
|
||||||
|
void handle_report_request(void) {
|
||||||
|
// Log the incoming request
|
||||||
|
log_request("PUT", "/report", "pending", 0);
|
||||||
|
|
||||||
|
// Validate HTTP method (only PUT allowed)
|
||||||
|
const char* request_method = getenv("REQUEST_METHOD");
|
||||||
|
if (!request_method || strcmp(request_method, "PUT") != 0) {
|
||||||
|
send_error_response(405, "method_not_allowed", "Only PUT method allowed", "The /report endpoint only accepts PUT requests");
|
||||||
|
log_request(request_method ? request_method : "NULL", "/report", "none", 405);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Content-Type
|
||||||
|
const char* content_type = getenv("CONTENT_TYPE");
|
||||||
|
if (!content_type || strstr(content_type, "application/json") == NULL) {
|
||||||
|
send_error_response(415, "unsupported_media_type", "Content-Type must be application/json", "Report requests must be JSON");
|
||||||
|
log_request("PUT", "/report", "none", 415);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Content-Length
|
||||||
|
const char* content_length_str = getenv("CONTENT_LENGTH");
|
||||||
|
if (!content_length_str) {
|
||||||
|
send_error_response(400, "missing_content_length", "Content-Length header required", "Request body size must be specified");
|
||||||
|
log_request("PUT", "/report", "none", 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long content_length = atol(content_length_str);
|
||||||
|
if (content_length <= 0 || content_length > 10240) { // 10KB limit for report events
|
||||||
|
send_error_response(400, "invalid_content_length", "Invalid content length", "Report events must be between 1 byte and 10KB");
|
||||||
|
log_request("PUT", "/report", "none", 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read JSON request body
|
||||||
|
char* json_body = malloc(content_length + 1);
|
||||||
|
if (!json_body) {
|
||||||
|
send_error_response(500, "memory_error", "Failed to allocate memory", "Internal server error");
|
||||||
|
log_request("PUT", "/report", "none", 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t bytes_read = fread(json_body, 1, content_length, stdin);
|
||||||
|
if (bytes_read != (size_t)content_length) {
|
||||||
|
free(json_body);
|
||||||
|
send_error_response(400, "incomplete_body", "Failed to read complete request body", "The request body was incomplete");
|
||||||
|
log_request("PUT", "/report", "none", 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
json_body[content_length] = '\0';
|
||||||
|
|
||||||
|
// Parse JSON event
|
||||||
|
cJSON* event = cJSON_Parse(json_body);
|
||||||
|
if (!event) {
|
||||||
|
free(json_body);
|
||||||
|
send_error_response(400, "invalid_json", "Invalid JSON format", "Request body must be valid JSON");
|
||||||
|
log_request("PUT", "/report", "none", 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate event structure (NIP-56 kind 1984 with x tags)
|
||||||
|
if (!validate_report_event_structure(event)) {
|
||||||
|
cJSON_Delete(event);
|
||||||
|
free(json_body);
|
||||||
|
send_error_response(400, "invalid_report_event", "Invalid report event structure", "Report must be NIP-56 kind 1984 event with x tags");
|
||||||
|
log_request("PUT", "/report", "none", 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate nostr event signature and structure
|
||||||
|
int structure_result = nostr_validate_event_structure(event);
|
||||||
|
if (structure_result != NOSTR_SUCCESS) {
|
||||||
|
cJSON_Delete(event);
|
||||||
|
free(json_body);
|
||||||
|
send_error_response(400, "invalid_event_structure", "Invalid nostr event structure", "Event does not conform to nostr event format");
|
||||||
|
log_request("PUT", "/report", "structure_invalid", 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_result = nostr_verify_event_signature(event);
|
||||||
|
if (crypto_result != NOSTR_SUCCESS) {
|
||||||
|
cJSON_Delete(event);
|
||||||
|
free(json_body);
|
||||||
|
send_error_response(400, "invalid_signature", "Invalid event signature", "Event signature verification failed");
|
||||||
|
log_request("PUT", "/report", "signature_invalid", 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract blob hashes from x tags
|
||||||
|
char blob_hashes[10][65]; // Support up to 10 blob hashes per report
|
||||||
|
int hash_count = extract_blob_hashes_from_report(event, blob_hashes, 10);
|
||||||
|
if (hash_count == 0) {
|
||||||
|
cJSON_Delete(event);
|
||||||
|
free(json_body);
|
||||||
|
send_error_response(400, "no_blob_hashes", "No valid blob hashes found", "Report must contain at least one valid SHA-256 hash in x tags");
|
||||||
|
log_request("PUT", "/report", "no_hashes", 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate report types (optional validation)
|
||||||
|
validate_report_types(event);
|
||||||
|
|
||||||
|
// Extract reporter pubkey
|
||||||
|
cJSON* pubkey_json = cJSON_GetObjectItem(event, "pubkey");
|
||||||
|
const char* reporter_pubkey = NULL;
|
||||||
|
if (pubkey_json && cJSON_IsString(pubkey_json)) {
|
||||||
|
reporter_pubkey = cJSON_GetStringValue(pubkey_json);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: Store report in database (server behavior)
|
||||||
|
if (reporter_pubkey) {
|
||||||
|
store_blob_report(json_body, reporter_pubkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
cJSON_Delete(event);
|
||||||
|
free(json_body);
|
||||||
|
|
||||||
|
// Return success response
|
||||||
|
printf("Status: 200 OK\r\n");
|
||||||
|
printf("Content-Type: application/json\r\n\r\n");
|
||||||
|
printf("{\n");
|
||||||
|
printf(" \"message\": \"Report received\",\n");
|
||||||
|
printf(" \"reported_blobs\": %d,\n", hash_count);
|
||||||
|
printf(" \"reporter\": \"%s\"\n", reporter_pubkey ? reporter_pubkey : "anonymous");
|
||||||
|
printf("}\n");
|
||||||
|
|
||||||
|
log_request("PUT", "/report", reporter_pubkey ? "authenticated" : "anonymous", 200);
|
||||||
|
}
|
||||||
121
src/ginxsom.h
121
src/ginxsom.h
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Ginxsom Blossom Server Header
|
* Ginxsom Blossom Server Header
|
||||||
*
|
*
|
||||||
* This header contains all function declarations and type definitions
|
* This header contains all function declarations and type definitions
|
||||||
* organized by BUD (Blossom Unified Draft) sections.
|
* organized by BUD (Blossom Unified Draft) sections.
|
||||||
*/
|
*/
|
||||||
@@ -43,18 +43,44 @@ void handle_head_request(const char* uri);
|
|||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Authorization header parsing
|
// NOTE: Old authentication functions removed - now handled by nostr_core_lib unified system
|
||||||
int parse_authorization_header(const char* auth_header, char* event_json, size_t json_size);
|
// Use nostr_validate_request() from request_validator.h for all authentication needs
|
||||||
|
|
||||||
// Blossom event validation (specific to kind 24242)
|
|
||||||
int validate_blossom_event(cJSON* event, const char* expected_hash, const char* method);
|
|
||||||
|
|
||||||
// Main authentication orchestrator
|
|
||||||
int authenticate_request(const char* auth_header, const char* method, const char* file_hash);
|
|
||||||
|
|
||||||
// Upload handling
|
// Upload handling
|
||||||
void handle_upload_request(void);
|
void handle_upload_request(void);
|
||||||
|
|
||||||
|
// Blob metadata structure
|
||||||
|
typedef struct {
|
||||||
|
char sha256[65];
|
||||||
|
long size;
|
||||||
|
char type[128];
|
||||||
|
long uploaded_at;
|
||||||
|
char filename[256];
|
||||||
|
int found;
|
||||||
|
} blob_metadata_t;
|
||||||
|
|
||||||
|
// Blob metadata database operations
|
||||||
|
int insert_blob_metadata(const char* sha256, long size, const char* type,
|
||||||
|
long uploaded_at, const char* uploader_pubkey,
|
||||||
|
const char* filename);
|
||||||
|
int get_blob_metadata(const char* sha256, blob_metadata_t* metadata);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// BUD 04 - Mirroring
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Mirror request handling
|
||||||
|
void handle_mirror_request(void);
|
||||||
|
|
||||||
|
// URL validation for mirroring
|
||||||
|
int validate_mirror_url(const char* url);
|
||||||
|
|
||||||
|
// Content type detection for mirrored blobs
|
||||||
|
const char* determine_blob_content_type(const char* url, const char* header_content_type,
|
||||||
|
const unsigned char* data, size_t size);
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// BUD 06 - Upload Requirements
|
// BUD 06 - Upload Requirements
|
||||||
@@ -64,6 +90,54 @@ void handle_upload_request(void);
|
|||||||
// Upload policy management (for future implementation)
|
// Upload policy management (for future implementation)
|
||||||
void handle_upload_requirements_request(void);
|
void handle_upload_requirements_request(void);
|
||||||
|
|
||||||
|
// BUD-06 specific functions
|
||||||
|
void handle_head_upload_request(void);
|
||||||
|
int validate_upload_headers(const char** sha256, const char** content_type,
|
||||||
|
long* content_length, char* error_reason, size_t reason_size);
|
||||||
|
void send_upload_error_response(int status_code, const char* error_type,
|
||||||
|
const char* message, const char* x_reason);
|
||||||
|
void send_upload_success_response(const char* sha256, const char* content_type, long content_length);
|
||||||
|
int validate_content_length(const char* content_length_str, long* parsed_length);
|
||||||
|
int check_blob_exists(const char* sha256);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// BUD 09 - Blob Report (NIP-56 Report Events)
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Report event validation
|
||||||
|
int validate_report_event_structure(cJSON* event);
|
||||||
|
int extract_blob_hashes_from_report(cJSON* event, char blob_hashes[][65], int max_hashes);
|
||||||
|
int validate_report_types(cJSON* event);
|
||||||
|
|
||||||
|
// Report storage and handling
|
||||||
|
int store_blob_report(const char* event_json, const char* reporter_pubkey);
|
||||||
|
void handle_report_request(void);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// BUD 08 - NIP-94 File Metadata Tags
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// NIP-94 configuration and control
|
||||||
|
int nip94_is_enabled(void);
|
||||||
|
int nip94_get_origin(char* out, size_t out_size);
|
||||||
|
|
||||||
|
// MIME type and file extension handling
|
||||||
|
const char* mime_to_extension(const char* mime_type);
|
||||||
|
void nip94_build_blob_url(const char* origin, const char* sha256, const char* mime_type, char* out, size_t out_size);
|
||||||
|
|
||||||
|
// Image dimension parsing
|
||||||
|
int parse_png_dimensions(const unsigned char* data, size_t size, int* width, int* height);
|
||||||
|
int parse_jpeg_dimensions(const unsigned char* data, size_t size, int* width, int* height);
|
||||||
|
int parse_webp_dimensions(const unsigned char* data, size_t size, int* width, int* height);
|
||||||
|
int nip94_get_dimensions(const unsigned char* data, size_t size, const char* mime_type, int* width, int* height);
|
||||||
|
|
||||||
|
// NIP-94 metadata emission
|
||||||
|
void nip94_emit_field(const char* url, const char* mime, const char* sha256, long size, int width, int height);
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// UTILITY FUNCTIONS
|
// UTILITY FUNCTIONS
|
||||||
@@ -77,6 +151,35 @@ void send_json_response(int status_code, const char* json_content);
|
|||||||
// Logging utilities
|
// Logging utilities
|
||||||
void log_request(const char* method, const char* uri, const char* auth_status, int status_code);
|
void log_request(const char* method, const char* uri, const char* auth_status, int status_code);
|
||||||
|
|
||||||
|
// SHA-256 validation helper (used by multiple BUDs)
|
||||||
|
int validate_sha256_format(const char* sha256);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// ADMIN API ENDPOINTS
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Admin API request handler
|
||||||
|
void handle_admin_api_request(const char* method, const char* uri);
|
||||||
|
|
||||||
|
// Individual endpoint handlers
|
||||||
|
void handle_stats_api(void);
|
||||||
|
void handle_config_get_api(void);
|
||||||
|
void handle_config_put_api(void);
|
||||||
|
void handle_files_api(void);
|
||||||
|
void handle_health_api(void);
|
||||||
|
|
||||||
|
// Admin authentication functions
|
||||||
|
int authenticate_admin_request(const char* auth_header);
|
||||||
|
int is_admin_enabled(void);
|
||||||
|
int verify_admin_pubkey(const char* event_pubkey);
|
||||||
|
|
||||||
|
// Admin API utility functions
|
||||||
|
void send_json_response(int status, const char* json_content);
|
||||||
|
void send_json_error(int status, const char* error, const char* message);
|
||||||
|
int parse_query_params(const char* query_string, char params[][256], int max_params);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
1773
src/main.c
1773
src/main.c
File diff suppressed because it is too large
Load Diff
@@ -9,13 +9,49 @@ UPLOAD_ENDPOINT="${SERVER_URL}/upload"
|
|||||||
DB_PATH="db/ginxsom.db"
|
DB_PATH="db/ginxsom.db"
|
||||||
TEST_DIR="tests/auth_test_tmp"
|
TEST_DIR="tests/auth_test_tmp"
|
||||||
|
|
||||||
|
# Test results tracking
|
||||||
|
TESTS_PASSED=0
|
||||||
|
TESTS_FAILED=0
|
||||||
|
TOTAL_TESTS=0
|
||||||
|
|
||||||
# Test keys for different scenarios
|
# Test keys for different scenarios
|
||||||
TEST_USER1_PRIVKEY="5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a"
|
TEST_USER1_PRIVKEY="5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a"
|
||||||
TEST_USER1_PUBKEY="79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
|
TEST_USER1_PUBKEY="87d3561f19b74adbe8bf840682992466068830a9d8c36b4a0c99d36f826cb6cb"
|
||||||
|
|
||||||
TEST_USER2_PRIVKEY="182c3a5e3b7a1b7e4f5c6b7c8b4a5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
|
TEST_USER2_PRIVKEY="182c3a5e3b7a1b7e4f5c6b7c8b4a5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
|
||||||
TEST_USER2_PUBKEY="c95195e5e7de1ad8c4d3c0ac4e8b5c0c4e0c4d3c1e5c8d4c2e7e9f4a5b6c7d8e"
|
TEST_USER2_PUBKEY="c95195e5e7de1ad8c4d3c0ac4e8b5c0c4e0c4d3c1e5c8d4c2e7e9f4a5b6c7d8e"
|
||||||
|
|
||||||
|
# Helper function to record test results
|
||||||
|
record_test_result() {
|
||||||
|
local test_name="$1"
|
||||||
|
local expected="$2"
|
||||||
|
local actual="$3"
|
||||||
|
local success="${4:-}" # Optional success override
|
||||||
|
|
||||||
|
TOTAL_TESTS=$((TOTAL_TESTS + 1))
|
||||||
|
|
||||||
|
if [[ -n "$success" ]]; then
|
||||||
|
# Use explicit success value
|
||||||
|
if [[ "$success" == "true" ]]; then
|
||||||
|
echo "✅ $test_name - PASSED"
|
||||||
|
TESTS_PASSED=$((TESTS_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo "❌ $test_name - FAILED"
|
||||||
|
TESTS_FAILED=$((TESTS_FAILED + 1))
|
||||||
|
fi
|
||||||
|
elif [[ "$expected" == "ANY" ]]; then
|
||||||
|
# Any result is acceptable
|
||||||
|
echo "✅ $test_name - PASSED (HTTP $actual)"
|
||||||
|
TESTS_PASSED=$((TESTS_PASSED + 1))
|
||||||
|
elif [[ "$actual" == "$expected" ]]; then
|
||||||
|
echo "✅ $test_name - PASSED"
|
||||||
|
TESTS_PASSED=$((TESTS_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo "❌ $test_name - FAILED (Expected: $expected, Got: $actual)"
|
||||||
|
TESTS_FAILED=$((TESTS_FAILED + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
echo "=== Ginxsom Authentication System Test Suite ==="
|
echo "=== Ginxsom Authentication System Test Suite ==="
|
||||||
echo "Testing unified nostr_core_lib authentication integration"
|
echo "Testing unified nostr_core_lib authentication integration"
|
||||||
echo "Timestamp: $(date -Iseconds)"
|
echo "Timestamp: $(date -Iseconds)"
|
||||||
@@ -115,12 +151,7 @@ test_upload() {
|
|||||||
local file_path="$3"
|
local file_path="$3"
|
||||||
local expected_status="${4:-ANY}"
|
local expected_status="${4:-ANY}"
|
||||||
|
|
||||||
echo "=== $test_name ==="
|
|
||||||
|
|
||||||
local file_hash=$(sha256sum "$file_path" | cut -d' ' -f1)
|
local file_hash=$(sha256sum "$file_path" | cut -d' ' -f1)
|
||||||
echo "File: $(basename "$file_path")"
|
|
||||||
echo "Hash: $file_hash"
|
|
||||||
echo "User pubkey: $(echo "$privkey" | nak key public)"
|
|
||||||
|
|
||||||
# Create auth event
|
# Create auth event
|
||||||
local event=$(create_auth_event "$privkey" "upload" "$file_hash")
|
local event=$(create_auth_event "$privkey" "upload" "$file_hash")
|
||||||
@@ -133,23 +164,12 @@ test_upload() {
|
|||||||
-H "Content-Type: text/plain" \
|
-H "Content-Type: text/plain" \
|
||||||
--data-binary "@$file_path" \
|
--data-binary "@$file_path" \
|
||||||
-X PUT "$UPLOAD_ENDPOINT" \
|
-X PUT "$UPLOAD_ENDPOINT" \
|
||||||
-o "$response_file")
|
-o "$response_file" 2>/dev/null)
|
||||||
|
|
||||||
echo "HTTP Status: $http_status"
|
|
||||||
echo "Server Response:"
|
|
||||||
cat "$response_file" | jq . 2>/dev/null || cat "$response_file"
|
|
||||||
echo
|
|
||||||
|
|
||||||
rm -f "$response_file"
|
rm -f "$response_file"
|
||||||
|
|
||||||
if [[ "$expected_status" != "ANY" ]]; then
|
# Record result
|
||||||
if [[ "$http_status" == "$expected_status" ]]; then
|
record_test_result "$test_name" "$expected_status" "$http_status"
|
||||||
echo "✓ Expected HTTP $expected_status - PASSED"
|
|
||||||
else
|
|
||||||
echo "✗ Expected HTTP $expected_status, got $http_status - FAILED"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
echo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Run the tests
|
# Run the tests
|
||||||
@@ -196,8 +216,6 @@ test_failure_mode() {
|
|||||||
local file_content="${3:-failure_test_content}"
|
local file_content="${3:-failure_test_content}"
|
||||||
local expected_status="${4:-401}"
|
local expected_status="${4:-401}"
|
||||||
|
|
||||||
echo "=== $test_name ==="
|
|
||||||
|
|
||||||
local test_file=$(mktemp)
|
local test_file=$(mktemp)
|
||||||
echo "$file_content" > "$test_file"
|
echo "$file_content" > "$test_file"
|
||||||
|
|
||||||
@@ -207,21 +225,12 @@ test_failure_mode() {
|
|||||||
-H "Content-Type: text/plain" \
|
-H "Content-Type: text/plain" \
|
||||||
--data-binary "@$test_file" \
|
--data-binary "@$test_file" \
|
||||||
-X PUT "$UPLOAD_ENDPOINT" \
|
-X PUT "$UPLOAD_ENDPOINT" \
|
||||||
-o "$response_file")
|
-o "$response_file" 2>/dev/null)
|
||||||
|
|
||||||
echo "HTTP Status: $http_status"
|
|
||||||
echo "Server Response:"
|
|
||||||
cat "$response_file" | jq . 2>/dev/null || cat "$response_file"
|
|
||||||
echo
|
|
||||||
|
|
||||||
rm -f "$test_file" "$response_file"
|
rm -f "$test_file" "$response_file"
|
||||||
|
|
||||||
if [[ "$http_status" == "$expected_status" ]]; then
|
# Record result
|
||||||
echo "✓ Expected HTTP $expected_status - PASSED"
|
record_test_result "$test_name" "$expected_status" "$http_status"
|
||||||
else
|
|
||||||
echo "✗ Expected HTTP $expected_status, got $http_status - FAILED"
|
|
||||||
fi
|
|
||||||
echo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Test 6a: Missing Authorization Header
|
# Test 6a: Missing Authorization Header
|
||||||
@@ -259,8 +268,8 @@ short_key_event=$(cat << EOF
|
|||||||
"created_at": $(date +%s),
|
"created_at": $(date +%s),
|
||||||
"pubkey": "$short_pubkey",
|
"pubkey": "$short_pubkey",
|
||||||
"tags": [["t", "upload"], ["x", "$file_hash"]],
|
"tags": [["t", "upload"], ["x", "$file_hash"]],
|
||||||
"id": "invalid_id",
|
"id": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
"sig": "invalid_signature"
|
"sig": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
@@ -279,8 +288,8 @@ nonhex_key_event=$(cat << EOF
|
|||||||
"created_at": $(date +%s),
|
"created_at": $(date +%s),
|
||||||
"pubkey": "$nonhex_pubkey",
|
"pubkey": "$nonhex_pubkey",
|
||||||
"tags": [["t", "upload"], ["x", "$file_hash"]],
|
"tags": [["t", "upload"], ["x", "$file_hash"]],
|
||||||
"id": "invalid_id",
|
"id": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
"sig": "invalid_signature"
|
"sig": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
@@ -345,16 +354,151 @@ corrupted_event=$(echo "$valid_event" | sed 's/.\{1\}$/x/') # Replace last char
|
|||||||
corrupted_b64=$(echo -n "$corrupted_event" | base64 -w 0)
|
corrupted_b64=$(echo -n "$corrupted_event" | base64 -w 0)
|
||||||
test_failure_mode "Test 12a: Corrupted Signature" "Nostr $corrupted_b64"
|
test_failure_mode "Test 12a: Corrupted Signature" "Nostr $corrupted_b64"
|
||||||
|
|
||||||
# Show final state
|
|
||||||
echo "=== Final Database State ==="
|
|
||||||
echo "Authentication rules left in database:"
|
|
||||||
sqlite3 "$DB_PATH" -header -column "SELECT rule_type, rule_target, operation, priority, enabled, description FROM auth_rules WHERE description LIKE 'TEST_%' ORDER BY priority;"
|
|
||||||
echo
|
|
||||||
echo "Auth config:"
|
|
||||||
sqlite3 "$DB_PATH" -header -column "SELECT key, value FROM auth_config WHERE key = 'auth_rules_enabled';"
|
|
||||||
echo
|
|
||||||
|
|
||||||
echo "=== Test Suite Completed ==="
|
echo "=== Test 13: NIP-42 Authentication Support ==="
|
||||||
echo "Comprehensive authentication and failure mode testing completed."
|
|
||||||
echo "Auth rules have been left in the database for inspection."
|
# Helper function to create NIP-42 challenge request
|
||||||
echo "To clean up, run: sqlite3 $DB_PATH \"DELETE FROM auth_rules WHERE description LIKE 'TEST_%';\""
|
test_nip42_challenge() {
|
||||||
|
local response_file=$(mktemp)
|
||||||
|
local http_status=$(curl -s -w "%{http_code}" -o "$response_file" \
|
||||||
|
-X GET "${SERVER_URL}/auth" 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ "$http_status" == "200" ]]; then
|
||||||
|
local challenge=$(cat "$response_file" | jq -r '.challenge' 2>/dev/null)
|
||||||
|
if [[ -n "$challenge" && "$challenge" != "null" ]]; then
|
||||||
|
echo "$challenge" > "$TEST_DIR/nip42_challenge"
|
||||||
|
record_test_result "NIP-42 Challenge Generation" "200" "$http_status"
|
||||||
|
rm -f "$response_file"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
record_test_result "NIP-42 Challenge Generation" "200" "INVALID_FORMAT"
|
||||||
|
rm -f "$response_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
elif [[ "$http_status" == "404" ]]; then
|
||||||
|
record_test_result "NIP-42 Challenge Generation" "DISABLED" "DISABLED" "true"
|
||||||
|
rm -f "$response_file"
|
||||||
|
return 2 # Disabled, not an error
|
||||||
|
else
|
||||||
|
record_test_result "NIP-42 Challenge Generation" "200" "$http_status"
|
||||||
|
rm -f "$response_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper function to create NIP-42 authentication event
|
||||||
|
create_nip42_auth_event() {
|
||||||
|
local challenge="$1"
|
||||||
|
local privkey="$2"
|
||||||
|
|
||||||
|
# Create NIP-42 authentication event (kind 22242) using nak for proper signing
|
||||||
|
nak event -k 22242 -c "" \
|
||||||
|
--tag "relay=ws://localhost:9001" \
|
||||||
|
--tag "challenge=$challenge" \
|
||||||
|
--sec "$privkey"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_nip42_authentication() {
|
||||||
|
# First, try to get a challenge
|
||||||
|
test_nip42_challenge
|
||||||
|
local challenge_result=$?
|
||||||
|
|
||||||
|
if [[ $challenge_result -eq 2 ]]; then
|
||||||
|
record_test_result "NIP-42 Authentication Flow" "DISABLED" "DISABLED" "true"
|
||||||
|
return 0
|
||||||
|
elif [[ $challenge_result -ne 0 ]]; then
|
||||||
|
record_test_result "NIP-42 Authentication Flow" "SUCCESS" "NO_CHALLENGE"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local challenge=$(cat "$TEST_DIR/nip42_challenge" 2>/dev/null)
|
||||||
|
if [[ -z "$challenge" ]]; then
|
||||||
|
record_test_result "NIP-42 Authentication Flow" "SUCCESS" "NO_CHALLENGE"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create NIP-42 auth event
|
||||||
|
local nip42_event=$(create_nip42_auth_event "$challenge" "$TEST_USER1_PRIVKEY")
|
||||||
|
local nip42_auth_header="Nostr $(echo "$nip42_event" | base64 -w 0)"
|
||||||
|
|
||||||
|
# Test upload with NIP-42 authentication
|
||||||
|
local test_file=$(create_test_file "nip42_test.txt" "NIP-42 authentication test content")
|
||||||
|
local response_file=$(mktemp)
|
||||||
|
local http_status=$(curl -s -w "%{http_code}" \
|
||||||
|
-H "Authorization: $nip42_auth_header" \
|
||||||
|
-H "Content-Type: text/plain" \
|
||||||
|
--data-binary "@$test_file" \
|
||||||
|
-X PUT "$UPLOAD_ENDPOINT" \
|
||||||
|
-o "$response_file" 2>/dev/null)
|
||||||
|
|
||||||
|
rm -f "$response_file"
|
||||||
|
|
||||||
|
# Record result
|
||||||
|
record_test_result "NIP-42 Authentication Flow" "200" "$http_status"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test NIP-42 configuration modes
|
||||||
|
test_nip42_configuration() {
|
||||||
|
# Check NIP-42 mode in database using correct table/column
|
||||||
|
local nip42_mode=$(sqlite3 "$DB_PATH" "SELECT value FROM server_config WHERE key = 'require_nip42_auth';" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [[ -n "$nip42_mode" ]]; then
|
||||||
|
case "$nip42_mode" in
|
||||||
|
"true"|"false")
|
||||||
|
record_test_result "NIP-42 Configuration Check" "VALID" "VALID" "true"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
record_test_result "NIP-42 Configuration Check" "VALID" "INVALID"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
record_test_result "NIP-42 Configuration Check" "VALID" "DEFAULT" "true"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test dual authentication capability
|
||||||
|
test_dual_authentication_detection() {
|
||||||
|
# Check if both authentication methods can be detected
|
||||||
|
local blossom_event=$(create_auth_event "$TEST_USER1_PRIVKEY" "upload" "")
|
||||||
|
local blossom_kind=$(echo "$blossom_event" | jq -r '.kind')
|
||||||
|
|
||||||
|
local nip42_event=$(create_nip42_auth_event "test_challenge" "$TEST_USER1_PRIVKEY")
|
||||||
|
local nip42_kind=$(echo "$nip42_event" | jq -r '.kind')
|
||||||
|
|
||||||
|
if [[ "$blossom_kind" == "24242" && "$nip42_kind" == "22242" ]]; then
|
||||||
|
record_test_result "Dual Authentication System Detection" "SUCCESS" "SUCCESS" "true"
|
||||||
|
else
|
||||||
|
record_test_result "Dual Authentication System Detection" "SUCCESS" "FAILED"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run NIP-42 tests
|
||||||
|
echo "Running NIP-42 authentication tests..."
|
||||||
|
test_nip42_configuration
|
||||||
|
test_dual_authentication_detection
|
||||||
|
test_nip42_challenge
|
||||||
|
test_nip42_authentication
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=========================================="
|
||||||
|
echo " TEST SUITE RESULTS"
|
||||||
|
echo "=========================================="
|
||||||
|
echo
|
||||||
|
echo "Total Tests: $TOTAL_TESTS"
|
||||||
|
echo "✅ Passed: $TESTS_PASSED"
|
||||||
|
echo "❌ Failed: $TESTS_FAILED"
|
||||||
|
echo
|
||||||
|
if [[ $TESTS_FAILED -eq 0 ]]; then
|
||||||
|
echo "🎉 ALL TESTS PASSED!"
|
||||||
|
echo "Authentication system fully operational:"
|
||||||
|
echo "- Blossom authentication (kind 24242): Working"
|
||||||
|
echo "- NIP-42 authentication (kind 22242): Working"
|
||||||
|
echo "- Dual authentication support: Available"
|
||||||
|
echo "- Challenge/response system: Ready"
|
||||||
|
else
|
||||||
|
echo "⚠️ Some tests failed. Check output above for details."
|
||||||
|
echo "Success rate: $(( (TESTS_PASSED * 100) / TOTAL_TESTS ))%"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
echo "To clean up test data: sqlite3 $DB_PATH \"DELETE FROM auth_rules WHERE description LIKE 'TEST_%';\""
|
||||||
|
echo "=========================================="
|
||||||
1
tests/auth_test_tmp/nip42_challenge
Normal file
1
tests/auth_test_tmp/nip42_challenge
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3fb6a0ea1d337bd09f1f88f65f124174ad7161dd5ea0fae74c0dd0b0db43a24e
|
||||||
1
tests/auth_test_tmp/nip42_test.txt
Normal file
1
tests/auth_test_tmp/nip42_test.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
NIP-42 authentication test content
|
||||||
Reference in New Issue
Block a user