Files
mc-webui/technotes/pending-contacts-api.md
MarekWo 9967980521 docs(technotes): Document contact type differences in add_pending behavior
Added comprehensive documentation of discovered differences between CLI and ROOM
contact types when using the add_pending command.

Key findings from real-world testing (2025-12-29):

CLI Contacts (flexible matching):
-  Full name works: "StNMobile T1000e"
-  Name prefix works: "StN"
-  Public key prefix works: "2ce5514"
-  Full public key works

ROOM Contacts (strict matching):
-  Full name fails: "TK room cwiczebny🔆" (UTF-8 issues)
-  Name prefix fails: "TK room"
-  Public key prefix fails: "b3fec489"
-  ONLY full public key works

Root cause: meshcli uses different matching logic for different contact types,
likely to prevent accidental approval of group rooms which have different
security/privacy implications.

Recommendation: UI should always send full public_key (not name) when calling
POST /add_pending to ensure compatibility with all contact types.

Updated sections:
- Selector formats with CLI/ROOM compatibility notes
- New "Important Discovery: Contact Type Differences" section
- Updated test commands with real-world results
- JavaScript code examples for UI implementation
- Best practices for UI design

This documentation will guide the next phase: UI implementation for pending
contact management.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-29 08:57:16 +01:00

15 KiB

Pending Contacts API - Technical Notes

Overview

This document describes the implementation of pending contacts management API in meshcore-bridge. This feature enables manual approval of new contacts when manual_add_contacts mode is enabled in meshcli.

Branch: dev-2 Status: Implemented (API only, no UI yet) Date: 2025-12-29

Problem Statement

When meshcli runs with manual_add_contacts on, new contacts attempting to connect are placed in a "pending" state instead of being automatically added to the contacts list. This provides security benefits:

  • Control over network access - Only approved contacts can communicate with the node
  • Prevention of spam/unwanted contacts - Filter out random nodes attempting connection
  • Explicit trust model - User decides who to trust on the mesh network

However, managing pending contacts required manual meshcli commands, which was inconvenient for web interface users.

Solution

Added two new HTTP endpoints to meshcore-bridge for programmatic pending contact management:

  1. GET /pending_contacts - List all contacts awaiting approval
  2. POST /add_pending - Approve and add a specific pending contact

Both endpoints use the existing persistent meshcli session architecture (no new processes spawned).

Implementation Details

1. Session Initialization

Modified _init_session_settings() in meshcore-bridge/bridge.py:122-136 to enable manual contact approval:

def _init_session_settings(self):
    """Configure meshcli session for advert logging, message subscription, and manual contact approval"""
    if self.process and self.process.stdin:
        self.process.stdin.write('set json_log_rx on\n')
        self.process.stdin.write('set print_adverts on\n')
        self.process.stdin.write('set manual_add_contacts on\n')  # NEW
        self.process.stdin.write('msgs_subscribe\n')
        self.process.stdin.flush()

Why in init?

  • Persistent setting - applies to entire session lifetime
  • No need to re-enable after watchdog restart
  • Consistent behavior across all API calls

2. GET /pending_contacts Endpoint

Location: meshcore-bridge/bridge.py:499-565

Purpose: Retrieve list of contacts awaiting manual approval

Request:

GET /pending_contacts HTTP/1.1
Host: meshcore-bridge:5001

Response (success):

{
  "success": true,
  "pending": [
    {
      "name": "Skyllancer",
      "public_key": "f9ef123abc456..."
    },
    {
      "name": "KRA Reksio mob2🐕",
      "public_key": "41d5789def012..."
    }
  ],
  "raw_stdout": "Skyllancer: f9ef123abc456...\nKRA Reksio mob2🐕: 41d5789def012..."
}

Response (no pending contacts):

{
  "success": true,
  "pending": [],
  "raw_stdout": ""
}

Response (error):

{
  "success": false,
  "error": "meshcli session not initialized",
  "pending": []
}

Implementation Notes:

  • Executes pending_contacts command via MeshCLISession.execute_command()
  • Parses meshcli output format: "ContactName: <hex_public_key>"
  • Removes spaces from public key hex (meshcli may insert spaces for readability)
  • Only parses lines containing colon :
  • Trims whitespace from contact names
  • Returns empty array if no pending contacts exist
  • Includes raw_stdout for debugging/troubleshooting

Parsing Logic:

for line in stdout.split('\n'):
    line = line.strip()
    if ':' in line:
        parts = line.split(':', 1)
        if len(parts) == 2:
            name = parts[0].strip()
            public_key = parts[1].strip().replace(' ', '')
            if name and public_key:
                pending.append({'name': name, 'public_key': public_key})

3. POST /add_pending Endpoint

Location: meshcore-bridge/bridge.py:568-629

Purpose: Approve and add a pending contact to the contacts list

Request:

POST /add_pending HTTP/1.1
Host: meshcore-bridge:5001
Content-Type: application/json

{
  "selector": "Skyllancer"
}

