v0.3.11 - Working on admin api
This commit is contained in:
325
README.md
325
README.md
@@ -22,4 +22,329 @@ Do NOT modify the formatting, add emojis, or change the text. Keep the simple fo
|
||||
- [ ] NIP-50: Keywords filter
|
||||
- [ ] NIP-70: Protected Events
|
||||
|
||||
## 🔧 Administrator API
|
||||
|
||||
C-Relay uses an innovative **event-based administration system** where all configuration and management commands are sent as signed Nostr events using the admin private key generated during first startup. Admin commands use ephemeral kinds 23455 and 23456 with **optional NIP-44 encryption** for enhanced security.
|
||||
|
||||
### Authentication
|
||||
|
||||
All admin commands require signing with the admin private key displayed during first-time startup. **Save this key securely** - it cannot be recovered and is needed for all administrative operations.
|
||||
|
||||
### Security Options
|
||||
|
||||
**Standard Mode (Plaintext):** Commands sent in tags as normal
|
||||
**Encrypted Mode (NIP-44):** Commands encrypted in content field, no tags used
|
||||
|
||||
### Kind 23455: Configuration Management (Ephemeral)
|
||||
Update relay configuration parameters or query available settings.
|
||||
|
||||
**Configuration Update:**
|
||||
```json
|
||||
{
|
||||
"kind": 23455,
|
||||
"content": "<optional nip-44 encrypted tags>",
|
||||
"tags": [
|
||||
["config_key1", "config_value1"],
|
||||
["config_key2", "config_value2"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Query Available Config Keys:**
|
||||
```json
|
||||
{
|
||||
"kind": 23455,
|
||||
"content": "<optional nip-44 encrypted tags>",
|
||||
"tags": [
|
||||
["config_query", "list_all_keys"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Get Current Configuration:**
|
||||
```json
|
||||
{
|
||||
"kind": 23455,
|
||||
"content": "<optional nip-44 encrypted tags>",
|
||||
"tags": [
|
||||
["config_query", "get_current_config"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Available Configuration Keys:**
|
||||
|
||||
**Basic Relay Settings:**
|
||||
- `relay_description`: Relay description text
|
||||
- `relay_contact`: Contact information
|
||||
- `max_connections`: Maximum concurrent connections
|
||||
- `max_subscriptions_per_client`: Max subscriptions per client
|
||||
- `max_event_tags`: Maximum tags per event
|
||||
- `max_content_length`: Maximum event content length
|
||||
|
||||
**Authentication & Access Control:**
|
||||
- `auth_enabled`: Enable whitelist/blacklist auth rules (`true`/`false`)
|
||||
- `nip42_auth_required`: Enable NIP-42 cryptographic authentication (`true`/`false`)
|
||||
- `nip42_auth_required_kinds`: Event kinds requiring NIP-42 auth (comma-separated)
|
||||
- `nip42_challenge_timeout`: NIP-42 challenge expiration seconds
|
||||
|
||||
**Proof of Work & Validation:**
|
||||
- `pow_min_difficulty`: Minimum proof-of-work difficulty
|
||||
- `nip40_expiration_enabled`: Enable event expiration (`true`/`false`)
|
||||
|
||||
### Kind 23456: Auth Rules & System Management (Ephemeral)
|
||||
Manage whitelist/blacklist rules and system administration commands.
|
||||
|
||||
**Add Blacklist Rule:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "<optional nip-44 encrypted tags>",
|
||||
"tags": [
|
||||
["blacklist", "pubkey", "deadbeef1234abcd..."]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Add Whitelist Rule:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "<optional nip-44 encrypted tags>",
|
||||
"tags": [
|
||||
["whitelist", "pubkey", "cafebabe5678efgh..."]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Remove Rule:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "<optional nip-44 encrypted tags>",
|
||||
"tags": [
|
||||
["blacklist", "pubkey", "deadbeef1234abcd..."]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Query Auth Rules:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "<optional nip-44 encrypted tags>",
|
||||
"tags": [
|
||||
["auth_query", "all"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Query Specific Rule Type:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "<optional nip-44 encrypted tags>",
|
||||
"tags": [
|
||||
["auth_query", "whitelist"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Query Specific Pattern:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "<optional nip-44 encrypted tags>",
|
||||
"tags": [
|
||||
["auth_query", "pattern", "deadbeef1234abcd..."]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Clear All Auth Rules:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "<optional nip-44 encrypted tags>",
|
||||
"tags": [
|
||||
["system_command", "clear_all_auth_rules"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Response Format
|
||||
All admin commands return JSON responses via WebSocket:
|
||||
|
||||
**Success Response:**
|
||||
```json
|
||||
["OK", "event_id", true, "Operation completed successfully"]
|
||||
```
|
||||
|
||||
**Error Response:**
|
||||
```json
|
||||
["OK", "event_id", false, "Error: invalid configuration value"]
|
||||
```
|
||||
|
||||
### Command Examples
|
||||
|
||||
**Using `nak` CLI tool:**
|
||||
|
||||
```bash
|
||||
# Set environment variables
|
||||
ADMIN_PRIVKEY="your_admin_private_key_here"
|
||||
RELAY_PUBKEY="your_relay_public_key_here"
|
||||
RELAY_URL="ws://localhost:8888"
|
||||
|
||||
# List all available configuration keys
|
||||
nak event -k 23455 --content "Discovery query" \
|
||||
-t "config_query=list_all_keys" \
|
||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
||||
|
||||
# Enable whitelist/blacklist auth rules
|
||||
nak event -k 23455 --content "Enable auth rules" \
|
||||
-t "auth_enabled=true" \
|
||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
||||
|
||||
# Add user to blacklist
|
||||
nak event -k 23456 --content "Block spam user" \
|
||||
-t "blacklist=pubkey;$SPAM_USER_PUBKEY" \
|
||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
||||
|
||||
# Add user to whitelist
|
||||
nak event -k 23456 --content "Allow trusted user" \
|
||||
-t "whitelist=pubkey;$TRUSTED_USER_PUBKEY" \
|
||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
||||
|
||||
# Query all current auth rules
|
||||
nak event -k 23456 --content "Get all auth rules" \
|
||||
-t "auth_query=all" \
|
||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
||||
|
||||
# Query only whitelist rules
|
||||
nak event -k 23456 --content "Get whitelist rules" \
|
||||
-t "auth_query=whitelist" \
|
||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
||||
|
||||
# Query only blacklist rules
|
||||
nak event -k 23456 --content "Get blacklist rules" \
|
||||
-t "auth_query=blacklist" \
|
||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
||||
|
||||
# Check if specific pattern exists
|
||||
nak event -k 23456 --content "Check user status" \
|
||||
-t "auth_query=pattern;$CHECK_USER_PUBKEY" \
|
||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
||||
|
||||
# Clear all auth rules (for testing)
|
||||
nak event -k 23456 --content "Clear all rules" \
|
||||
-t "system_command=clear_all_auth_rules" \
|
||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
||||
|
||||
# Update relay description
|
||||
nak event -k 23455 --content "Update description" \
|
||||
-t "relay_description=My awesome relay" \
|
||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
||||
|
||||
# Set connection limits
|
||||
nak event -k 23455 --content "Update limits" \
|
||||
-t "max_connections=500" \
|
||||
-t "max_subscriptions_per_client=10" \
|
||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
||||
```
|
||||
|
||||
**For encrypted commands (NIP-44), use empty tags and encrypted content:**
|
||||
```bash
|
||||
# Enable auth rules (encrypted)
|
||||
ENCRYPTED_TAGS=$(echo '[["auth_enabled","true"]]' | nip44_encrypt_with_relay_pubkey)
|
||||
nak event -k 23455 --content "{\"encrypted_tags\":\"$ENCRYPTED_TAGS\"}" \
|
||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
||||
|
||||
# Add user to blacklist (encrypted)
|
||||
ENCRYPTED_TAGS=$(echo '[["blacklist","pubkey","'$SPAM_USER_PUBKEY'"]]' | nip44_encrypt_with_relay_pubkey)
|
||||
nak event -k 23456 --content "{\"encrypted_tags\":\"$ENCRYPTED_TAGS\"}" \
|
||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
||||
```
|
||||
|
||||
### Authentication Systems
|
||||
|
||||
C-Relay supports **two independent authentication systems**:
|
||||
|
||||
#### 1. Auth Rules (Whitelist/Blacklist)
|
||||
- **Config Key**: `auth_enabled=true`
|
||||
- **Purpose**: Block or allow specific pubkeys from publishing events
|
||||
- **Method**: Database-driven rules enforcement
|
||||
- **Use Case**: Spam prevention, content moderation
|
||||
|
||||
#### 2. NIP-42 Cryptographic Authentication
|
||||
- **Config Key**: `nip42_auth_required=true`
|
||||
- **Purpose**: Require cryptographic proof of pubkey ownership
|
||||
- **Method**: Challenge-response authentication protocol
|
||||
- **Use Case**: Verified identity, premium features
|
||||
|
||||
**Important**: These systems can be used independently or together:
|
||||
- `auth_enabled=true` + `nip42_auth_required=false`: Only whitelist/blacklist rules
|
||||
- `auth_enabled=false` + `nip42_auth_required=true`: Only NIP-42 auth challenges
|
||||
- Both `true`: Users must pass NIP-42 auth AND not be blacklisted
|
||||
|
||||
### Security Considerations
|
||||
|
||||
- **Private Key Protection**: Admin private key grants full relay control
|
||||
- **Network Access**: Admin commands should only be sent over secure connections
|
||||
- **Backup**: Keep secure backups of admin private key
|
||||
- **Rotation**: Consider implementing admin key rotation for production deployments
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
**Common Issues:**
|
||||
- `auth-required: not authorized admin`: Verify you're using the correct admin private key
|
||||
- `invalid: missing or invalid kind`: Ensure event kind is 23455 or 23456
|
||||
- `error: failed to process configuration event`: Check configuration key/value validity
|
||||
- `no valid auth rules found`: Verify tag format uses semicolon syntax for multi-element tags
|
||||
|
||||
**Debug Commands:**
|
||||
```bash
|
||||
# Check if relay is accepting connections
|
||||
echo '["REQ","test",{}]' | websocat ws://localhost:8888
|
||||
|
||||
# Test admin authentication
|
||||
nak event -k 23455 --content "Test auth" \
|
||||
-t "test_config=true" \
|
||||
--sec $ADMIN_PRIVKEY | nak event ws://localhost:8888
|
||||
|
||||
# Discover available configuration keys
|
||||
nak event -k 23455 --content "Discovery query" \
|
||||
-t "config_query=list_all_keys" \
|
||||
--sec $ADMIN_PRIVKEY | nak event ws://localhost:8888
|
||||
|
||||
# Query current auth rules
|
||||
nak event -k 23456 --content "Get auth rules" \
|
||||
-t "auth_query=all" \
|
||||
--sec $ADMIN_PRIVKEY | nak event ws://localhost:8888
|
||||
```
|
||||
|
||||
### Configuration Discovery
|
||||
|
||||
Before making changes, admins can query the relay to discover all available configuration options:
|
||||
|
||||
```bash
|
||||
# Get list of all editable configuration keys with descriptions
|
||||
nak event -k 23455 --content '{"query":"list_config_keys","description":"Discovery"}' \
|
||||
-t "config_query=list_all_keys" \
|
||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
||||
|
||||
# Get current values of all configuration parameters
|
||||
nak event -k 23455 --content '{"query":"get_config","description":"Current state"}' \
|
||||
-t "config_query=get_current_config" \
|
||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
||||
```
|
||||
|
||||
**Expected Response Format:**
|
||||
```json
|
||||
["EVENT", "subscription_id", {
|
||||
"kind": 23455,
|
||||
"content": "{\"config_keys\": [\"auth_enabled\", \"max_connections\", ...], \"descriptions\": {...}}",
|
||||
"tags": [["response_type", "config_keys_list"]]
|
||||
}]
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -951,12 +951,12 @@
|
||||
relayStatus.textContent = 'CONNECTED - SUBSCRIBING...';
|
||||
relayStatus.className = 'status connected';
|
||||
|
||||
// Send REQ message to subscribe to kind 33334 events
|
||||
// Send REQ message to subscribe to kind 23455 events (ephemeral config events)
|
||||
const reqMessage = [
|
||||
"REQ",
|
||||
subscriptionId,
|
||||
{
|
||||
"kinds": [33334],
|
||||
"kinds": [23455],
|
||||
"limit": 50
|
||||
}
|
||||
];
|
||||
@@ -1176,10 +1176,10 @@
|
||||
'max_limit': 'Maximum Query Limit'
|
||||
};
|
||||
|
||||
// Process configuration tags
|
||||
// Process configuration tags (no d tag filtering for ephemeral events)
|
||||
const configData = {};
|
||||
event.tags.forEach(tag => {
|
||||
if (tag.length >= 2 && tag[0] !== 'd') { // Skip 'd' tag (relay identifier)
|
||||
if (tag.length >= 2) {
|
||||
configData[tag[0]] = tag[1];
|
||||
}
|
||||
});
|
||||
@@ -1270,26 +1270,23 @@
|
||||
const formInputs = configForm.querySelectorAll('input, select');
|
||||
const newTags = [];
|
||||
|
||||
// Preserve the 'd' tag (relay identifier) from original event
|
||||
const dTag = currentConfig.tags.find(tag => tag[0] === 'd');
|
||||
if (dTag) {
|
||||
newTags.push(dTag);
|
||||
}
|
||||
|
||||
// Add updated configuration tags
|
||||
// Add updated configuration tags (no d tag needed for ephemeral events)
|
||||
formInputs.forEach(input => {
|
||||
if (!input.disabled && input.name) {
|
||||
newTags.push([input.name, input.value]);
|
||||
}
|
||||
});
|
||||
|
||||
// Create new kind 33334 event
|
||||
// Create new kind 23455 event (ephemeral configuration event)
|
||||
const newEvent = {
|
||||
kind: 33334,
|
||||
kind: 23455,
|
||||
pubkey: userPubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: newTags,
|
||||
content: currentConfig.content || 'C Nostr Relay Configuration'
|
||||
content: JSON.stringify({
|
||||
action: 'update_config',
|
||||
config_data: Object.fromEntries(newTags.filter(tag => tag[0] !== 'd'))
|
||||
})
|
||||
};
|
||||
|
||||
console.log('Signing event with window.nostr.signEvent()...');
|
||||
@@ -1660,7 +1657,7 @@
|
||||
try {
|
||||
log(`Deleting auth rule: ${rule.rule_type} - ${rule.pattern_value || rule.rule_target}`, 'INFO');
|
||||
|
||||
// TODO: Implement actual rule deletion via WebSocket kind 33335 event
|
||||
// TODO: Implement actual rule deletion via WebSocket kind 23456 event
|
||||
// For now, just remove from local array
|
||||
currentAuthRules.splice(index, 1);
|
||||
displayAuthRules(currentAuthRules);
|
||||
@@ -1746,7 +1743,7 @@
|
||||
if (editingAuthRule) {
|
||||
log(`Updating auth rule: ${ruleData.rule_type} - ${ruleData.pattern_value}`, 'INFO');
|
||||
|
||||
// TODO: Implement actual rule update via WebSocket kind 33335 event
|
||||
// TODO: Implement actual rule update via WebSocket kind 23456 event
|
||||
// For now, just update local array
|
||||
currentAuthRules[editingAuthRule.index] = { ...ruleData, id: editingAuthRule.id || Date.now() };
|
||||
|
||||
@@ -1754,7 +1751,7 @@
|
||||
} else {
|
||||
log(`Adding new auth rule: ${ruleData.rule_type} - ${ruleData.pattern_value}`, 'INFO');
|
||||
|
||||
// TODO: Implement actual rule creation via WebSocket kind 33335 event
|
||||
// TODO: Implement actual rule creation via WebSocket kind 23456 event
|
||||
// For now, just add to local array
|
||||
currentAuthRules.push({ ...ruleData, id: Date.now() });
|
||||
|
||||
@@ -2021,7 +2018,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Add auth rule via WebSocket (kind 33335 event)
|
||||
// Add auth rule via WebSocket (kind 23456 event)
|
||||
async function addAuthRuleViaWebSocket(ruleData) {
|
||||
if (!isLoggedIn || !userPubkey) {
|
||||
throw new Error('Must be logged in to add auth rules');
|
||||
@@ -2064,13 +2061,12 @@
|
||||
dbPatternType = 'pubkey';
|
||||
}
|
||||
|
||||
// Create kind 33335 auth rule event with database schema values
|
||||
// Create kind 23456 auth rule event (ephemeral auth management)
|
||||
const authEvent = {
|
||||
kind: 33335,
|
||||
kind: 23456,
|
||||
pubkey: userPubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [
|
||||
['d', 'auth-rules'], // Addressable event identifier
|
||||
[dbRuleType, dbPatternType, ruleData.pattern_value]
|
||||
],
|
||||
content: JSON.stringify({
|
||||
|
||||
537
docs/admin_api_plan.md
Normal file
537
docs/admin_api_plan.md
Normal file
@@ -0,0 +1,537 @@
|
||||
# C-Relay Administrator API Implementation Plan
|
||||
|
||||
## Problem Analysis
|
||||
|
||||
### Current Issues Identified:
|
||||
|
||||
1. **Schema Mismatch**: Storage system (config.c) vs Validation system (request_validator.c) use different column names and values
|
||||
2. **Missing API Endpoint**: No way to clear auth_rules table for testing
|
||||
3. **Configuration Gap**: Auth rules enforcement may not be properly enabled
|
||||
4. **Documentation Gap**: Admin API commands not documented
|
||||
|
||||
### Root Cause: Auth Rules Schema Inconsistency
|
||||
|
||||
**Current Schema (sql_schema.h lines 140-150):**
|
||||
```sql
|
||||
CREATE TABLE auth_rules (
|
||||
rule_type TEXT CHECK (rule_type IN ('whitelist', 'blacklist')),
|
||||
pattern_type TEXT CHECK (pattern_type IN ('pubkey', 'hash')),
|
||||
pattern_value TEXT,
|
||||
action TEXT CHECK (action IN ('allow', 'deny')),
|
||||
active INTEGER DEFAULT 1
|
||||
);
|
||||
```
|
||||
|
||||
**Storage Implementation (config.c):**
|
||||
- Stores: `rule_type='blacklist'`, `pattern_type='pubkey'`, `pattern_value='hex'`, `action='allow'`
|
||||
|
||||
**Validation Implementation (request_validator.c):**
|
||||
- Queries: `rule_type='pubkey_blacklist'`, `rule_target='hex'`, `operation='event'`, `enabled=1`
|
||||
|
||||
**MISMATCH**: Validator looks for non-existent columns and wrong rule_type values!
|
||||
|
||||
## Proposed Solution Architecture
|
||||
|
||||
### Phase 1: API Documentation & Standardization
|
||||
|
||||
#### Admin API Commands (via WebSocket with admin private key)
|
||||
|
||||
**Kind 23455: Configuration Management (Ephemeral)**
|
||||
- Update relay settings, limits, authentication policies
|
||||
- **Standard Mode**: Commands in tags `["config_key", "config_value"]`
|
||||
- **Encrypted Mode**: Commands NIP-44 encrypted in content `{"encrypted_tags": "..."}`
|
||||
- Content: Descriptive text or encrypted payload
|
||||
- Security: Optional NIP-44 encryption for sensitive operations
|
||||
|
||||
**Kind 23456: Auth Rules & System Management (Ephemeral)**
|
||||
- Auth rules: Add/remove/query whitelist/blacklist rules
|
||||
- System commands: clear rules, status, cache management
|
||||
- **Standard Mode**: Commands in tags
|
||||
- Rule format: `["rule_type", "pattern_type", "pattern_value"]`
|
||||
- Query format: `["auth_query", "filter"]`
|
||||
- System format: `["system_command", "command_name"]`
|
||||
- **Encrypted Mode**: Commands NIP-44 encrypted in content `{"encrypted_tags": "..."}`
|
||||
- Content: Action description + optional encrypted payload
|
||||
- Security: Optional NIP-44 encryption for sensitive operations
|
||||
|
||||
#### Configuration Query Commands (using Kind 23455)
|
||||
|
||||
1. **List All Configuration Keys (Standard)**:
|
||||
```json
|
||||
{
|
||||
"kind": 23455,
|
||||
"content": "Discovery query",
|
||||
"tags": [["config_query", "list_all_keys"]]
|
||||
}
|
||||
```
|
||||
|
||||
2. **List All Configuration Keys (Encrypted)**:
|
||||
```json
|
||||
{
|
||||
"kind": 23455,
|
||||
"content": "{\"query\":\"list_config_keys\",\"encrypted_tags\":\"nip44_encrypted_payload\"}",
|
||||
"tags": []
|
||||
}
|
||||
```
|
||||
*Encrypted payload contains:* `[["config_query", "list_all_keys"]]`
|
||||
|
||||
3. **Get Current Configuration (Standard)**:
|
||||
```json
|
||||
{
|
||||
"kind": 23455,
|
||||
"content": "Config query",
|
||||
"tags": [["config_query", "get_current_config"]]
|
||||
}
|
||||
```
|
||||
|
||||
4. **Get Current Configuration (Encrypted)**:
|
||||
```json
|
||||
{
|
||||
"kind": 23455,
|
||||
"content": "{\"query\":\"get_config\",\"encrypted_tags\":\"nip44_encrypted_payload\"}",
|
||||
"tags": []
|
||||
}
|
||||
```
|
||||
*Encrypted payload contains:* `[["config_query", "get_current_config"]]`
|
||||
|
||||
#### System Management Commands (using Kind 23456)
|
||||
|
||||
1. **Clear All Auth Rules (Standard)**:
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"action\":\"clear_all\"}",
|
||||
"tags": [["system_command", "clear_all_auth_rules"]]
|
||||
}
|
||||
```
|
||||
|
||||
2. **Clear All Auth Rules (Encrypted)**:
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"action\":\"clear_all\",\"encrypted_tags\":\"nip44_encrypted_payload\"}",
|
||||
"tags": []
|
||||
}
|
||||
```
|
||||
*Encrypted payload contains:* `[["system_command", "clear_all_auth_rules"]]`
|
||||
|
||||
3. **Query All Auth Rules (Standard)**:
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"query\":\"list_auth_rules\"}",
|
||||
"tags": [["auth_query", "all"]]
|
||||
}
|
||||
```
|
||||
|
||||
4. **Query All Auth Rules (Encrypted)**:
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"query\":\"list_auth_rules\",\"encrypted_tags\":\"nip44_encrypted_payload\"}",
|
||||
"tags": []
|
||||
}
|
||||
```
|
||||
*Encrypted payload contains:* `[["auth_query", "all"]]`
|
||||
|
||||
5. **Add Blacklist Rule (Standard)**:
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"action\":\"add\"}",
|
||||
"tags": [["blacklist", "pubkey", "deadbeef1234abcd..."]]
|
||||
}
|
||||
```
|
||||
|
||||
6. **Add Blacklist Rule (Encrypted)**:
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"action\":\"add\",\"encrypted_tags\":\"nip44_encrypted_payload\"}",
|
||||
"tags": []
|
||||
}
|
||||
```
|
||||
*Encrypted payload contains:* `[["blacklist", "pubkey", "deadbeef1234abcd..."]]`
|
||||
|
||||
### Phase 2: Auth Rules Schema Alignment
|
||||
|
||||
#### Option A: Fix Validator to Match Schema (RECOMMENDED)
|
||||
|
||||
**Update request_validator.c:**
|
||||
```sql
|
||||
-- OLD (broken):
|
||||
WHERE rule_type = 'pubkey_blacklist' AND rule_target = ? AND operation = ? AND enabled = 1
|
||||
|
||||
-- NEW (correct):
|
||||
WHERE rule_type = 'blacklist' AND pattern_type = 'pubkey' AND pattern_value = ? AND active = 1
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Matches actual database schema
|
||||
- Simpler rule_type values ('blacklist' vs 'pubkey_blacklist')
|
||||
- Uses existing columns (pattern_value vs rule_target)
|
||||
- Consistent with storage implementation
|
||||
|
||||
#### Option B: Update Schema to Match Validator (NOT RECOMMENDED)
|
||||
|
||||
Would require changing schema, migration scripts, and storage logic.
|
||||
|
||||
### Phase 3: Implementation Priority
|
||||
|
||||
#### High Priority (Critical for blacklist functionality):
|
||||
1. Fix request_validator.c schema mismatch
|
||||
2. Ensure auth_required configuration is enabled
|
||||
3. Update tests to use ephemeral event kinds (23455/23456)
|
||||
4. Test blacklist enforcement
|
||||
|
||||
#### Medium Priority (Enhanced Admin Features):
|
||||
1. **Implement NIP-44 Encryption Support**:
|
||||
- Detect empty tags array for Kind 23455/23456 events
|
||||
- Parse `encrypted_tags` field from content JSON
|
||||
- Decrypt using admin privkey and relay pubkey
|
||||
- Process decrypted tags as normal commands
|
||||
2. Add clear_all_auth_rules system command
|
||||
3. Add auth rule query functionality (both standard and encrypted modes)
|
||||
4. Add configuration discovery (list available config keys)
|
||||
5. Enhanced error reporting in admin API
|
||||
6. Conflict resolution (same pubkey in whitelist + blacklist)
|
||||
|
||||
#### Security Priority (NIP-44 Implementation):
|
||||
1. **Encryption Detection Logic**: Check for empty tags + encrypted_tags field
|
||||
2. **Key Pair Management**: Use admin private key + relay public key for NIP-44
|
||||
3. **Backward Compatibility**: Support both standard and encrypted modes
|
||||
4. **Error Handling**: Graceful fallback if decryption fails
|
||||
5. **Performance**: Cache decrypted results to avoid repeated decryption
|
||||
|
||||
#### Low Priority (Documentation & Polish):
|
||||
1. Complete README.md API documentation
|
||||
2. Example usage scripts
|
||||
3. Admin client tools
|
||||
|
||||
### Phase 4: Expected API Structure
|
||||
|
||||
#### README.md Documentation Format:
|
||||
|
||||
```markdown
|
||||
# C-Relay Administrator API
|
||||
|
||||
## Authentication
|
||||
All admin commands require signing with the admin private key generated during first startup.
|
||||
|
||||
## Configuration Management (Kind 23455 - Ephemeral)
|
||||
Update relay configuration parameters or query available settings.
|
||||
|
||||
**Configuration Update Event:**
|
||||
```json
|
||||
{
|
||||
"kind": 23455,
|
||||
"content": "Configuration update",
|
||||
"tags": [
|
||||
["config_key1", "config_value1"],
|
||||
["config_key2", "config_value2"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**List Available Config Keys:**
|
||||
```json
|
||||
{
|
||||
"kind": 23455,
|
||||
"content": "{\"query\":\"list_config_keys\",\"description\":\"Get editable config keys\"}",
|
||||
"tags": [
|
||||
["config_query", "list_all_keys"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Get Current Configuration:**
|
||||
```json
|
||||
{
|
||||
"kind": 23455,
|
||||
"content": "{\"query\":\"get_config\",\"description\":\"Get current config values\"}",
|
||||
"tags": [
|
||||
["config_query", "get_current_config"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Auth Rules Management (Kind 23456 - Ephemeral)
|
||||
Manage whitelist and blacklist rules.
|
||||
|
||||
**Add Rule Event:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"action\":\"add\",\"description\":\"Block malicious user\"}",
|
||||
"tags": [
|
||||
["blacklist", "pubkey", "deadbeef1234..."]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Remove Rule Event:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"action\":\"remove\",\"description\":\"Unblock user\"}",
|
||||
"tags": [
|
||||
["blacklist", "pubkey", "deadbeef1234..."]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Query All Auth Rules:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"query\":\"list_auth_rules\",\"description\":\"Get all rules\"}",
|
||||
"tags": [
|
||||
["auth_query", "all"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Query Whitelist Rules Only:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"query\":\"list_auth_rules\",\"description\":\"Get whitelist\"}",
|
||||
"tags": [
|
||||
["auth_query", "whitelist"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Check Specific Pattern:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"query\":\"check_pattern\",\"description\":\"Check if pattern exists\"}",
|
||||
"tags": [
|
||||
["auth_query", "pattern", "deadbeef1234..."]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## System Management (Kind 23456 - Ephemeral)
|
||||
System administration commands using the same kind as auth rules.
|
||||
|
||||
**Clear All Auth Rules:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"action\":\"clear_all\",\"description\":\"Clear all auth rules\"}",
|
||||
"tags": [
|
||||
["system_command", "clear_all_auth_rules"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**System Status:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"action\":\"system_status\",\"description\":\"Get system status\"}",
|
||||
"tags": [
|
||||
["system_command", "system_status"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Response Format
|
||||
All admin commands return JSON responses via WebSocket:
|
||||
|
||||
**Success Response:**
|
||||
```json
|
||||
["OK", "event_id", true, "success_message"]
|
||||
```
|
||||
|
||||
**Error Response:**
|
||||
```json
|
||||
["OK", "event_id", false, "error_message"]
|
||||
```
|
||||
|
||||
## Configuration Keys
|
||||
- `relay_description`: Relay description text
|
||||
- `relay_contact`: Contact information
|
||||
- `auth_enabled`: Enable authentication system
|
||||
- `max_connections`: Maximum concurrent connections
|
||||
- `pow_min_difficulty`: Minimum proof-of-work difficulty
|
||||
- ... (full list of config keys)
|
||||
|
||||
## Examples
|
||||
|
||||
### Enable Authentication & Add Blacklist
|
||||
```bash
|
||||
# 1. Enable auth system
|
||||
nak event -k 23455 --content "Enable authentication" \
|
||||
-t "auth_enabled=true" \
|
||||
--sec $ADMIN_PRIVKEY | nak event ws://localhost:8888
|
||||
|
||||
# 2. Add user to blacklist
|
||||
nak event -k 23456 --content '{"action":"add","description":"Spam user"}' \
|
||||
-t "blacklist=pubkey;$SPAM_USER_PUBKEY" \
|
||||
--sec $ADMIN_PRIVKEY | nak event ws://localhost:8888
|
||||
|
||||
# 3. Query all auth rules
|
||||
nak event -k 23456 --content '{"query":"list_auth_rules","description":"Get all rules"}' \
|
||||
-t "auth_query=all" \
|
||||
--sec $ADMIN_PRIVKEY | nak event ws://localhost:8888
|
||||
|
||||
# 4. Clear all rules for testing
|
||||
nak event -k 23456 --content '{"action":"clear_all","description":"Clear all rules"}' \
|
||||
-t "system_command=clear_all_auth_rules" \
|
||||
--sec $ADMIN_PRIVKEY | nak event ws://localhost:8888
|
||||
```
|
||||
|
||||
## Expected Response Formats
|
||||
|
||||
### Configuration Query Response
|
||||
```json
|
||||
["EVENT", "subscription_id", {
|
||||
"kind": 23455,
|
||||
"content": "{\"config_keys\": [\"auth_enabled\", \"max_connections\"], \"descriptions\": {\"auth_enabled\": \"Enable whitelist/blacklist rules\"}}",
|
||||
"tags": [["response_type", "config_keys_list"]]
|
||||
}]
|
||||
```
|
||||
|
||||
### Current Config Response
|
||||
```json
|
||||
["EVENT", "subscription_id", {
|
||||
"kind": 23455,
|
||||
"content": "{\"current_config\": {\"auth_enabled\": \"true\", \"max_connections\": \"1000\"}}",
|
||||
"tags": [["response_type", "current_config"]]
|
||||
}]
|
||||
```
|
||||
|
||||
### Auth Rules Query Response
|
||||
```json
|
||||
["EVENT", "subscription_id", {
|
||||
"kind": 23456,
|
||||
"content": "{\"auth_rules\": [{\"rule_type\": \"blacklist\", \"pattern_type\": \"pubkey\", \"pattern_value\": \"deadbeef...\"}, {\"rule_type\": \"whitelist\", \"pattern_type\": \"pubkey\", \"pattern_value\": \"cafebabe...\"}]}",
|
||||
"tags": [["response_type", "auth_rules_list"], ["query_type", "all"]]
|
||||
}]
|
||||
```
|
||||
|
||||
### Pattern Check Response
|
||||
```json
|
||||
["EVENT", "subscription_id", {
|
||||
"kind": 23456,
|
||||
"content": "{\"pattern_exists\": true, \"rule_type\": \"blacklist\", \"pattern_value\": \"deadbeef...\"}",
|
||||
"tags": [["response_type", "pattern_check"], ["pattern", "deadbeef..."]]
|
||||
}]
|
||||
```
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. **Document API** (this file) ✅
|
||||
2. **Update to ephemeral event kinds** ✅
|
||||
3. **Fix request_validator.c** schema mismatch
|
||||
4. **Update tests** to use Kind 23455/23456
|
||||
5. **Add auth rule query functionality**
|
||||
6. **Add configuration discovery feature**
|
||||
7. **Test blacklist functionality**
|
||||
8. **Add remaining system commands**
|
||||
|
||||
## Testing Plan
|
||||
|
||||
1. Fix schema mismatch and test basic blacklist
|
||||
2. Add clear_auth_rules and test table cleanup
|
||||
3. Test whitelist/blacklist conflict scenarios
|
||||
4. Test all admin API commands end-to-end
|
||||
5. Update integration tests
|
||||
|
||||
This plan addresses the immediate blacklist issue while establishing a comprehensive admin API framework for future expansion.
|
||||
|
||||
## NIP-44 Encryption Implementation Details
|
||||
|
||||
### Server-Side Detection Logic
|
||||
```c
|
||||
// In admin event processing function
|
||||
bool is_encrypted_command(struct nostr_event *event) {
|
||||
// Check if Kind 23455 or 23456 with empty tags
|
||||
if ((event->kind == 23455 || event->kind == 23456) &&
|
||||
event->tags_count == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
cJSON *decrypt_admin_tags(struct nostr_event *event) {
|
||||
cJSON *content_json = cJSON_Parse(event->content);
|
||||
if (!content_json) return NULL;
|
||||
|
||||
cJSON *encrypted_tags = cJSON_GetObjectItem(content_json, "encrypted_tags");
|
||||
if (!encrypted_tags) {
|
||||
cJSON_Delete(content_json);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Decrypt using NIP-44 with admin pubkey and relay privkey
|
||||
char *decrypted = nip44_decrypt(
|
||||
cJSON_GetStringValue(encrypted_tags),
|
||||
admin_pubkey, // Shared secret with admin
|
||||
relay_private_key // Our private key
|
||||
);
|
||||
|
||||
cJSON *decrypted_tags = cJSON_Parse(decrypted);
|
||||
free(decrypted);
|
||||
cJSON_Delete(content_json);
|
||||
|
||||
return decrypted_tags; // Returns tag array: [["key1", "val1"], ["key2", "val2"]]
|
||||
}
|
||||
```
|
||||
|
||||
### Admin Event Processing Flow
|
||||
1. **Receive Event**: Kind 23455/23456 with admin signature
|
||||
2. **Check Mode**: Empty tags = encrypted, populated tags = standard
|
||||
3. **Decrypt if Needed**: Extract and decrypt `encrypted_tags` from content
|
||||
4. **Process Commands**: Use decrypted/standard tags for command processing
|
||||
5. **Execute**: Same logic for both modes after tag extraction
|
||||
6. **Respond**: Standard response format (optionally encrypt response)
|
||||
|
||||
### Security Benefits
|
||||
- **Command Privacy**: Admin operations invisible in event tags
|
||||
- **Replay Protection**: NIP-44 includes timestamp/randomness
|
||||
- **Key Management**: Uses existing admin/relay key pair
|
||||
- **Backward Compatible**: Standard mode still works
|
||||
- **Performance**: Only decrypt when needed (empty tags detection)
|
||||
|
||||
### NIP-44 Library Integration
|
||||
The relay will need to integrate a NIP-44 encryption/decryption library:
|
||||
|
||||
```c
|
||||
// Required NIP-44 functions
|
||||
char* nip44_encrypt(const char* plaintext, const char* sender_privkey, const char* recipient_pubkey);
|
||||
char* nip44_decrypt(const char* ciphertext, const char* recipient_privkey, const char* sender_pubkey);
|
||||
```
|
||||
|
||||
### Implementation Priority (Updated)
|
||||
|
||||
#### Phase 1: Core Infrastructure (Complete)
|
||||
- [x] Event-based admin authentication system
|
||||
- [x] Kind 23455/23456 (Configuration/Auth Rules) processing
|
||||
- [x] Basic configuration parameter updates
|
||||
- [x] Auth rule add/remove/clear functionality
|
||||
- [x] Updated to ephemeral event kinds
|
||||
- [x] Designed NIP-44 encryption support
|
||||
|
||||
#### Phase 2: NIP-44 Encryption Support (Next Priority)
|
||||
- [ ] **Add NIP-44 library dependency** to project
|
||||
- [ ] **Implement encryption detection logic** (`is_encrypted_command()`)
|
||||
- [ ] **Add decrypt_admin_tags() function** with NIP-44 support
|
||||
- [ ] **Update admin command processing** to handle both modes
|
||||
- [ ] **Test encrypted admin commands** end-to-end
|
||||
|
||||
#### Phase 3: Enhanced Features
|
||||
- [ ] **Auth rule query functionality** (both standard and encrypted modes)
|
||||
- [ ] **Configuration discovery API** (list available config keys)
|
||||
- [ ] **Enhanced error messages** with encryption status
|
||||
- [ ] **Performance optimization** (caching, async decrypt)
|
||||
|
||||
#### Phase 4: Schema Fixes (Critical)
|
||||
- [ ] **Fix request_validator.c** schema mismatch
|
||||
- [ ] **Enable blacklist enforcement** with encrypted commands
|
||||
- [ ] **Update tests** to use both standard and encrypted modes
|
||||
|
||||
This enhanced admin API provides enterprise-grade security while maintaining ease of use for basic operations.
|
||||
586
src/config.c
586
src/config.c
@@ -73,6 +73,16 @@ int is_config_table_ready(void);
|
||||
int migrate_config_from_events_to_table(void);
|
||||
int populate_config_table_from_event(const cJSON* event);
|
||||
|
||||
// Forward declarations for admin API query handlers
|
||||
int handle_config_list_keys_query(cJSON* event, char* error_message, size_t error_size);
|
||||
int handle_config_get_current_query(cJSON* event, char* error_message, size_t error_size);
|
||||
int handle_auth_list_all_query(cJSON* event, char* error_message, size_t error_size);
|
||||
int handle_auth_whitelist_query(cJSON* event, char* error_message, size_t error_size);
|
||||
int handle_auth_blacklist_query(cJSON* event, char* error_message, size_t error_size);
|
||||
int handle_auth_pattern_check_query(cJSON* event, char* error_message, size_t error_size);
|
||||
int handle_clear_all_auth_rules_command(cJSON* event, char* error_message, size_t error_size);
|
||||
int handle_system_status_query(cJSON* event, char* error_message, size_t error_size);
|
||||
|
||||
// Current configuration cache
|
||||
static cJSON* g_current_config = NULL;
|
||||
|
||||
@@ -2055,7 +2065,7 @@ int add_pubkeys_to_config_table(void) {
|
||||
// ADMIN EVENT PROCESSING FUNCTIONS
|
||||
// ================================
|
||||
|
||||
// Process admin events (moved from main.c)
|
||||
// Process admin events (updated for new Kind 23455/23456)
|
||||
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size) {
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
if (!kind_obj || !cJSON_IsNumber(kind_obj)) {
|
||||
@@ -2081,26 +2091,64 @@ int process_admin_event_in_config(cJSON* event, char* error_message, size_t erro
|
||||
int kind = (int)cJSON_GetNumberValue(kind_obj);
|
||||
|
||||
switch (kind) {
|
||||
case 33334:
|
||||
case 23455: // New ephemeral configuration management
|
||||
return process_admin_config_event(event, error_message, error_size);
|
||||
case 33335:
|
||||
case 23456: // New ephemeral auth rules management
|
||||
return process_admin_auth_event(event, error_message, error_size);
|
||||
case 33334: // Legacy addressable config events (backward compatibility)
|
||||
return process_admin_config_event(event, error_message, error_size);
|
||||
case 33335: // Legacy addressable auth events (backward compatibility)
|
||||
return process_admin_auth_event(event, error_message, error_size);
|
||||
default:
|
||||
snprintf(error_message, error_size, "invalid: unsupported admin event kind");
|
||||
snprintf(error_message, error_size, "invalid: unsupported admin event kind %d", kind);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle kind 33334 config events
|
||||
// Handle Kind 23455 configuration management events and legacy Kind 33334
|
||||
int process_admin_config_event(cJSON* event, char* error_message, size_t error_size) {
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
int kind = kind_obj ? (int)cJSON_GetNumberValue(kind_obj) : 0;
|
||||
|
||||
// Parse content for action commands
|
||||
cJSON* content_obj = cJSON_GetObjectItem(event, "content");
|
||||
const char* content = content_obj ? cJSON_GetStringValue(content_obj) : "";
|
||||
|
||||
// Check if this is a query command
|
||||
cJSON* content_json = cJSON_Parse(content);
|
||||
char action_buffer[32] = "set"; // default action
|
||||
const char* action = action_buffer;
|
||||
|
||||
if (content_json) {
|
||||
cJSON* action_obj = cJSON_GetObjectItem(content_json, "action");
|
||||
if (action_obj && cJSON_IsString(action_obj)) {
|
||||
const char* action_str = cJSON_GetStringValue(action_obj);
|
||||
if (action_str) {
|
||||
strncpy(action_buffer, action_str, sizeof(action_buffer) - 1);
|
||||
action_buffer[sizeof(action_buffer) - 1] = '\0';
|
||||
}
|
||||
}
|
||||
cJSON_Delete(content_json);
|
||||
}
|
||||
|
||||
log_info("Processing admin configuration event");
|
||||
printf(" Kind: %d, Action: %s\n", kind, action);
|
||||
|
||||
// Handle query commands
|
||||
if (strcmp(action, "list_config_keys") == 0) {
|
||||
return handle_config_list_keys_query(event, error_message, error_size);
|
||||
}
|
||||
if (strcmp(action, "get_current_config") == 0) {
|
||||
return handle_config_get_current_query(event, error_message, error_size);
|
||||
}
|
||||
|
||||
// Handle configuration updates (set action)
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
if (!tags_obj || !cJSON_IsArray(tags_obj)) {
|
||||
snprintf(error_message, error_size, "invalid: configuration event must have tags");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Config table should already exist from embedded schema
|
||||
|
||||
// Begin transaction for atomic config updates
|
||||
int rc = sqlite3_exec(g_db, "BEGIN IMMEDIATE TRANSACTION", NULL, NULL, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
@@ -2127,8 +2175,8 @@ int process_admin_config_event(cJSON* event, char* error_message, size_t error_s
|
||||
const char* key = cJSON_GetStringValue(tag_name);
|
||||
const char* value = cJSON_GetStringValue(tag_value);
|
||||
|
||||
// Skip relay identifier tag
|
||||
if (strcmp(key, "d") == 0) {
|
||||
// Skip relay identifier tag (only for legacy addressable events)
|
||||
if (kind == 33334 && strcmp(key, "d") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2154,35 +2202,21 @@ int process_admin_config_event(cJSON* event, char* error_message, size_t error_s
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle kind 33335 auth rule events
|
||||
// Handle Kind 23456 auth rules management and legacy Kind 33335
|
||||
int process_admin_auth_event(cJSON* event, char* error_message, size_t error_size) {
|
||||
log_info("=== SERVER-SIDE AUTH RULE EVENT DEBUG ===");
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
int kind = kind_obj ? (int)cJSON_GetNumberValue(kind_obj) : 0;
|
||||
|
||||
// Print the entire received event for debugging
|
||||
char* debug_event_str = cJSON_Print(event);
|
||||
if (debug_event_str) {
|
||||
printf("Received Auth Event JSON: %s\n", debug_event_str);
|
||||
free(debug_event_str);
|
||||
}
|
||||
log_info("Processing admin auth rule event");
|
||||
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
if (!tags_obj || !cJSON_IsArray(tags_obj)) {
|
||||
log_error("Auth event missing or invalid tags array");
|
||||
snprintf(error_message, error_size, "invalid: auth rule event must have tags");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Tags array size: %d\n", cJSON_GetArraySize(tags_obj));
|
||||
|
||||
// Extract action from content or tags
|
||||
// Parse content for action commands
|
||||
cJSON* content_obj = cJSON_GetObjectItem(event, "content");
|
||||
const char* content = content_obj ? cJSON_GetStringValue(content_obj) : "";
|
||||
printf("Event content: '%s'\n", content);
|
||||
|
||||
// Parse the action from content (should be "add" or "remove")
|
||||
cJSON* content_json = cJSON_Parse(content);
|
||||
char action_buffer[16] = "add"; // Local buffer for action string
|
||||
const char* action = action_buffer; // default
|
||||
char action_buffer[32] = "add"; // default action
|
||||
const char* action = action_buffer;
|
||||
|
||||
if (content_json) {
|
||||
cJSON* action_obj = cJSON_GetObjectItem(content_json, "action");
|
||||
if (action_obj && cJSON_IsString(action_obj)) {
|
||||
@@ -2194,7 +2228,35 @@ int process_admin_auth_event(cJSON* event, char* error_message, size_t error_siz
|
||||
}
|
||||
cJSON_Delete(content_json);
|
||||
}
|
||||
printf("Parsed action: '%s'\n", action);
|
||||
|
||||
printf(" Kind: %d, Action: %s\n", kind, action);
|
||||
|
||||
// Handle query commands
|
||||
if (strcmp(action, "list_all") == 0) {
|
||||
return handle_auth_list_all_query(event, error_message, error_size);
|
||||
}
|
||||
if (strcmp(action, "whitelist_only") == 0) {
|
||||
return handle_auth_whitelist_query(event, error_message, error_size);
|
||||
}
|
||||
if (strcmp(action, "blacklist_only") == 0) {
|
||||
return handle_auth_blacklist_query(event, error_message, error_size);
|
||||
}
|
||||
if (strcmp(action, "pattern_check") == 0) {
|
||||
return handle_auth_pattern_check_query(event, error_message, error_size);
|
||||
}
|
||||
if (strcmp(action, "clear_all_auth_rules") == 0) {
|
||||
return handle_clear_all_auth_rules_command(event, error_message, error_size);
|
||||
}
|
||||
if (strcmp(action, "system_status") == 0) {
|
||||
return handle_system_status_query(event, error_message, error_size);
|
||||
}
|
||||
|
||||
// Handle auth rule updates (add/remove actions)
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
if (!tags_obj || !cJSON_IsArray(tags_obj)) {
|
||||
snprintf(error_message, error_size, "invalid: auth rule event must have tags");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Begin transaction for atomic auth rule updates
|
||||
int rc = sqlite3_exec(g_db, "BEGIN IMMEDIATE TRANSACTION", NULL, NULL, NULL);
|
||||
@@ -2204,33 +2266,11 @@ int process_admin_auth_event(cJSON* event, char* error_message, size_t error_siz
|
||||
}
|
||||
|
||||
int rules_processed = 0;
|
||||
int tags_examined = 0;
|
||||
int tags_skipped = 0;
|
||||
|
||||
// Process each tag as an auth rule specification
|
||||
cJSON* tag = NULL;
|
||||
cJSON_ArrayForEach(tag, tags_obj) {
|
||||
tags_examined++;
|
||||
|
||||
printf("Examining tag #%d:\n", tags_examined);
|
||||
char* tag_debug_str = cJSON_Print(tag);
|
||||
if (tag_debug_str) {
|
||||
printf(" Tag JSON: %s\n", tag_debug_str);
|
||||
free(tag_debug_str);
|
||||
}
|
||||
|
||||
if (!cJSON_IsArray(tag)) {
|
||||
printf(" SKIPPED: Not an array\n");
|
||||
tags_skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int tag_size = cJSON_GetArraySize(tag);
|
||||
printf(" Tag array size: %d\n", tag_size);
|
||||
|
||||
if (tag_size < 3) {
|
||||
printf(" SKIPPED: Array size < 3 (need at least 3 elements for auth rules)\n");
|
||||
tags_skipped++;
|
||||
if (!cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2241,8 +2281,6 @@ int process_admin_auth_event(cJSON* event, char* error_message, size_t error_siz
|
||||
if (!cJSON_IsString(rule_type_obj) ||
|
||||
!cJSON_IsString(pattern_type_obj) ||
|
||||
!cJSON_IsString(pattern_value_obj)) {
|
||||
printf(" SKIPPED: One or more elements are not strings\n");
|
||||
tags_skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2250,33 +2288,18 @@ int process_admin_auth_event(cJSON* event, char* error_message, size_t error_siz
|
||||
const char* pattern_type = cJSON_GetStringValue(pattern_type_obj);
|
||||
const char* pattern_value = cJSON_GetStringValue(pattern_value_obj);
|
||||
|
||||
printf(" Extracted rule: type='%s', pattern_type='%s', pattern_value='%s'\n",
|
||||
rule_type, pattern_type, pattern_value);
|
||||
|
||||
// Process the auth rule based on action
|
||||
if (strcmp(action, "add") == 0) {
|
||||
printf(" Attempting to add rule to database...\n");
|
||||
if (add_auth_rule_from_config(rule_type, pattern_type, pattern_value, "allow") == 0) {
|
||||
printf(" SUCCESS: Rule added to database\n");
|
||||
rules_processed++;
|
||||
} else {
|
||||
printf(" FAILED: Could not add rule to database\n");
|
||||
}
|
||||
} else if (strcmp(action, "remove") == 0) {
|
||||
printf(" Attempting to remove rule from database...\n");
|
||||
if (remove_auth_rule_from_config(rule_type, pattern_type, pattern_value) == 0) {
|
||||
printf(" SUCCESS: Rule removed from database\n");
|
||||
rules_processed++;
|
||||
} else {
|
||||
printf(" FAILED: Could not remove rule from database\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("Processing summary: examined=%d, skipped=%d, processed=%d\n",
|
||||
tags_examined, tags_skipped, rules_processed);
|
||||
log_info("=== END SERVER-SIDE AUTH RULE EVENT DEBUG ===");
|
||||
|
||||
if (rules_processed > 0) {
|
||||
sqlite3_exec(g_db, "COMMIT", NULL, NULL, NULL);
|
||||
|
||||
@@ -2348,6 +2371,425 @@ int remove_auth_rule_from_config(const char* rule_type, const char* pattern_type
|
||||
return (rc == SQLITE_DONE) ? 0 : -1;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// ADMIN API QUERY HANDLERS
|
||||
// ================================
|
||||
|
||||
// Handle configuration list keys query
|
||||
int handle_config_list_keys_query(cJSON* event, char* error_message, size_t error_size) {
|
||||
(void)event; // Suppress unused parameter warning
|
||||
|
||||
if (!g_db) {
|
||||
snprintf(error_message, error_size, "database not available");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing config list keys query");
|
||||
|
||||
const char* sql = "SELECT key, data_type, category FROM config ORDER BY category, key";
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
snprintf(error_message, error_size, "failed to prepare config keys query");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("=== Configuration Keys ===\n");
|
||||
int key_count = 0;
|
||||
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* key = (const char*)sqlite3_column_text(stmt, 0);
|
||||
const char* data_type = (const char*)sqlite3_column_text(stmt, 1);
|
||||
const char* category = (const char*)sqlite3_column_text(stmt, 2);
|
||||
|
||||
printf(" [%s] %s (%s)\n", category ? category : "general", key ? key : "", data_type ? data_type : "string");
|
||||
key_count++;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
printf("Total configuration keys: %d\n", key_count);
|
||||
log_success("Configuration keys listed successfully");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle get current configuration query
|
||||
int handle_config_get_current_query(cJSON* event, char* error_message, size_t error_size) {
|
||||
(void)event; // Suppress unused parameter warning
|
||||
|
||||
if (!g_db) {
|
||||
snprintf(error_message, error_size, "database not available");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing get current config query");
|
||||
|
||||
const char* sql = "SELECT key, value, data_type, category FROM config ORDER BY category, key";
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
snprintf(error_message, error_size, "failed to prepare current config query");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("=== Current Configuration ===\n");
|
||||
int config_count = 0;
|
||||
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* key = (const char*)sqlite3_column_text(stmt, 0);
|
||||
const char* value = (const char*)sqlite3_column_text(stmt, 1);
|
||||
const char* data_type = (const char*)sqlite3_column_text(stmt, 2);
|
||||
const char* category = (const char*)sqlite3_column_text(stmt, 3);
|
||||
|
||||
printf(" [%s] %s = %s (%s)\n",
|
||||
category ? category : "general",
|
||||
key ? key : "",
|
||||
value ? value : "",
|
||||
data_type ? data_type : "string");
|
||||
config_count++;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
printf("Total configuration items: %d\n", config_count);
|
||||
log_success("Current configuration retrieved successfully");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle auth rules list all query
|
||||
int handle_auth_list_all_query(cJSON* event, char* error_message, size_t error_size) {
|
||||
(void)event; // Suppress unused parameter warning
|
||||
|
||||
if (!g_db) {
|
||||
snprintf(error_message, error_size, "database not available");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing auth rules list all query");
|
||||
|
||||
const char* sql = "SELECT rule_type, pattern_type, pattern_value, action FROM auth_rules ORDER BY rule_type, pattern_type";
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
snprintf(error_message, error_size, "failed to prepare auth rules query");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("=== All Auth Rules ===\n");
|
||||
int rule_count = 0;
|
||||
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* rule_type = (const char*)sqlite3_column_text(stmt, 0);
|
||||
const char* pattern_type = (const char*)sqlite3_column_text(stmt, 1);
|
||||
const char* pattern_value = (const char*)sqlite3_column_text(stmt, 2);
|
||||
const char* action = (const char*)sqlite3_column_text(stmt, 3);
|
||||
|
||||
printf(" %s %s:%s -> %s\n",
|
||||
rule_type ? rule_type : "",
|
||||
pattern_type ? pattern_type : "",
|
||||
pattern_value ? pattern_value : "",
|
||||
action ? action : "allow");
|
||||
rule_count++;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
printf("Total auth rules: %d\n", rule_count);
|
||||
log_success("Auth rules listed successfully");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle whitelist only query
|
||||
int handle_auth_whitelist_query(cJSON* event, char* error_message, size_t error_size) {
|
||||
(void)event; // Suppress unused parameter warning
|
||||
|
||||
if (!g_db) {
|
||||
snprintf(error_message, error_size, "database not available");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing whitelist rules query");
|
||||
|
||||
const char* sql = "SELECT rule_type, pattern_type, pattern_value, action FROM auth_rules WHERE rule_type LIKE '%whitelist%' ORDER BY pattern_type";
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
snprintf(error_message, error_size, "failed to prepare whitelist query");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("=== Whitelist Rules ===\n");
|
||||
int whitelist_count = 0;
|
||||
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* rule_type = (const char*)sqlite3_column_text(stmt, 0);
|
||||
const char* pattern_type = (const char*)sqlite3_column_text(stmt, 1);
|
||||
const char* pattern_value = (const char*)sqlite3_column_text(stmt, 2);
|
||||
const char* action = (const char*)sqlite3_column_text(stmt, 3);
|
||||
|
||||
printf(" %s %s:%s -> %s\n",
|
||||
rule_type ? rule_type : "",
|
||||
pattern_type ? pattern_type : "",
|
||||
pattern_value ? pattern_value : "",
|
||||
action ? action : "allow");
|
||||
whitelist_count++;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
printf("Total whitelist rules: %d\n", whitelist_count);
|
||||
log_success("Whitelist rules listed successfully");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle blacklist only query
|
||||
int handle_auth_blacklist_query(cJSON* event, char* error_message, size_t error_size) {
|
||||
(void)event; // Suppress unused parameter warning
|
||||
|
||||
if (!g_db) {
|
||||
snprintf(error_message, error_size, "database not available");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing blacklist rules query");
|
||||
|
||||
const char* sql = "SELECT rule_type, pattern_type, pattern_value, action FROM auth_rules WHERE rule_type LIKE '%blacklist%' ORDER BY pattern_type";
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
snprintf(error_message, error_size, "failed to prepare blacklist query");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("=== Blacklist Rules ===\n");
|
||||
int blacklist_count = 0;
|
||||
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* rule_type = (const char*)sqlite3_column_text(stmt, 0);
|
||||
const char* pattern_type = (const char*)sqlite3_column_text(stmt, 1);
|
||||
const char* pattern_value = (const char*)sqlite3_column_text(stmt, 2);
|
||||
const char* action = (const char*)sqlite3_column_text(stmt, 3);
|
||||
|
||||
printf(" %s %s:%s -> %s\n",
|
||||
rule_type ? rule_type : "",
|
||||
pattern_type ? pattern_type : "",
|
||||
pattern_value ? pattern_value : "",
|
||||
action ? action : "deny");
|
||||
blacklist_count++;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
printf("Total blacklist rules: %d\n", blacklist_count);
|
||||
log_success("Blacklist rules listed successfully");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle pattern check query
|
||||
int handle_auth_pattern_check_query(cJSON* event, char* error_message, size_t error_size) {
|
||||
// Parse tags to get the pattern to check
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
if (!tags_obj || !cJSON_IsArray(tags_obj)) {
|
||||
snprintf(error_message, error_size, "invalid: pattern check requires tags with pattern information");
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* check_pattern_type = NULL;
|
||||
const char* check_pattern_value = NULL;
|
||||
|
||||
// Find pattern_check tag
|
||||
cJSON* tag = NULL;
|
||||
cJSON_ArrayForEach(tag, tags_obj) {
|
||||
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 3) {
|
||||
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
|
||||
if (tag_name && cJSON_IsString(tag_name) &&
|
||||
strcmp(cJSON_GetStringValue(tag_name), "pattern_check") == 0) {
|
||||
|
||||
cJSON* pattern_type_obj = cJSON_GetArrayItem(tag, 1);
|
||||
cJSON* pattern_value_obj = cJSON_GetArrayItem(tag, 2);
|
||||
|
||||
if (pattern_type_obj && cJSON_IsString(pattern_type_obj) &&
|
||||
pattern_value_obj && cJSON_IsString(pattern_value_obj)) {
|
||||
check_pattern_type = cJSON_GetStringValue(pattern_type_obj);
|
||||
check_pattern_value = cJSON_GetStringValue(pattern_value_obj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!check_pattern_type || !check_pattern_value) {
|
||||
snprintf(error_message, error_size, "invalid: pattern_check tag format should be [\"pattern_check\", \"pattern_type\", \"pattern_value\"]");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!g_db) {
|
||||
snprintf(error_message, error_size, "database not available");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing pattern check query");
|
||||
printf(" Checking pattern: %s:%s\n", check_pattern_type, check_pattern_value);
|
||||
|
||||
const char* sql = "SELECT rule_type, pattern_type, pattern_value, action FROM auth_rules WHERE pattern_type = ? AND pattern_value = ? ORDER BY rule_type";
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
snprintf(error_message, error_size, "failed to prepare pattern check query");
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, check_pattern_type, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, check_pattern_value, -1, SQLITE_STATIC);
|
||||
|
||||
printf("=== Pattern Check Results ===\n");
|
||||
printf("Pattern: %s:%s\n", check_pattern_type, check_pattern_value);
|
||||
int match_count = 0;
|
||||
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* rule_type = (const char*)sqlite3_column_text(stmt, 0);
|
||||
const char* pattern_type = (const char*)sqlite3_column_text(stmt, 1);
|
||||
const char* pattern_value = (const char*)sqlite3_column_text(stmt, 2);
|
||||
const char* action = (const char*)sqlite3_column_text(stmt, 3);
|
||||
|
||||
printf(" MATCH: %s %s:%s -> %s\n",
|
||||
rule_type ? rule_type : "",
|
||||
pattern_type ? pattern_type : "",
|
||||
pattern_value ? pattern_value : "",
|
||||
action ? action : "allow");
|
||||
match_count++;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if (match_count == 0) {
|
||||
printf(" No matching rules found for this pattern\n");
|
||||
}
|
||||
|
||||
printf("Total matches: %d\n", match_count);
|
||||
log_success("Pattern check completed successfully");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle clear all auth rules command
|
||||
int handle_clear_all_auth_rules_command(cJSON* event, char* error_message, size_t error_size) {
|
||||
(void)event; // Suppress unused parameter warning
|
||||
|
||||
if (!g_db) {
|
||||
snprintf(error_message, error_size, "database not available");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing clear all auth rules command");
|
||||
|
||||
// Count existing rules first
|
||||
const char* count_sql = "SELECT COUNT(*) FROM auth_rules";
|
||||
sqlite3_stmt* count_stmt;
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_db, count_sql, -1, &count_stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
snprintf(error_message, error_size, "failed to prepare count query");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rule_count = 0;
|
||||
if (sqlite3_step(count_stmt) == SQLITE_ROW) {
|
||||
rule_count = sqlite3_column_int(count_stmt, 0);
|
||||
}
|
||||
sqlite3_finalize(count_stmt);
|
||||
|
||||
// Delete all auth rules (this operation succeeds even if table is empty)
|
||||
const char* delete_sql = "DELETE FROM auth_rules";
|
||||
rc = sqlite3_exec(g_db, delete_sql, NULL, NULL, NULL);
|
||||
|
||||
if (rc != SQLITE_OK) {
|
||||
snprintf(error_message, error_size, "failed to execute clear auth rules command");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Always return success - clearing empty table is still a successful operation
|
||||
if (rule_count > 0) {
|
||||
printf("Cleared %d auth rules from database\n", rule_count);
|
||||
log_success("All auth rules cleared successfully");
|
||||
} else {
|
||||
printf("Auth rules table was already empty - no rules to clear\n");
|
||||
log_success("Clear auth rules completed successfully (table was already empty)");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle system status query
|
||||
int handle_system_status_query(cJSON* event, char* error_message, size_t error_size) {
|
||||
(void)event; // Suppress unused parameter warning
|
||||
(void)error_message; // This command always succeeds
|
||||
(void)error_size;
|
||||
|
||||
log_info("Processing system status query");
|
||||
|
||||
printf("=== System Status ===\n");
|
||||
|
||||
// Database status
|
||||
printf("Database: %s\n", g_db ? "Connected" : "Not available");
|
||||
if (strlen(g_database_path) > 0) {
|
||||
printf("Database path: %s\n", g_database_path);
|
||||
}
|
||||
|
||||
// Configuration status
|
||||
printf("Unified cache: %s\n", g_unified_cache.cache_valid ? "Valid" : "Invalid");
|
||||
printf("Admin pubkey: %s\n", g_unified_cache.admin_pubkey[0] ? g_unified_cache.admin_pubkey : "Not set");
|
||||
printf("Relay pubkey: %s\n", g_unified_cache.relay_pubkey[0] ? g_unified_cache.relay_pubkey : "Not set");
|
||||
|
||||
// Count configuration items
|
||||
if (g_db) {
|
||||
const char* config_count_sql = "SELECT COUNT(*) FROM config";
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, config_count_sql, -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int config_count = sqlite3_column_int(stmt, 0);
|
||||
printf("Configuration items: %d\n", config_count);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
// Count auth rules
|
||||
const char* auth_count_sql = "SELECT COUNT(*) FROM auth_rules";
|
||||
if (sqlite3_prepare_v2(g_db, auth_count_sql, -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int auth_count = sqlite3_column_int(stmt, 0);
|
||||
printf("Auth rules: %d\n", auth_count);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache expiration
|
||||
if (g_unified_cache.cache_expires > 0) {
|
||||
time_t now = time(NULL);
|
||||
long seconds_to_expire = g_unified_cache.cache_expires - now;
|
||||
printf("Cache expires in: %ld seconds\n", seconds_to_expire);
|
||||
}
|
||||
|
||||
printf("System time: %ld\n", (long)time(NULL));
|
||||
|
||||
log_success("System status retrieved successfully");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// CONFIGURATION CACHE MANAGEMENT
|
||||
// ================================
|
||||
|
||||
41
src/main.c
41
src/main.c
@@ -972,28 +972,28 @@ static void get_timestamp_string(char* buffer, size_t buffer_size) {
|
||||
void log_info(const char* message) {
|
||||
char timestamp[32];
|
||||
get_timestamp_string(timestamp, sizeof(timestamp));
|
||||
printf("[%s] " BLUE "[INFO]" RESET " %s\n", timestamp, message);
|
||||
printf("[%s] [INFO] %s\n", timestamp, message);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void log_success(const char* message) {
|
||||
char timestamp[32];
|
||||
get_timestamp_string(timestamp, sizeof(timestamp));
|
||||
printf("[%s] " GREEN "[SUCCESS]" RESET " %s\n", timestamp, message);
|
||||
printf("[%s] [SUCCESS] %s\n", timestamp, message);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void log_error(const char* message) {
|
||||
char timestamp[32];
|
||||
get_timestamp_string(timestamp, sizeof(timestamp));
|
||||
printf("[%s] " RED "[ERROR]" RESET " %s\n", timestamp, message);
|
||||
printf("[%s] [ERROR] %s\n", timestamp, message);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void log_warning(const char* message) {
|
||||
char timestamp[32];
|
||||
get_timestamp_string(timestamp, sizeof(timestamp));
|
||||
printf("[%s] " YELLOW "[WARNING]" RESET " %s\n", timestamp, message);
|
||||
printf("[%s] [WARNING] %s\n", timestamp, message);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
@@ -3243,7 +3243,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// Cleanup event JSON string
|
||||
free(event_json_str);
|
||||
|
||||
// Check for admin events (kinds 33334 and 33335) and intercept them
|
||||
// Check for admin events (kinds 33334, 33335, 23455, and 23456) and intercept them
|
||||
if (result == 0) {
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
if (kind_obj && cJSON_IsNumber(kind_obj)) {
|
||||
@@ -3251,7 +3251,21 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
|
||||
log_info("DEBUG ADMIN: Checking if admin event processing is needed");
|
||||
|
||||
if (event_kind == 33334 || event_kind == 33335) {
|
||||
// Log reception of Kind 23455 and 23456 events
|
||||
if (event_kind == 23455 || event_kind == 23456) {
|
||||
char* event_json_debug = cJSON_Print(event);
|
||||
char debug_received_msg[1024];
|
||||
snprintf(debug_received_msg, sizeof(debug_received_msg),
|
||||
"RECEIVED Kind %d event: %s", event_kind,
|
||||
event_json_debug ? event_json_debug : "Failed to serialize");
|
||||
log_info(debug_received_msg);
|
||||
|
||||
if (event_json_debug) {
|
||||
free(event_json_debug);
|
||||
}
|
||||
}
|
||||
|
||||
if (event_kind == 33334 || event_kind == 33335 || event_kind == 23455 || event_kind == 23456) {
|
||||
// This is an admin event - process it through the admin API instead of normal storage
|
||||
log_info("DEBUG ADMIN: Admin event detected, processing through admin API");
|
||||
|
||||
@@ -3263,6 +3277,21 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
"DEBUG ADMIN: process_admin_event_in_config returned %d", admin_result);
|
||||
log_info(debug_admin_msg);
|
||||
|
||||
// Log results for Kind 23455 and 23456 events
|
||||
if (event_kind == 23455 || event_kind == 23456) {
|
||||
if (admin_result == 0) {
|
||||
char success_result_msg[256];
|
||||
snprintf(success_result_msg, sizeof(success_result_msg),
|
||||
"SUCCESS: Kind %d event processed successfully", event_kind);
|
||||
log_success(success_result_msg);
|
||||
} else {
|
||||
char error_result_msg[512];
|
||||
snprintf(error_result_msg, sizeof(error_result_msg),
|
||||
"ERROR: Kind %d event processing failed: %s", event_kind, admin_error);
|
||||
log_error(error_result_msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (admin_result != 0) {
|
||||
log_error("DEBUG ADMIN: Failed to process admin event through admin API");
|
||||
result = -1;
|
||||
|
||||
@@ -629,28 +629,26 @@ static int check_database_auth_rules(const char *pubkey, const char *operation,
|
||||
|
||||
// Step 1: Check pubkey blacklist (highest priority)
|
||||
const char *blacklist_sql =
|
||||
"SELECT rule_type, description FROM auth_rules WHERE rule_type = "
|
||||
"'pubkey_blacklist' AND rule_target = ? AND operation = ? AND enabled = "
|
||||
"1 ORDER BY priority LIMIT 1";
|
||||
"SELECT rule_type, action FROM auth_rules WHERE rule_type = "
|
||||
"'blacklist' AND pattern_type = 'pubkey' AND pattern_value = ? LIMIT 1";
|
||||
rc = sqlite3_prepare_v2(db, blacklist_sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, operation ? operation : "", -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char *description = (const char *)sqlite3_column_text(stmt, 1);
|
||||
const char *action = (const char *)sqlite3_column_text(stmt, 1);
|
||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 1 FAILED - "
|
||||
"Pubkey blacklisted\n");
|
||||
char blacklist_msg[256];
|
||||
sprintf(blacklist_msg,
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Blacklist rule matched: %s\n",
|
||||
description ? description : "Unknown");
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Blacklist rule matched: action=%s\n",
|
||||
action ? action : "deny");
|
||||
validator_debug_log(blacklist_msg);
|
||||
|
||||
// Set specific violation details for status code mapping
|
||||
strcpy(g_last_rule_violation.violation_type, "pubkey_blacklist");
|
||||
sprintf(g_last_rule_violation.reason, "%s: Public key blacklisted",
|
||||
description ? description : "TEST_PUBKEY_BLACKLIST");
|
||||
sprintf(g_last_rule_violation.reason, "Public key blacklisted: %s",
|
||||
action ? action : "PUBKEY_BLACKLIST");
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_close(db);
|
||||
@@ -664,29 +662,27 @@ static int check_database_auth_rules(const char *pubkey, const char *operation,
|
||||
// Step 2: Check hash blacklist
|
||||
if (resource_hash) {
|
||||
const char *hash_blacklist_sql =
|
||||
"SELECT rule_type, description FROM auth_rules WHERE rule_type = "
|
||||
"'hash_blacklist' AND rule_target = ? AND operation = ? AND enabled = "
|
||||
"1 ORDER BY priority LIMIT 1";
|
||||
"SELECT rule_type, action FROM auth_rules WHERE rule_type = "
|
||||
"'blacklist' AND pattern_type = 'hash' AND pattern_value = ? LIMIT 1";
|
||||
rc = sqlite3_prepare_v2(db, hash_blacklist_sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, resource_hash, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, operation ? operation : "", -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char *description = (const char *)sqlite3_column_text(stmt, 1);
|
||||
const char *action = (const char *)sqlite3_column_text(stmt, 1);
|
||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 2 FAILED - "
|
||||
"Hash blacklisted\n");
|
||||
char hash_blacklist_msg[256];
|
||||
sprintf(
|
||||
hash_blacklist_msg,
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Hash blacklist rule matched: %s\n",
|
||||
description ? description : "Unknown");
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Hash blacklist rule matched: action=%s\n",
|
||||
action ? action : "deny");
|
||||
validator_debug_log(hash_blacklist_msg);
|
||||
|
||||
// Set specific violation details for status code mapping
|
||||
strcpy(g_last_rule_violation.violation_type, "hash_blacklist");
|
||||
sprintf(g_last_rule_violation.reason, "%s: File hash blacklisted",
|
||||
description ? description : "TEST_HASH_BLACKLIST");
|
||||
sprintf(g_last_rule_violation.reason, "File hash blacklisted: %s",
|
||||
action ? action : "HASH_BLACKLIST");
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_close(db);
|
||||
@@ -703,22 +699,20 @@ static int check_database_auth_rules(const char *pubkey, const char *operation,
|
||||
|
||||
// Step 3: Check pubkey whitelist
|
||||
const char *whitelist_sql =
|
||||
"SELECT rule_type, description FROM auth_rules WHERE rule_type = "
|
||||
"'pubkey_whitelist' AND rule_target = ? AND operation = ? AND enabled = "
|
||||
"1 ORDER BY priority LIMIT 1";
|
||||
"SELECT rule_type, action FROM auth_rules WHERE rule_type = "
|
||||
"'whitelist' AND pattern_type = 'pubkey' AND pattern_value = ? LIMIT 1";
|
||||
rc = sqlite3_prepare_v2(db, whitelist_sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, operation ? operation : "", -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char *description = (const char *)sqlite3_column_text(stmt, 1);
|
||||
const char *action = (const char *)sqlite3_column_text(stmt, 1);
|
||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 3 PASSED - "
|
||||
"Pubkey whitelisted\n");
|
||||
char whitelist_msg[256];
|
||||
sprintf(whitelist_msg,
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Whitelist rule matched: %s\n",
|
||||
description ? description : "Unknown");
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Whitelist rule matched: action=%s\n",
|
||||
action ? action : "allow");
|
||||
validator_debug_log(whitelist_msg);
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_close(db);
|
||||
@@ -731,12 +725,10 @@ static int check_database_auth_rules(const char *pubkey, const char *operation,
|
||||
|
||||
// Step 4: Check if any whitelist rules exist - if yes, deny by default
|
||||
const char *whitelist_exists_sql =
|
||||
"SELECT COUNT(*) FROM auth_rules WHERE rule_type = 'pubkey_whitelist' "
|
||||
"AND operation = ? AND enabled = 1 LIMIT 1";
|
||||
"SELECT COUNT(*) FROM auth_rules WHERE rule_type = 'whitelist' "
|
||||
"AND pattern_type = 'pubkey' LIMIT 1";
|
||||
rc = sqlite3_prepare_v2(db, whitelist_exists_sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, operation ? operation : "", -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int whitelist_count = sqlite3_column_int(stmt, 0);
|
||||
if (whitelist_count > 0) {
|
||||
|
||||
@@ -20,19 +20,18 @@ set -e # Exit on any error
|
||||
# CONFIGURATION
|
||||
# =======================================================================
|
||||
|
||||
# Test mode credentials (provided by user)
|
||||
# Test mode credentials (from current relay startup)
|
||||
ADMIN_PRIVKEY="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
ADMIN_PUBKEY="6a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3"
|
||||
RELAY_PUBKEY="4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa"
|
||||
|
||||
# Server configuration
|
||||
RELAY_HOST="localhost"
|
||||
RELAY_HOST="127.0.0.1"
|
||||
RELAY_PORT="8888"
|
||||
RELAY_URL="ws://${RELAY_HOST}:${RELAY_PORT}"
|
||||
|
||||
# Test configuration
|
||||
TIMEOUT=5
|
||||
LOG_FILE="whitelist_blacklist_test.log"
|
||||
TEMP_DIR="/tmp/c_relay_test_$$"
|
||||
|
||||
# Color codes for output
|
||||
@@ -53,23 +52,23 @@ TESTS_FAILED=0
|
||||
# =======================================================================
|
||||
|
||||
log() {
|
||||
echo -e "${BLUE}[$(date '+%H:%M:%S')]${RESET} $1" | tee -a "$LOG_FILE"
|
||||
echo -e "${BLUE}[$(date '+%H:%M:%S')]${RESET} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${RESET} $1" | tee -a "$LOG_FILE"
|
||||
echo -e "${GREEN}[SUCCESS]${RESET} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${RESET} $1" | tee -a "$LOG_FILE"
|
||||
echo -e "${RED}[ERROR]${RESET} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${RESET} $1" | tee -a "$LOG_FILE"
|
||||
echo -e "${YELLOW}[WARNING]${RESET} $1"
|
||||
}
|
||||
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${RESET} $1" | tee -a "$LOG_FILE"
|
||||
echo -e "${BLUE}[INFO]${RESET} $1"
|
||||
}
|
||||
|
||||
increment_test() {
|
||||
@@ -79,11 +78,15 @@ increment_test() {
|
||||
pass_test() {
|
||||
TESTS_PASSED=$((TESTS_PASSED + 1))
|
||||
log_success "Test $TESTS_RUN: PASSED - $1"
|
||||
echo ""
|
||||
echo ""
|
||||
}
|
||||
|
||||
fail_test() {
|
||||
TESTS_FAILED=$((TESTS_FAILED + 1))
|
||||
log_error "Test $TESTS_RUN: FAILED - $1"
|
||||
echo ""
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Generate test keypairs
|
||||
@@ -123,14 +126,21 @@ send_websocket_message() {
|
||||
local expected_response="$2"
|
||||
local timeout="${3:-$TIMEOUT}"
|
||||
|
||||
log_info "Sending WebSocket message: ${message:0:100}..."
|
||||
|
||||
# Use wscat to send message and capture response
|
||||
# Use websocat to send message and capture response (following pattern from tests/1_nip_test.sh)
|
||||
local response=""
|
||||
if command -v wscat &> /dev/null; then
|
||||
response=$(echo "$message" | timeout "$timeout" wscat -c "$RELAY_URL" 2>/dev/null | head -1)
|
||||
if command -v websocat &> /dev/null; then
|
||||
# Capture output from websocat (following working pattern from 1_nip_test.sh)
|
||||
response=$(echo "$message" | timeout "$timeout" websocat "$RELAY_URL" 2>&1 || echo "Connection failed")
|
||||
|
||||
# Check if connection failed
|
||||
if [[ "$response" == *"Connection failed"* ]]; then
|
||||
log_error "Failed to connect to relay"
|
||||
return 1
|
||||
fi
|
||||
|
||||
else
|
||||
log_error "wscat not found - required for WebSocket testing"
|
||||
log_error "websocat not found - required for WebSocket testing"
|
||||
log_error "Please install websocat for WebSocket communication"
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -147,13 +157,12 @@ send_auth_rule_event() {
|
||||
|
||||
log_info "Creating auth rule event: $action $rule_type $pattern_type ${pattern_value:0:16}..."
|
||||
|
||||
# Create the auth rule event using nak - match the working NIP-42 pattern
|
||||
# Create the auth rule event using nak with correct tag format
|
||||
# Server expects proper key=value tags for auth rules
|
||||
# Using Kind 23456 (ephemeral auth rules management) - no d tag needed
|
||||
local event_json
|
||||
event_json=$(nak event -k 33335 --content "{\"action\":\"$action\",\"description\":\"$description\"}" \
|
||||
-t "d=$RELAY_PUBKEY" \
|
||||
-t "$rule_type=$pattern_type" \
|
||||
-t "pattern=$pattern_value" \
|
||||
-t "action=$action" \
|
||||
event_json=$(nak event -k 23456 --content "{\"action\":\"$action\",\"description\":\"$description\"}" \
|
||||
-t "$rule_type=$pattern_type" -t "pattern_value=$pattern_value" \
|
||||
--sec "$ADMIN_PRIVKEY" 2>/dev/null)
|
||||
|
||||
if [ $? -ne 0 ] || [ -z "$event_json" ]; then
|
||||
@@ -161,7 +170,7 @@ send_auth_rule_event() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Send the event using nak directly to relay (more reliable than wscat)
|
||||
# Send the event using nak directly to relay (more reliable than websocat)
|
||||
log_info "Publishing auth rule event to relay..."
|
||||
local result
|
||||
result=$(echo "$event_json" | timeout 10s nak event "$RELAY_URL" 2>&1)
|
||||
@@ -179,6 +188,40 @@ send_auth_rule_event() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Clear all auth rules using the new system command functionality
|
||||
clear_all_auth_rules() {
|
||||
log_info "Clearing all existing auth rules..."
|
||||
|
||||
# Create system command event to clear all auth rules
|
||||
# Using Kind 23456 (ephemeral auth rules management)
|
||||
local event_json
|
||||
event_json=$(nak event -k 23456 --content "{\"action\":\"clear_all\"}" \
|
||||
-t "system_command=clear_all_auth_rules" \
|
||||
--sec "$ADMIN_PRIVKEY" 2>/dev/null)
|
||||
|
||||
if [ $? -ne 0 ] || [ -z "$event_json" ]; then
|
||||
log_error "Failed to create clear auth rules event with nak"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Send the event using nak directly to relay
|
||||
log_info "Sending clear all auth rules command..."
|
||||
local result
|
||||
result=$(echo "$event_json" | timeout 10s nak event "$RELAY_URL" 2>&1)
|
||||
local exit_code=$?
|
||||
|
||||
log_info "Clear auth rules result: $result"
|
||||
|
||||
# Check if response indicates success
|
||||
if [ $exit_code -eq 0 ] && echo "$result" | grep -q -i "success\|OK.*true\|published"; then
|
||||
log_success "All auth rules cleared successfully"
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to clear auth rules: $result (exit code: $exit_code)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test event publishing with a specific key
|
||||
test_event_publishing() {
|
||||
local test_privkey="$1"
|
||||
@@ -198,7 +241,7 @@ test_event_publishing() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Send the event using nak directly (more reliable than wscat)
|
||||
# Send the event using nak directly (more reliable than websocat)
|
||||
log_info "Publishing test event to relay..."
|
||||
local result
|
||||
result=$(echo "$test_event" | timeout 10s nak event "$RELAY_URL" 2>&1)
|
||||
@@ -236,9 +279,6 @@ setup_test_environment() {
|
||||
# Create temporary directory
|
||||
mkdir -p "$TEMP_DIR"
|
||||
|
||||
# Clear log file
|
||||
echo "=== C-Relay Whitelist/Blacklist Test Started at $(date) ===" > "$LOG_FILE"
|
||||
|
||||
# Check if required tools are available - like NIP-42 test
|
||||
log_info "Checking dependencies..."
|
||||
|
||||
@@ -258,9 +298,10 @@ setup_test_environment() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v wscat &> /dev/null; then
|
||||
log_warning "wscat not found. Some WebSocket tests may be limited"
|
||||
log_warning "Install with: npm install -g wscat"
|
||||
if ! command -v websocat &> /dev/null; then
|
||||
log_error "websocat not found - required for WebSocket testing"
|
||||
log_error "Please install websocat for WebSocket communication"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "Dependencies check complete"
|
||||
@@ -283,10 +324,10 @@ test_admin_authentication() {
|
||||
log "Test $TESTS_RUN: Admin Authentication"
|
||||
|
||||
# Create a simple configuration event to test admin authentication
|
||||
local content="Testing admin authentication"
|
||||
# Using Kind 23455 (ephemeral configuration management) - no d tag needed
|
||||
local content="{\"action\":\"set\",\"description\":\"Testing admin authentication\"}"
|
||||
local config_event
|
||||
config_event=$(nak event -k 33334 --content "$content" \
|
||||
-t "d=$RELAY_PUBKEY" \
|
||||
config_event=$(nak event -k 23455 --content "$content" \
|
||||
-t "test_auth=true" \
|
||||
--sec "$ADMIN_PRIVKEY" 2>/dev/null)
|
||||
|
||||
@@ -295,25 +336,11 @@ test_admin_authentication() {
|
||||
return
|
||||
fi
|
||||
|
||||
# DEBUG: Print the full event that will be sent
|
||||
log_info "=== DEBUG: Full admin event being sent ==="
|
||||
echo "$config_event" | jq . 2>/dev/null || echo "$config_event"
|
||||
log_info "=== END DEBUG EVENT ==="
|
||||
|
||||
# Send admin event
|
||||
local message="[\"EVENT\",$config_event]"
|
||||
log_info "=== DEBUG: Full WebSocket message ==="
|
||||
echo "$message"
|
||||
log_info "=== END DEBUG MESSAGE ==="
|
||||
|
||||
local response
|
||||
response=$(send_websocket_message "$message" "OK" 10)
|
||||
|
||||
# DEBUG: Print the full response from server
|
||||
log_info "=== DEBUG: Full server response ==="
|
||||
echo "$response"
|
||||
log_info "=== END DEBUG RESPONSE ==="
|
||||
|
||||
if echo "$response" | grep -q '"OK".*true'; then
|
||||
pass_test "Admin authentication successful"
|
||||
else
|
||||
@@ -321,11 +348,65 @@ test_admin_authentication() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 2: Basic Whitelist Functionality
|
||||
# Test 2: Auth Rules Storage and Query Test
|
||||
test_auth_rules_storage_query() {
|
||||
increment_test
|
||||
log "Test $TESTS_RUN: Auth Rules Storage and Query Test"
|
||||
|
||||
# Clear all existing rules to start fresh
|
||||
clear_all_auth_rules
|
||||
|
||||
# Add a simple blacklist rule
|
||||
log_info "Adding test blacklist rule..."
|
||||
if send_auth_rule_event "add" "blacklist" "pubkey" "$TEST1_PUBKEY" "Test storage blacklist entry"; then
|
||||
log_success "Auth rule added successfully"
|
||||
|
||||
# Wait a moment for rule to be processed
|
||||
sleep 1
|
||||
|
||||
# Query all auth rules using admin query
|
||||
log_info "Querying all auth rules..."
|
||||
local query_event
|
||||
query_event=$(nak event -k 23456 --content "{\"action\":\"list_all\"}" \
|
||||
-t "auth_query=list_all" \
|
||||
--sec "$ADMIN_PRIVKEY" 2>/dev/null)
|
||||
|
||||
if [ $? -ne 0 ] || [ -z "$query_event" ]; then
|
||||
fail_test "Failed to create auth query event"
|
||||
return
|
||||
fi
|
||||
|
||||
# Send the query event
|
||||
log_info "Sending auth query to relay..."
|
||||
local query_result
|
||||
query_result=$(echo "$query_event" | timeout 10s nak event "$RELAY_URL" 2>&1)
|
||||
local exit_code=$?
|
||||
|
||||
log_info "Auth query result: $query_result"
|
||||
|
||||
# Check if we got a response and if it contains our test rule
|
||||
if [ $exit_code -eq 0 ]; then
|
||||
if echo "$query_result" | grep -q "$TEST1_PUBKEY"; then
|
||||
pass_test "Auth rule storage and query working - found test rule in query results"
|
||||
else
|
||||
fail_test "Auth rule not found in query results - rule may not have been stored"
|
||||
fi
|
||||
else
|
||||
fail_test "Auth query failed: $query_result"
|
||||
fi
|
||||
else
|
||||
fail_test "Failed to add auth rule for storage test"
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 3: Basic Whitelist Functionality
|
||||
test_basic_whitelist() {
|
||||
increment_test
|
||||
log "Test $TESTS_RUN: Basic Whitelist Functionality"
|
||||
|
||||
# Clear all existing rules to start fresh
|
||||
clear_all_auth_rules
|
||||
|
||||
# Add TEST1 pubkey to whitelist
|
||||
if send_auth_rule_event "add" "whitelist" "pubkey" "$TEST1_PUBKEY" "Test whitelist entry"; then
|
||||
# Test that whitelisted pubkey can publish
|
||||
@@ -339,11 +420,14 @@ test_basic_whitelist() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 3: Basic Blacklist Functionality
|
||||
# Test 4: Basic Blacklist Functionality
|
||||
test_basic_blacklist() {
|
||||
increment_test
|
||||
log "Test $TESTS_RUN: Basic Blacklist Functionality"
|
||||
|
||||
# Clear all existing rules to start fresh
|
||||
clear_all_auth_rules
|
||||
|
||||
# Add TEST2 pubkey to blacklist
|
||||
if send_auth_rule_event "add" "blacklist" "pubkey" "$TEST2_PUBKEY" "Test blacklist entry"; then
|
||||
# Test that blacklisted pubkey cannot publish
|
||||
@@ -357,11 +441,20 @@ test_basic_blacklist() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 4: Rule Removal
|
||||
# Test 5: Rule Removal
|
||||
test_rule_removal() {
|
||||
increment_test
|
||||
log "Test $TESTS_RUN: Rule Removal"
|
||||
|
||||
# Clear all existing rules to start fresh
|
||||
clear_all_auth_rules
|
||||
|
||||
# First add TEST2 to blacklist to test removal
|
||||
if ! send_auth_rule_event "add" "blacklist" "pubkey" "$TEST2_PUBKEY" "Test blacklist for removal"; then
|
||||
fail_test "Failed to add pubkey to blacklist for removal test"
|
||||
return
|
||||
fi
|
||||
|
||||
# Remove TEST2 from blacklist
|
||||
if send_auth_rule_event "remove" "blacklist" "pubkey" "$TEST2_PUBKEY" "Remove test blacklist entry"; then
|
||||
# Test that previously blacklisted pubkey can now publish
|
||||
@@ -375,11 +468,14 @@ test_rule_removal() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 5: Multiple Users Scenario
|
||||
# Test 6: Multiple Users Scenario
|
||||
test_multiple_users() {
|
||||
increment_test
|
||||
log "Test $TESTS_RUN: Multiple Users Scenario"
|
||||
|
||||
# Clear all existing rules to start fresh
|
||||
clear_all_auth_rules
|
||||
|
||||
# Add TEST1 to whitelist and TEST3 to blacklist
|
||||
local success_count=0
|
||||
|
||||
@@ -408,11 +504,14 @@ test_multiple_users() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 6: Priority Testing (Blacklist vs Whitelist)
|
||||
# Test 7: Priority Testing (Blacklist vs Whitelist)
|
||||
test_priority_rules() {
|
||||
increment_test
|
||||
log "Test $TESTS_RUN: Priority Rules Testing"
|
||||
|
||||
# Clear all existing rules to start fresh
|
||||
clear_all_auth_rules
|
||||
|
||||
# Add same pubkey to both whitelist and blacklist
|
||||
local setup_success=0
|
||||
|
||||
@@ -438,11 +537,14 @@ test_priority_rules() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 7: Hash-based Blacklist
|
||||
# Test 8: Hash-based Blacklist
|
||||
test_hash_blacklist() {
|
||||
increment_test
|
||||
log "Test $TESTS_RUN: Hash-based Blacklist"
|
||||
|
||||
# Clear all existing rules to start fresh
|
||||
clear_all_auth_rules
|
||||
|
||||
# Create a test event to get its hash
|
||||
local test_content="Content to be blacklisted by hash"
|
||||
local test_event
|
||||
@@ -482,11 +584,14 @@ test_hash_blacklist() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 8: WebSocket Connection Behavior
|
||||
# Test 9: WebSocket Connection Behavior
|
||||
test_websocket_behavior() {
|
||||
increment_test
|
||||
log "Test $TESTS_RUN: WebSocket Connection Behavior"
|
||||
|
||||
# Clear all existing rules to start fresh
|
||||
clear_all_auth_rules
|
||||
|
||||
# Test that the WebSocket connection handles multiple rapid requests
|
||||
local rapid_success_count=0
|
||||
|
||||
@@ -516,11 +621,14 @@ test_websocket_behavior() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 9: Rule Persistence Verification
|
||||
# Test 10: Rule Persistence Verification
|
||||
test_rule_persistence() {
|
||||
increment_test
|
||||
log "Test $TESTS_RUN: Rule Persistence Verification"
|
||||
|
||||
# Clear all existing rules to start fresh
|
||||
clear_all_auth_rules
|
||||
|
||||
# Add a rule, then verify it persists by testing enforcement
|
||||
if send_auth_rule_event "add" "blacklist" "pubkey" "$TEST3_PUBKEY" "Persistence test blacklist"; then
|
||||
# Wait a moment for rule to be processed
|
||||
@@ -546,7 +654,7 @@ test_rule_persistence() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 10: Cleanup and Final Verification
|
||||
# Test 11: Cleanup and Final Verification
|
||||
test_cleanup_verification() {
|
||||
increment_test
|
||||
log "Test $TESTS_RUN: Cleanup and Final Verification"
|
||||
@@ -589,10 +697,11 @@ run_all_tests() {
|
||||
# Setup
|
||||
setup_test_environment
|
||||
|
||||
# Run only test 1 for debugging admin authentication
|
||||
test_admin_authentication
|
||||
# Clear all auth rules before starting tests
|
||||
clear_all_auth_rules
|
||||
|
||||
# Comment out other tests for now to focus on debugging
|
||||
# test_admin_authentication
|
||||
# test_auth_rules_storage_query
|
||||
# test_basic_whitelist
|
||||
# test_basic_blacklist
|
||||
# test_rule_removal
|
||||
@@ -648,8 +757,8 @@ main() {
|
||||
echo -e "${BLUE}===============================================${RESET}"
|
||||
echo ""
|
||||
|
||||
# Check if relay is running - use the same method we verified manually
|
||||
if ! echo '["REQ","connection_test",{}]' | timeout 5 wscat -c "$RELAY_URL" >/dev/null 2>&1; then
|
||||
# Check if relay is running - using websocat like the working tests
|
||||
if ! echo '["REQ","connection_test",{}]' | timeout 5 websocat "$RELAY_URL" >/dev/null 2>&1; then
|
||||
log_error "Cannot connect to relay at $RELAY_URL"
|
||||
log_error "Please ensure the C-Relay server is running in test mode"
|
||||
exit 1
|
||||
@@ -661,12 +770,10 @@ main() {
|
||||
if run_all_tests; then
|
||||
echo ""
|
||||
log_success "All whitelist/blacklist tests completed successfully!"
|
||||
echo -e "Test log saved to: ${YELLOW}$LOG_FILE${RESET}"
|
||||
exit 0
|
||||
else
|
||||
echo ""
|
||||
log_error "Some tests failed. Check the log for details."
|
||||
echo -e "Test log saved to: ${YELLOW}$LOG_FILE${RESET}"
|
||||
log_error "Some tests failed."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
=== C-Relay Whitelist/Blacklist Test Started at Tue Sep 23 11:20:40 AM EDT 2025 ===
|
||||
=== C-Relay Whitelist/Blacklist Test Started at Thu Sep 25 07:28:40 AM EDT 2025 ===
|
||||
[0;34m[INFO][0m Checking dependencies...
|
||||
[0;32m[SUCCESS][0m Dependencies check complete
|
||||
[0;34m[INFO][0m Generated keypair for TEST1: pubkey=eab7cac03049d07f...
|
||||
[0;34m[INFO][0m Generated keypair for TEST2: pubkey=4e07a99f656d5301...
|
||||
[0;34m[INFO][0m Generated keypair for TEST3: pubkey=bf48b836426805cb...
|
||||
[0;34m[INFO][0m Generated keypair for TEST1: pubkey=36e6521000b2ddda...
|
||||
[0;34m[INFO][0m Generated keypair for TEST2: pubkey=9cdd32f27fffeea8...
|
||||
[0;34m[INFO][0m Generated keypair for TEST3: pubkey=e05928b64d3ad54a...
|
||||
[0;32m[SUCCESS][0m Test environment setup complete
|
||||
[0;34m[11:20:41][0m Test 1: Admin Authentication
|
||||
[0;34m[07:28:42][0m Test 1: Admin Authentication
|
||||
[0;34m[INFO][0m === DEBUG: Full admin event being sent ===
|
||||
[0;34m[INFO][0m === END DEBUG EVENT ===
|
||||
[0;34m[INFO][0m === DEBUG: Full WebSocket message ===
|
||||
[0;34m[INFO][0m === END DEBUG MESSAGE ===
|
||||
[0;34m[INFO][0m Sending WebSocket message: ["EVENT",{"kind":33334,"id":"ce73fa326eb558505742770eb927a50edc16a69512089939f76da90c7ca5291f","pubk...
|
||||
[0;34m[INFO][0m Sending WebSocket message (full):
|
||||
[0;34m[INFO][0m ["EVENT",{"kind":33334,"id":"ba07a9d01ef3bf8c424eb5ecd8a162980e5d596f3c8520ea59c4cf80961a347a","pubkey":"6a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3","created_at":1758799722,"tags":[["d","4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa"],["test_auth","true"]],"content":"Testing admin authentication","sig":"b8f40558060f402da232f3cc2ff72ea98257a3e3d74bc5c4bebd6fbd24d8d258138f879795b6089c01c4afa4c64088306dc917fdcad7b054d3c12513581b9228"}]
|
||||
[0;34m[INFO][0m WebSocket response (full):
|
||||
[0;34m[INFO][0m
|
||||
[0;34m[INFO][0m === DEBUG: Full server response ===
|
||||
[0;34m[INFO][0m === END DEBUG RESPONSE ===
|
||||
[0;31m[ERROR][0m Test 1: FAILED - Admin authentication failed: [0;34m[INFO][0m Sending WebSocket message: ["EVENT",{"kind":33334,"id":"ce73fa326eb558505742770eb927a50edc16a69512089939f76da90c7ca5291f","pubk...
|
||||
[0;31m[ERROR][0m Test 1: FAILED - Admin authentication failed: [0;34m[INFO][0m Sending WebSocket message (full):
|
||||
[0;34m[INFO][0m ["EVENT",{"kind":33334,"id":"ba07a9d01ef3bf8c424eb5ecd8a162980e5d596f3c8520ea59c4cf80961a347a","pubkey":"6a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3","created_at":1758799722,"tags":[["d","4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa"],["test_auth","true"]],"content":"Testing admin authentication","sig":"b8f40558060f402da232f3cc2ff72ea98257a3e3d74bc5c4bebd6fbd24d8d258138f879795b6089c01c4afa4c64088306dc917fdcad7b054d3c12513581b9228"}]
|
||||
[0;34m[INFO][0m WebSocket response (full):
|
||||
[0;34m[INFO][0m
|
||||
[0;31m[ERROR][0m 1 out of 1 tests failed.
|
||||
[0;31m[ERROR][0m Some tests failed. Check the log for details.
|
||||
[0;34m[11:20:42][0m Cleaning up test environment...
|
||||
[0;34m[INFO][0m Temporary directory removed: /tmp/c_relay_test_1773069
|
||||
[0;34m[11:20:42][0m Test cleanup completed.
|
||||
[0;34m[07:28:42][0m Cleaning up test environment...
|
||||
[0;34m[INFO][0m Temporary directory removed: /tmp/c_relay_test_184904
|
||||
[0;34m[07:28:42][0m Test cleanup completed.
|
||||
|
||||
Reference in New Issue
Block a user