Selector formats supported (by meshcli):

  • Full contact name: "Skyllancer" (works for CLI contacts)
  • Public key prefix: "f9ef123" (works for CLI contacts, may not work for ROOM)
  • Full public key: "f9ef123abc456..." (RECOMMENDED - works for all contact types)

Response (success):

{
  "success": true,
  "stdout": "Contact added successfully",
  "stderr": "",
  "returncode": 0
}

Response (validation error):

{
  "success": false,
  "stdout": "",
  "stderr": "selector must be a non-empty string",
  "returncode": -1
}

Implementation Notes:

  • Validates selector is non-empty string
  • Trims whitespace from selector before execution
  • Executes add_pending <selector> command via persistent session
  • Returns full command result (stdout, stderr, returncode)
  • Uses default timeout (10 seconds)

Validation:

if not isinstance(selector, str) or not selector.strip():
    return jsonify({
        'success': False,
        'stderr': 'selector must be a non-empty string',
        'returncode': -1
    }), 400

Important Discovery: Contact Type Differences

🔍 Testing revealed different behavior for different contact types:

CLI Contacts (Clients)

Examples: StNMobile T1000e, ML2056, olekstomek

All selector formats work:

  • Full name: "StNMobile T1000e"SUCCESS
  • Name prefix: "StN"SUCCESS
  • Public key prefix: "2ce5514"SUCCESS
  • Full public key: "2ce5514826d39a44d23eb8eb9539676b57cae234528573c45d44bdd6ed01eeb5"SUCCESS

ROOM Contacts (Group Rooms)

Examples: TK room cwiczebny🔆

Name-based selectors DO NOT work:

  • Full name: "TK room cwiczebny🔆"FAILED (also UTF-8 encoding issues in shell)
  • Name prefix: "TK room"FAILED

Public key prefix DOES NOT work:

  • Prefix: "b3fec489"FAILED

Only FULL public key works:

  • Full public key: "b3fec489e1ee2d0277bd16de957253c8f5aa44a721df722224bbdc1edc5829b6"SUCCESS

Root Cause Analysis

The add_pending command in meshcli appears to use different matching logic for different contact types:

  • CLI contacts: Flexible matching - accepts name prefix, key prefix, or full key
  • ROOM contacts: Strict matching - requires exact full public key

This may be intentional behavior to prevent accidental approval of group rooms, which have different security/privacy implications than individual client contacts.

Recommendation for UI Implementation

Always use full public key for add_pending calls:

// Good - works for all contact types
fetch('/add_pending', {
  method: 'POST',
  body: JSON.stringify({
    selector: pendingContact.public_key  // Full key from GET /pending_contacts
  })
})

// Bad - may fail for ROOM contacts
fetch('/add_pending', {
  method: 'POST',
  body: JSON.stringify({
    selector: pendingContact.name  // Won't work for ROOMs
  })
})

UI Design Consideration:

Since GET /pending_contacts already returns both name and public_key, the UI should:

  1. Display human-readable name for user
  2. Send full public_key to API (not name)
  3. This ensures compatibility with all contact types

Example UI flow:

User sees: "TK room cwiczebny🔆" [Approve button]
User clicks: Approve
Frontend sends: {"selector": "b3fec489e1ee...5829b6"}  ← Full public key

Testing

Prerequisites

The bridge container must have:

  1. manual_add_contacts on enabled (automatic in session init)
  2. Pending contacts available (requires other nodes trying to connect)

Test Commands

From host or inside mc-webui container:

# List pending contacts
curl -s http://meshcore-bridge:5001/pending_contacts | jq

# Add a CLI contact by name (works for CLI, not ROOM)
curl -s -X POST http://meshcore-bridge:5001/add_pending \
  -H 'Content-Type: application/json' \
  -d '{"selector":"ML2056"}' | jq

# Add by full public key (RECOMMENDED - works for all types)
curl -s -X POST http://meshcore-bridge:5001/add_pending \
  -H 'Content-Type: application/json' \
  -d '{"selector":"b3fec489e1ee2d0277bd16de957253c8f5aa44a721df722224bbdc1edc5829b6"}' | jq

Real-world test results (2025-12-29):

  1. CLI contacts - flexible matching:

    # All worked successfully
    curl -d '{"selector":"StNMobile T1000e"}' → ✅ SUCCESS
    curl -d '{"selector":"ML2056"}' → ✅ SUCCESS
    curl -d '{"selector":"2ce5514"}' → ✅ SUCCESS (prefix)
    
  2. ROOM contact - strict matching:

    # Failed attempts
    curl -d '{"selector":"TK room cwiczebny🔆"}' → ❌ FAILED (UTF-8 encoding)
    curl -d '{"selector":"TK room"}' → ❌ FAILED (name prefix)
    curl -d '{"selector":"b3fec489"}' → ❌ FAILED (key prefix)
    
    # Success
    curl -d '{"selector":"b3fec489e1ee...5829b6"}' → ✅ SUCCESS (full key)
    

Expected workflow:

  1. New node attempts connection
  2. GET /pending_contacts shows the node in pending list with name and public_key
  3. POST /add_pending with full public_key (not name!) approves the contact
  4. GET /pending_contacts no longer shows the approved contact (moved to regular contacts)

Best Practice: Always use full public_key from the GET /pending_contacts response to ensure compatibility with all contact types (CLI, ROOM, REP, SENS).

Architecture Benefits

Reuses Persistent Session

  • No new subprocess spawning
  • Uses existing command queue (FIFO serialization)
  • Same event-based synchronization mechanism
  • Consistent error handling with other endpoints

Thread-safe

  • Commands queued through queue.Queue()
  • Protected by pending_lock during response handling
  • No race conditions with other CLI commands

Consistent with /cli Endpoint

  • Same request/response format
  • Same timeout handling
  • Same error reporting structure

Future Work

UI Integration (Next Phase)

Planned features:

  1. Pending Contacts Badge - Notification icon showing count of pending contacts
  2. Pending Contacts Modal - List view with approve/reject buttons
  3. Auto-refresh - Poll /pending_contacts every 30-60 seconds
  4. Notifications - Toast when new pending contacts appear

UI Mockup:

┌─────────────────────────────────────┐
│ Pending Contact Requests        [X] │
├─────────────────────────────────────┤
│ Skyllancer                          │
│ f9ef123abc...                       │
│          [Approve] [Reject]         │
├─────────────────────────────────────┤
│ KRA Reksio mob2🐕                   │
│ 41d5789def...                       │
│          [Approve] [Reject]         │
└─────────────────────────────────────┘

Additional API Endpoints (Consideration)

POST /reject_pending - Reject a pending contact

{
  "selector": "Skyllancer"
}

DELETE /pending_contacts - Flush all pending contacts

  • Executes flush_pending meshcli command
  • Useful for mass-reject after scanning for unwanted connections

Settings Integration

Add UI toggle in Settings modal:

☑ Manual Contact Approval
  Require explicit approval for new contacts

This would execute:

# Enable
curl -X POST /cli -d '{"args":["set","manual_add_contacts","on"]}'

# Disable
curl -X POST /cli -d '{"args":["set","manual_add_contacts","off"]}'

Security Considerations

Why Manual Contact Approval?

  1. DoS Prevention - Prevents flooding with fake contact requests
  2. Network Privacy - Control who can see your node
  3. Trust Model - Explicit approval creates stronger trust relationships
  4. Spam Filtering - Reject unwanted contact attempts

Trade-offs

Pros:

  • Enhanced security and privacy
  • User controls network access
  • Prevents automatic addition of random nodes

Cons:

  • Requires user interaction for every new contact
  • May miss legitimate contacts if user doesn't check pending list regularly
  • Additional management overhead

Recommendation

Use manual approval when:

  • Running on public/shared networks
  • Privacy is a concern
  • Network has history of spam/unwanted contacts
  • Small, controlled mesh network

Use auto-approval (default) when:

  • Private/trusted network environment
  • Ease of use is priority
  • Large mesh network where manual approval is impractical

Meshcli Command Reference

pending_contacts

meshcli> pending_contacts
Skyllancer: f9ef123abc456def789012345678901234567890abcdef
KRA Reksio mob2🐕: 41d5789def012345678901234567890abcdefabcdef012

add_pending

# By name
meshcli> add_pending Skyllancer
Contact added successfully

# By public key prefix (first few chars)
meshcli> add_pending f9ef123
Contact added successfully

# By full public key
meshcli> add_pending f9ef123abc456def789012345678901234567890abcdef
Contact added successfully

flush_pending

meshcli> flush_pending
All pending contacts removed

Manual mode toggle

# Enable manual approval
meshcli> set manual_add_contacts on
manual_add_contacts set to on

# Disable (auto-approve)
meshcli> set manual_add_contacts off
manual_add_contacts set to off

# Check current setting
meshcli> get manual_add_contacts
manual_add_contacts: on

Deployment

Files Modified

  1. meshcore-bridge/bridge.py

  2. Dockerfile

    • Added curl package for testing (line 7)
  3. README.md

    • Added "Testing Bridge API" section (lines 362-406)
    • Documented endpoints with examples

Commit

Branch: dev-2 Commit: 815adb5 - "feat(bridge): Add pending contacts management API endpoints"

Deployment Steps

# On server
cd ~/mc-webui
git fetch
git checkout dev-2
git pull origin dev-2
docker compose up -d --build

Verification

# Check logs for successful init
docker compose logs meshcore-bridge | grep manual_add_contacts
# Should see: "Session settings applied: ... manual_add_contacts=on ..."

# Test endpoint
docker exec mc-webui curl -s http://meshcore-bridge:5001/pending_contacts | jq

References


Author: Claude Code (Anthropic) Date: 2025-12-29 Status: API Implemented | UI Pending