featherChat side (10 tasks): WZP-FC-1: CallSignal WireMessage variant (2-4h) WZP-FC-2: Call state management + sled tree (1-2d) WZP-FC-3: WS handler for call signaling (0.5d) WZP-FC-4: Auth token validation endpoint (2-4h) WZP-FC-5: Group-to-room mapping (1d) WZP-FC-6: Presence/online status API (0.5-2d) WZP-FC-7: Missed call notifications (0.5d) WZP-FC-8: Cross-project identity verification test (2-4h) CRITICAL WZP-FC-9: HKDF salt investigation — VERIFIED: no mismatch (b""→None == None) WZP-FC-10: WZP web bridge shared auth (1-2d) WZP side suggestions (9 items): WZP-S-1 through WZP-S-9 covering auth, signaling bridge, room access control, proto publishing, CLI flags, and 6 hardcoded assumptions that conflict with integration. All tasks reference specific file:line in both codebases. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
677 lines
39 KiB
Markdown
677 lines
39 KiB
Markdown
# Future Tasks & Improvement Suggestions
|
|
|
|
These are optional improvements identified during development. Each includes context, effort estimate, and questions to answer before starting.
|
|
|
|
---
|
|
|
|
## Priority: High (Security/Reliability)
|
|
|
|
### 1. Auth Enforcement Middleware
|
|
**What:** Add axum middleware to enforce bearer tokens on protected endpoints.
|
|
**Why:** Currently anyone can impersonate any fingerprint — the auth system issues tokens but doesn't require them.
|
|
**Effort:** Half a day (~200 lines)
|
|
**Blocked by:** Nothing — can be done now.
|
|
|
|
**Questions before starting:**
|
|
- [ ] Should we enforce auth on all `/v1/*` routes or only write operations (send, groups, aliases)?
|
|
- [ ] Should the web client use cookie-based auth (simpler) or bearer tokens (consistent with CLI)?
|
|
- [ ] What's the token refresh strategy — silent refresh or re-auth on expiry?
|
|
|
|
---
|
|
|
|
### 2. Session Auto-Recovery
|
|
**What:** When ratchet decryption fails (corrupted state), auto-send a new X3DH KeyExchange to re-establish the session.
|
|
**Why:** Currently a corrupted session = permanent inability to decrypt from that peer until manual intervention.
|
|
**Effort:** 1 day
|
|
|
|
**Questions before starting:**
|
|
- [ ] Should we show a warning ("security session was reset") like Signal does?
|
|
- [ ] Should we keep the old corrupted session state for debugging, or just delete it?
|
|
- [ ] How many auto-recovery attempts before giving up (prevent infinite loops)?
|
|
|
|
---
|
|
|
|
### 3. Crypto Audit Plan
|
|
**What:** Prepare the codebase for a professional cryptographic audit.
|
|
**Why:** We implemented X3DH + Double Ratchet from scratch. Production use requires independent verification.
|
|
**Effort:** 1 week (documentation + code cleanup), then $20-50K for audit firm
|
|
|
|
**Questions before starting:**
|
|
- [ ] Do we want to audit now (before federation) or after Phase 3?
|
|
- [ ] Budget range? Trail of Bits (~$50K), Cure53 (~$30K), NCC Group (~$40K)?
|
|
- [ ] Should we migrate to libsignal instead? (Avoids audit but loses WASM + static binary)
|
|
|
|
---
|
|
|
|
## Priority: Medium (Architecture/Quality)
|
|
|
|
### 4. Extract Web Client from Monolith
|
|
**What:** Split the ~1000-line JS embedded in `web.rs` into separate files (app.js, crypto.js, ws.js, ui.js, style.css).
|
|
**Why:** Currently unmaintainable — one raw string in Rust containing all HTML/CSS/JS.
|
|
**Effort:** 1-2 days, zero functionality change
|
|
|
|
**Questions before starting:**
|
|
- [ ] Serve as static files from disk, or embed multiple files at compile time?
|
|
- [ ] Use ES modules (modern) or keep everything global (simpler)?
|
|
- [ ] Add a minimal build step (esbuild for bundling) or keep it build-free?
|
|
|
|
---
|
|
|
|
### 5. Session State Versioning
|
|
**What:** Add version byte to serialized ratchet/session state. Migrate old formats instead of failing.
|
|
**Why:** Any change to `RatchetState` struct breaks all existing sessions silently.
|
|
**Effort:** Half a day
|
|
|
|
**Questions before starting:**
|
|
- [ ] Just a version prefix byte, or a full envelope format (version + length + data)?
|
|
- [ ] Should we support migrating from v1→v2, or just re-establish sessions on version mismatch?
|
|
|
|
---
|
|
|
|
### 6. Periodic Auto-Backup
|
|
**What:** Automatically backup session state + contacts + history every N minutes to an encrypted local file.
|
|
**Why:** Currently backup is manual (`warzone backup`). If the process crashes, unsaved sessions are lost.
|
|
**Effort:** Half a day
|
|
|
|
**Questions before starting:**
|
|
- [ ] Backup interval? Every 5 minutes? Every new session? On quit?
|
|
- [ ] Where to store? Same directory as DB? Configurable?
|
|
- [ ] Keep N backups with rotation, or just latest?
|
|
|
|
---
|
|
|
|
### 7. WireMessage Versioning
|
|
**What:** Add a version field or envelope around the bincode `WireMessage` so old clients don't crash on new message types.
|
|
**Why:** Adding a new enum variant to `WireMessage` is a breaking change for all existing clients.
|
|
**Effort:** 1 day (careful, touches everything)
|
|
|
|
**Questions before starting:**
|
|
- [ ] Prefix with version byte before bincode, or switch to a self-describing format (protobuf, msgpack)?
|
|
- [ ] How do old clients handle unknown message types — ignore silently or show "update required"?
|
|
- [ ] Is this the right time to consider protobuf migration for the wire format?
|
|
|
|
---
|
|
|
|
## Priority: Normal (Features)
|
|
|
|
### 8. Mule Binary Implementation
|
|
**What:** Build the `warzone-mule` binary for physical message delivery between disconnected networks.
|
|
**Why:** Core warzone use case — the design is complete (DESIGN.md section 4) but no code exists.
|
|
**Effort:** 3-5 days
|
|
|
|
**Questions before starting:**
|
|
- [ ] Start with USB/file transport only, or include Bluetooth from the start?
|
|
- [ ] Do we need a mule GUI, or is CLI sufficient?
|
|
- [ ] How do we test this? Two isolated Docker networks? Physical devices?
|
|
- [ ] Should the mule have its own identity (keypair), or is it anonymous?
|
|
|
|
---
|
|
|
|
### 9. libsignal Migration Assessment
|
|
**What:** Evaluate replacing our custom X3DH + Double Ratchet with libsignal-client.
|
|
**Why:** Battle-tested, audited. But has trade-offs.
|
|
**Effort:** 1-2 weeks if we decide to do it
|
|
|
|
**Questions before starting:**
|
|
- [ ] Can we accept the BoringSSL C dependency? (Breaks pure Rust, complicates cross-compilation)
|
|
- [ ] libsignal doesn't support WASM — do we keep dual implementations (libsignal native + our ratchet for WASM)?
|
|
- [ ] Is the storage trait adaptation worth it? Their `IdentityKeyStore`/`SessionStore` are different from ours.
|
|
- [ ] Would an audit of our implementation be cheaper and better?
|
|
|
|
---
|
|
|
|
### 10. featherChat as OIDC Identity Provider
|
|
**What:** Add OIDC provider endpoints so external services (Authentik, Keycloak, Grafana) can use featherChat for SSO.
|
|
**Why:** Makes featherChat the identity backbone for an entire organization.
|
|
**Effort:** 1-2 weeks (see IDP_SMART_CONTRACT.md)
|
|
|
|
**Questions before starting:**
|
|
- [ ] OIDC only, or also SAML for enterprise environments?
|
|
- [ ] What claims to include in the JWT? (fingerprint, alias, eth_address, groups)
|
|
- [ ] Should Authentik integration be a priority, or generic OIDC first?
|
|
- [ ] Do we need a consent screen / authorization UI?
|
|
|
|
---
|
|
|
|
### 11. Smart Contract Access Control
|
|
**What:** Deploy a Solidity contract for on-chain permission management (server/group/feature access).
|
|
**Why:** Decentralized, auditable, censorship-resistant permissions.
|
|
**Effort:** 3-4 weeks (see IDP_SMART_CONTRACT.md)
|
|
|
|
**Questions before starting:**
|
|
- [ ] Which L2? Arbitrum, Base, Polygon, or deploy our own?
|
|
- [ ] Who pays gas? Admin only, or users too?
|
|
- [ ] Is NFT-gated access a priority, or start with simple ACL?
|
|
- [ ] How do we handle users who don't have a wallet? (featherChat-managed vs self-custody)
|
|
|
|
---
|
|
|
|
### 12. DNS Federation
|
|
**What:** Server discovery via DNS TXT records, server-to-server message relay.
|
|
**Why:** Multiple servers, no single point of failure.
|
|
**Effort:** 2-3 weeks (Phase 3)
|
|
|
|
**Questions before starting:**
|
|
- [ ] Do we run our own DNS server, or use existing infrastructure?
|
|
- [ ] DNSSEC required or optional?
|
|
- [ ] How do we handle split-brain (two servers think they're authoritative for the same user)?
|
|
- [ ] Is gossip discovery (no DNS) a sufficient fallback for warzone scenarios?
|
|
|
|
---
|
|
|
|
### 13. WarzonePhone Integration
|
|
**What:** Shared identity + call signaling through featherChat (see WZP_INTEGRATION.md).
|
|
**Why:** One seed, one identity, chat + calls.
|
|
**Effort:** 4-8 weeks across 4 phases
|
|
|
|
**Questions before starting:**
|
|
- [ ] Fix the HKDF info string mismatch first (`"warzone-ed25519"` vs `"warzone-ed25519-identity"`)?
|
|
- [ ] Who aligns — featherChat or WZP? (featherChat has more users/data to migrate)
|
|
- [ ] Start with shared identity only (Phase A), or jump to signaling (Phase B)?
|
|
- [ ] QUIC transport (WZP's choice) — should featherChat also adopt QUIC for messaging?
|
|
|
|
---
|
|
|
|
## Priority: Low (Polish/Nice-to-Have)
|
|
|
|
### 14. Message Search
|
|
**What:** Full-text search across local message history.
|
|
**Why:** "What was that config someone shared last week?"
|
|
**Effort:** 1 day (sled scan + substring match), 1 week (proper full-text index)
|
|
|
|
**Questions before starting:**
|
|
- [ ] Simple substring search, or proper full-text (tantivy crate)?
|
|
- [ ] Search across all contacts, or per-peer?
|
|
- [ ] Web client search too, or CLI only?
|
|
|
|
---
|
|
|
|
### 15. Read Receipts
|
|
**What:** Currently we have Delivered receipts. Add Read receipts (when user scrolls to/sees the message).
|
|
**Why:** Users want to know if their message was read, not just delivered.
|
|
**Effort:** Half a day
|
|
|
|
**Questions before starting:**
|
|
- [ ] Should read receipts be opt-in (privacy sensitive)?
|
|
- [ ] What constitutes "read"? Message visible in viewport for N seconds?
|
|
- [ ] TUI: is "displayed on screen" equivalent to "read"?
|
|
|
|
---
|
|
|
|
### 16. Typing Indicators
|
|
**What:** Show "User is typing..." when a peer is composing a message.
|
|
**Why:** UX polish.
|
|
**Effort:** Half a day
|
|
|
|
**Questions before starting:**
|
|
- [ ] Privacy concern — do we want to leak typing activity?
|
|
- [ ] Should this be opt-in?
|
|
- [ ] Encrypted or plaintext? (Plaintext is simpler, typing indicators aren't sensitive)
|
|
|
|
---
|
|
|
|
### 17. Message Reactions (Emoji)
|
|
**What:** React to a message with an emoji instead of a full reply.
|
|
**Why:** Quick acknowledgment without cluttering the chat.
|
|
**Effort:** 1 day
|
|
|
|
**Questions before starting:**
|
|
- [ ] New WireMessage variant, or encode as a special text message?
|
|
- [ ] Display inline (next to message) or as a separate line?
|
|
- [ ] Multiple reactions per message?
|
|
|
|
---
|
|
|
|
### 18. Voice Messages as Attachments
|
|
**What:** Record audio in the client, send as file attachment.
|
|
**Why:** Voice messages are critical in field use. Uses existing file transfer — no new protocol needed.
|
|
**Effort:** 1 day (CLI: pipe from mic, Web: MediaRecorder API)
|
|
|
|
**Questions before starting:**
|
|
- [ ] Codec? Opus at 16kbps is good. Or Codec2 for LoRa-compatible ultra-low bitrate?
|
|
- [ ] Max duration? 60 seconds? 5 minutes?
|
|
- [ ] Inline playback in web client, or download-only?
|
|
|
|
---
|
|
|
|
*Last updated: v0.0.20*
|
|
*To work on a task: answer the questions first, then implement.*
|
|
|
|
---
|
|
|
|
## WarzonePhone Integration Tasks (featherChat side)
|
|
|
|
These tasks describe concrete changes needed in the featherChat codebase to enable integration with WarzonePhone (WZP). Each references actual WZP source code that will consume or produce the integration point.
|
|
|
|
---
|
|
|
|
### WZP-FC-1. Add `CallSignal` WireMessage Variant
|
|
|
|
**What to change:**
|
|
- `warzone-protocol/src/message.rs:46-104` — Add a new variant to the `WireMessage` enum:
|
|
```rust
|
|
CallSignal {
|
|
id: String,
|
|
sender_fingerprint: String,
|
|
signal: Vec<u8>, // opaque serialized wzp_proto::SignalMessage (JSON bytes)
|
|
},
|
|
```
|
|
- `warzone-server/src/routes/ws.rs:25-41` — Add match arm to `extract_message_id()`:
|
|
```rust
|
|
WireMessage::CallSignal { id, .. } => Some(id),
|
|
```
|
|
- Any `match` on `WireMessage` across the codebase (search for exhaustive pattern matches on `WireMessage`) must gain a `CallSignal` arm.
|
|
|
|
**Why:** WZP call signaling (`CallOffer`, `CallAnswer`, `IceCandidate`, `Hangup`) needs to travel through featherChat's E2E encrypted Double Ratchet channel. The `signal` field carries an opaque JSON blob from `wzp-proto/src/packet.rs:249-310` (`SignalMessage` enum). The featherChat server never decrypts or interprets it — it is just another encrypted blob routed via the existing WS relay.
|
|
|
|
**WZP code that produces/consumes this:**
|
|
- `wzp-client/src/handshake.rs:35-44` — caller sends `SignalMessage::CallOffer` (identity_pub, ephemeral_pub, signature, supported_profiles)
|
|
- `wzp-relay/src/handshake.rs:71-77` — callee sends `SignalMessage::CallAnswer`
|
|
- `wzp-proto/src/packet.rs:275-278` — `SignalMessage::IceCandidate { candidate: String }`
|
|
- `wzp-proto/src/packet.rs:298-299` — `SignalMessage::Hangup { reason: HangupReason }`
|
|
|
|
**Effort:** 2-4 hours. The `WireMessage` enum already has 7 variants; adding one more is mechanical. The server treats all variants identically (opaque bincode blobs routed by fingerprint).
|
|
|
|
**Questions before starting:**
|
|
- [ ] Should `CallSignal` carry a `target_fingerprint` for the callee, or rely on the outer routing (the 64-byte hex fingerprint prefix in WS binary frames at ws.rs:108-109)?
|
|
- [ ] Task 7 in FUTURE_TASKS.md (WireMessage Versioning) — should we version the wire format before adding new variants, or add `CallSignal` now and version later?
|
|
- [ ] Should the `signal` field be `Vec<u8>` (raw JSON) or a typed `serde_json::Value`? Raw bytes preserve opacity but typed allows server-side validation of message structure.
|
|
|
|
---
|
|
|
|
### WZP-FC-2. Call State Management on the Server
|
|
|
|
**What to change:**
|
|
- `warzone-server/src/state.rs` — Add call tracking to `AppState`:
|
|
```rust
|
|
/// Active calls: call_id -> CallState
|
|
pub calls: Arc<Mutex<HashMap<String, CallState>>>,
|
|
```
|
|
Where `CallState` tracks: caller fingerprint, callee fingerprint, start time, state (ringing/active/ended).
|
|
- `warzone-server/src/db.rs` — Add a `calls` sled tree for persistent call history (missed calls, durations).
|
|
- New file `warzone-server/src/routes/calls.rs` — REST endpoints:
|
|
- `POST /v1/calls/initiate` — register a call intent (returns call_id)
|
|
- `GET /v1/calls/:id` — get call state
|
|
- `POST /v1/calls/:id/end` — mark call as ended
|
|
- `GET /v1/calls/active` — list active calls for a fingerprint
|
|
|
|
**Why:** WZP currently has no concept of call routing through featherChat. The relay (`wzp-relay/src/room.rs:73-127`) manages rooms by transport-level connections, not by user identity. featherChat needs to know which user is calling whom so it can: (a) route `CallSignal` messages to the correct recipient, (b) show "incoming call" UI, (c) store missed calls, (d) enforce authorization (only group members can call each other).
|
|
|
|
**WZP code that maps to this:**
|
|
- `wzp-proto/src/session.rs:10-24` — `SessionState` enum (Idle, Connecting, Handshaking, Active, Rekeying, Closed) — featherChat's CallState should mirror this progression.
|
|
- `wzp-relay/src/session_mgr.rs:13-24` — `RelaySession` tracks per-session state on the relay side.
|
|
|
|
**Effort:** 1-2 days. Requires new routes, new DB tree, state management. The `groups.rs` pattern (load_group/save_group with sled) can be reused.
|
|
|
|
**Questions before starting:**
|
|
- [ ] Should call state be purely in-memory (like CHALLENGES in auth.rs:54) or persistent in sled (like groups)?
|
|
- [ ] Call timeout — how long before an unanswered call is auto-cancelled? 30 seconds? 60?
|
|
- [ ] Multi-device: if a user has multiple WS connections (state.rs:14, `Vec<WsSender>`), should all devices ring?
|
|
- [ ] Should there be a limit on concurrent active calls per user?
|
|
|
|
---
|
|
|
|
### WZP-FC-3. WebSocket Call Signaling Handler
|
|
|
|
**What to change:**
|
|
- `warzone-server/src/routes/ws.rs:102-167` — In the `Message::Binary` and `Message::Text` handlers, after deserializing a `WireMessage`, detect the `CallSignal` variant and perform call-specific logic:
|
|
1. If `CallSignal` with a `CallOffer` signal: create/update the CallState (from WZP-FC-2), push to the callee's WS, and if callee is offline, queue it (existing queue:fingerprint:uuid pattern at ws.rs:124).
|
|
2. If `CallSignal` with a `Hangup` signal: update CallState to ended, notify the other party.
|
|
3. All other `CallSignal` types: route as opaque blobs (same as existing message routing).
|
|
|
|
**Why:** The existing WS handler (ws.rs:64-189) already routes binary messages by fingerprint prefix. Call signaling needs the same routing but with additional server-side awareness of call lifecycle. Without this, the server cannot generate "missed call" notifications or enforce call authorization.
|
|
|
|
**WZP code that will send these messages:**
|
|
- The integrated client will serialize `wzp_proto::SignalMessage` to JSON, wrap it in `WireMessage::CallSignal`, serialize with bincode, then prepend the 64-hex-char recipient fingerprint — same as existing message sending (ws.rs:108-128).
|
|
|
|
**Effort:** Half a day. Mostly adding conditional logic inside the existing `Message::Binary` handler. The routing infrastructure already exists.
|
|
|
|
**Questions before starting:**
|
|
- [ ] Should the server peek inside the `signal` field to determine if it is a CallOffer vs Hangup? Or should the server be completely opaque and rely on separate REST calls (WZP-FC-2) for state tracking?
|
|
- [ ] If opaque: how does the server know a call was missed (callee never connected)?
|
|
- [ ] Priority: should CallSignal messages bypass the dedup tracker (state.rs:17-48) or use it? Duplicate call offers could be confusing.
|
|
|
|
---
|
|
|
|
### WZP-FC-4. Auth Token Issuance for WZP
|
|
|
|
**What to change:**
|
|
- `warzone-server/src/routes/auth.rs` — Add a new endpoint:
|
|
```rust
|
|
.route("/auth/validate", post(validate_token_endpoint))
|
|
```
|
|
This wraps the existing `validate_token()` function (auth.rs:177-186) as an HTTP endpoint:
|
|
```
|
|
POST /v1/auth/validate
|
|
Body: { "token": "hex..." }
|
|
Response: { "valid": true, "fingerprint": "a3f8...", "expires_at": ... }
|
|
or { "valid": false }
|
|
```
|
|
- Optionally, add a scoped token variant: `POST /v1/auth/issue-service-token` that issues a short-lived token (5 min) specifically for WZP relay authentication, with a `"service": "wzp"` claim in the JSON payload stored in the `tokens` sled tree.
|
|
|
|
**Why:** WZP relays need a way to verify that a connecting client is a legitimate featherChat user. Currently WZP has NO server-side auth (`wzp-relay/src/main.rs:159-231` accepts any QUIC connection). The validate endpoint lets the WZP relay call featherChat's server to confirm a bearer token is valid before allowing the QUIC call session.
|
|
|
|
**WZP code that will use this:**
|
|
- Currently none — WZP would need to add a pre-connection auth step where the client presents a featherChat bearer token. See "Suggestions for WarzonePhone" section below.
|
|
- `wzp-crypto/src/handshake.rs:79-88` — WZP already verifies Ed25519 identity signatures during the QUIC handshake. The featherChat token adds a second layer: "this identity is registered with the featherChat server."
|
|
|
|
**Effort:** 2-4 hours. The `validate_token()` function already exists and works; this is wrapping it as a route + adding optional scoped tokens.
|
|
|
|
**Questions before starting:**
|
|
- [ ] Should the validate endpoint require auth itself (only WZP relay can call it)? Or is it public?
|
|
- [ ] Should we use HMAC-signed JWTs instead of random hex tokens? JWTs can be verified without a round-trip to the server. The WZP relay could verify locally if it knows the signing key.
|
|
- [ ] Token scope: should a single featherChat token work for both messaging and calling, or should calls require a separate scoped token?
|
|
|
|
---
|
|
|
|
### WZP-FC-5. Group-to-Room Mapping
|
|
|
|
**What to change:**
|
|
- `warzone-server/src/routes/groups.rs` — Add endpoint:
|
|
```rust
|
|
.route("/groups/:name/call", post(initiate_group_call))
|
|
```
|
|
This creates a WZP room ID from the group name (e.g., `room_id = SHA-256("featherchat-group:" + group_name)[:16]` as hex), stores it in the call state, and pushes a `CallSignal` with `CallOffer` to all online group members.
|
|
- `warzone-server/src/routes/groups.rs:270-297` (`get_members`) — Extend response to include online/offline status (requires checking `state.connections` from state.rs:13-14).
|
|
|
|
**Why:** WZP rooms (`wzp-relay/src/room.rs:73-127`) are identified by a string name passed as QUIC SNI (`wzp-relay/src/main.rs:173-179`). featherChat groups (`groups.rs:23-28`, `GroupInfo { name, creator, members }`) are the natural unit for group calls. Mapping group name to room name lets clients know which WZP relay room to connect to.
|
|
|
|
**WZP code that consumes this:**
|
|
- `wzp-relay/src/main.rs:173-179` — room name extracted from QUIC SNI (`connection.handshake_data()...server_name`).
|
|
- `wzp-relay/src/room.rs:85-93` — `RoomManager::join(room_name, ...)` — the room_name string is the join key.
|
|
- `wzp-web/src/main.rs:158-161` — web bridge passes room name as SNI when connecting to relay.
|
|
|
|
**Effort:** 1 day. The group membership infrastructure exists; the new logic is generating a deterministic room ID and coordinating the call invitation fanout (similar to `send_to_group` at groups.rs:171-209).
|
|
|
|
**Questions before starting:**
|
|
- [ ] Room ID derivation: hash of group name (deterministic, predictable) or random (requires distribution)? Hash is simpler but leaks group name to the relay via SNI.
|
|
- [ ] Should the relay enforce room membership, or is room access open (anyone with the room name can join)? Currently WZP rooms are open (room.rs:91).
|
|
- [ ] Max participants per group call? WZP relay is SFU — bandwidth scales as O(N) per participant.
|
|
- [ ] Should group calls use featherChat's Sender Key mechanism (`WireMessage::GroupSenderKey` at message.rs:87-95) for group call signaling encryption?
|
|
|
|
---
|
|
|
|
### WZP-FC-6. Presence / Online Status
|
|
|
|
**What to change:**
|
|
- `warzone-server/src/state.rs:50-105` — Add a public method:
|
|
```rust
|
|
pub async fn is_online(&self, fingerprint: &str) -> bool {
|
|
let conns = self.connections.lock().await;
|
|
conns.get(fingerprint).map(|s| !s.is_empty()).unwrap_or(false)
|
|
}
|
|
```
|
|
- New route in a `routes/presence.rs` or extending `routes/keys.rs`:
|
|
```
|
|
GET /v1/presence/:fingerprint -> { "online": true/false, "devices": 2 }
|
|
POST /v1/presence/batch -> [{ "fingerprint": "...", "online": true }, ...]
|
|
```
|
|
- `warzone-server/src/routes/ws.rs:64-189` — On WS connect/disconnect, emit a presence event (optionally push to subscribers).
|
|
|
|
**Why:** Before initiating a call, the client needs to know if the callee is reachable. Currently `state.connections` (state.rs:13-14) tracks WS connections but this data is not exposed via any API. WZP's client (`wzp-client/src/cli.rs`) currently connects directly to the relay without checking if the peer is available.
|
|
|
|
**WZP code that benefits:**
|
|
- The integrated client would check presence before constructing `SignalMessage::CallOffer` (`wzp-client/src/handshake.rs:34-44`). If the callee is offline, the client can skip the call or send a "missed call" notification instead.
|
|
|
|
**Effort:** Half a day for basic online/offline. 1-2 days for real-time presence subscriptions.
|
|
|
|
**Questions before starting:**
|
|
- [ ] Privacy: should presence be opt-in? Some users may not want others to know they are online.
|
|
- [ ] Should presence updates be push (WS event when a contact comes online) or poll (REST endpoint)?
|
|
- [ ] Should we expose device count, or just online/offline boolean?
|
|
- [ ] Auth: should presence queries require a valid bearer token?
|
|
|
|
---
|
|
|
|
### WZP-FC-7. Missed Call Notifications (Store-and-Forward)
|
|
|
|
**What to change:**
|
|
- `warzone-server/src/routes/ws.rs:120-128` — When a `CallSignal` with a `CallOffer` signal cannot be delivered (callee offline, `push_to_client` returns false), store a "missed call" record in sled instead of (or in addition to) queuing the raw message.
|
|
- `warzone-server/src/db.rs` — Add a `missed_calls` sled tree. Schema: key = `missed:{callee_fp}:{timestamp}`, value = JSON `{ caller_fp, timestamp, call_id }`.
|
|
- New endpoint: `GET /v1/calls/missed` — returns missed calls for the authenticated user.
|
|
- On WS reconnect (ws.rs:70-85, queue drain loop): include missed call notifications alongside queued messages.
|
|
|
|
**Why:** In warzone/field scenarios, users may be offline when called. They need to know who tried to reach them. This mirrors the existing store-and-forward for text messages (queue:fingerprint:uuid pattern at ws.rs:124) but specifically for call attempts.
|
|
|
|
**WZP code that maps to this:**
|
|
- `wzp-proto/src/packet.rs:298-299` — `SignalMessage::Hangup { reason: HangupReason::Timeout }` would be generated by the caller after no answer. featherChat should interpret this as a missed call.
|
|
- `wzp-proto/src/packet.rs:303-310` — `HangupReason::Busy`, `HangupReason::Declined` should produce different notification types.
|
|
|
|
**Effort:** Half a day. Reuses existing queue infrastructure.
|
|
|
|
**Questions before starting:**
|
|
- [ ] Should missed calls expire? After 7 days? 30 days?
|
|
- [ ] Should the caller get a "call not delivered" notification if the callee is offline?
|
|
- [ ] Should missed calls from the same caller within N minutes be collapsed into one?
|
|
|
|
---
|
|
|
|
### WZP-FC-8. Cross-Project Identity Verification Test
|
|
|
|
**What to change:**
|
|
- New integration test (could live in `warzone-protocol/tests/wzp_identity_compat.rs` or a shared test crate):
|
|
1. Generate a seed with featherChat's `Seed::from_bytes()` (`identity.rs:24-26`)
|
|
2. Derive identity with `seed.derive_identity()` (`identity.rs:29-47`)
|
|
3. Derive identity with WZP's `WarzoneKeyExchange::from_identity_seed()` (`wzp-crypto/src/handshake.rs:32-53`)
|
|
4. Assert: `featherchat_identity.signing.verifying_key().as_bytes() == wzp_identity.identity_public_key()`
|
|
5. Assert: featherChat fingerprint == WZP fingerprint
|
|
6. Cross-sign: sign with featherChat key, verify with WZP's `WarzoneKeyExchange::verify()`
|
|
7. Cross-sign: sign with WZP key, verify with featherChat's Ed25519 verifier
|
|
|
|
**Critical blocker:** This test WILL FAIL today because of the HKDF info string mismatch:
|
|
- featherChat uses `"warzone-ed25519"` (`identity.rs:31`) and `"warzone-x25519"` (`identity.rs:38`)
|
|
- WZP uses `"warzone-ed25519"` (`handshake.rs:36`) and `"warzone-x25519"` (`handshake.rs:43`)
|
|
|
|
**Wait — re-reading the actual WZP code:** The WZP code at `wzp-crypto/src/handshake.rs:36` uses `b"warzone-ed25519"` (NOT `b"warzone-ed25519-identity"` as documented in WZP_INTEGRATION.md). The WZP code was apparently already updated to match featherChat. Let me confirm:
|
|
- `handshake.rs:36`: `hk.expand(b"warzone-ed25519", ...)` — matches featherChat
|
|
- `handshake.rs:43`: `hk.expand(b"warzone-x25519", ...)` — matches featherChat
|
|
|
|
**However**, featherChat uses `hkdf_derive()` which calls `Hkdf::new(Some(salt), ikm)` with `salt=b""` (non-empty empty bytes), while WZP uses `Hkdf::new(None, seed)` (no salt). This is a REAL mismatch: `Hkdf::new(Some(b""), seed)` vs `Hkdf::new(None, seed)` produce DIFFERENT outputs because HKDF treats `None` salt as all-zero bytes of hash length (32 bytes for SHA-256), while `Some(b"")` is a zero-length salt.
|
|
|
|
**This test is the single most critical integration task.** It validates or invalidates the "same seed = same identity" assumption.
|
|
|
|
**Effort:** 2-4 hours to write the test. Fix depends on which side adjusts the HKDF salt parameter.
|
|
|
|
**Questions before starting:**
|
|
- [ ] Which HKDF salt convention should be canonical? featherChat's `Some(b"")` or WZP's `None`? Check what the actual `hkdf_derive` function does — it may already handle this.
|
|
- [ ] If the salt mismatch is confirmed: who changes? featherChat has deployed users; WZP is v0.1.0 with no deployed users. WZP should change.
|
|
- [ ] Should this test live in featherChat's repo (as a dev-dependency on wzp-crypto) or in a separate integration test crate?
|
|
|
|
---
|
|
|
|
### WZP-FC-9. HKDF Salt Alignment Investigation
|
|
|
|
**What to change:**
|
|
- `warzone-protocol/src/crypto.rs` (wherever `hkdf_derive` is defined) — Audit the exact HKDF construction. The function signature is `hkdf_derive(&self.0, b"", b"warzone-ed25519", 32)` at `identity.rs:31`. If it passes `b""` as salt to `Hkdf::new(Some(b""), ikm)`, this differs from WZP's `Hkdf::new(None, seed)`.
|
|
- Depending on findings, either:
|
|
- Document the current behavior as canonical and request WZP align, OR
|
|
- Add a compatibility mode that accepts both derivation paths during a migration period.
|
|
|
|
**Why:** This is a prerequisite for WZP-FC-8 and ALL other integration tasks. If the same seed produces different keys in featherChat vs WZP, no integration is possible without key migration.
|
|
|
|
**WZP code reference:**
|
|
- `wzp-crypto/src/handshake.rs:34`: `Hkdf::<Sha256>::new(None, seed)` — uses None salt.
|
|
- featherChat `identity.rs:31`: `hkdf_derive(&self.0, b"", b"warzone-ed25519", 32)` — passes `b""` as salt parameter.
|
|
|
|
**Effort:** 1-2 hours investigation, potentially 0 code change if both resolve to the same thing (depends on `hkdf_derive` implementation).
|
|
|
|
**Questions before starting:**
|
|
- [ ] Read the `hkdf_derive` function implementation — does it pass `b""` as `Some(b"")` or convert it to `None`?
|
|
- [ ] If different: test with a known seed to confirm outputs differ.
|
|
|
|
---
|
|
|
|
### WZP-FC-10. WZP Web Bridge Shared Authentication
|
|
|
|
**What to change:**
|
|
- `warzone-server/src/routes/` — Add CORS headers or a proxy endpoint that allows `wzp-web` (running on port 8080, see `wzp-web/src/main.rs:51-52`) to authenticate against featherChat (port 7700).
|
|
- Alternatively, add a route `GET /v1/wzp/relay-config` that returns the WZP relay address and a time-limited auth token for the WZP relay, so the web client can connect to the relay with featherChat-issued credentials.
|
|
|
|
**Why:** The WZP web bridge (`wzp-web/src/main.rs`) currently has NO authentication. Any browser can open a WebSocket to `/ws/<room>` (`wzp-web/src/main.rs:93`) and join any room. It also has its own independent axum server with no connection to featherChat's identity system. The web client HTML (`wzp-web/static/index.html:85-101`) requests mic access and connects to a room by name — no identity verification whatsoever.
|
|
|
|
**WZP code reference:**
|
|
- `wzp-web/src/main.rs:141-258` — `handle_ws()` immediately connects to the relay with no auth check.
|
|
- `wzp-web/static/index.html:106-111` — WebSocket URL constructed from room name only, no auth token.
|
|
|
|
**Effort:** 1-2 days. Requires CORS configuration, token passing from featherChat web client to WZP web bridge, and potentially merging the two web servers.
|
|
|
|
**Questions before starting:**
|
|
- [ ] Should WZP web bridge be a separate server or integrated into featherChat's axum server (as additional routes)?
|
|
- [ ] Can we reuse featherChat's WASM identity (`warzone-wasm`) in the WZP web client?
|
|
- [ ] If separate servers: how do we share auth? Redirect? Shared cookie domain? Token in URL query param?
|
|
|
|
---
|
|
|
|
## Suggestions for WarzonePhone (other side)
|
|
|
|
These are advisory changes WZP should make to enable integration. We do not control the WZP codebase.
|
|
|
|
---
|
|
|
|
### WZP-S-1. Align HKDF Salt Convention
|
|
|
|
**What to change:** `wzp-crypto/src/handshake.rs:34`
|
|
```rust
|
|
// Current:
|
|
let hk = Hkdf::<Sha256>::new(None, seed);
|
|
// Should be (if featherChat uses non-None salt):
|
|
let hk = Hkdf::<Sha256>::new(Some(&[]), seed); // or whatever featherChat uses
|
|
```
|
|
|
|
**Why:** This is the CRITICAL prerequisite for shared identity. If featherChat's `hkdf_derive` passes `Some(b"")` vs WZP's `None`, the same seed produces different keys. The `hkdf` crate (v0.12, used by both) treats `None` as a zero-filled salt of hash length (32 bytes for SHA-256), while `Some(&[])` is an empty salt — these ARE different per RFC 5869 Section 2.2.
|
|
|
|
**Maps to featherChat task:** WZP-FC-8, WZP-FC-9.
|
|
|
|
**Recommendation:** WZP should align to featherChat's convention since featherChat has deployed users. This is a one-line change but invalidates any existing WZP identities (acceptable at v0.1.0).
|
|
|
|
---
|
|
|
|
### WZP-S-2. Accept featherChat Bearer Token on Relay Connection
|
|
|
|
**What to change:**
|
|
- `wzp-relay/src/main.rs:159-231` — Before accepting a QUIC connection into a room, require the client to present a featherChat bearer token via the reliable signaling stream. Add a new `SignalMessage` variant:
|
|
```rust
|
|
AuthToken { token: String },
|
|
```
|
|
The relay would call featherChat's `POST /v1/auth/validate` endpoint (WZP-FC-4) to verify the token, extracting the fingerprint. Reject the connection if validation fails.
|
|
|
|
- `wzp-relay/src/main.rs:173-179` — Currently, room name comes from QUIC SNI with no validation. After auth, verify that the authenticated fingerprint is a member of the corresponding featherChat group.
|
|
|
|
**Why:** Currently WZP relay accepts any QUIC connection with no authentication. The `RoomManager::join()` at `room.rs:85-93` takes any transport and adds it to the room. This is fine for testing but unacceptable in production — anyone who knows the relay address can join any call.
|
|
|
|
**Maps to featherChat task:** WZP-FC-4, WZP-FC-5.
|
|
|
|
---
|
|
|
|
### WZP-S-3. Add Signaling Bridge Mode to WZP Client
|
|
|
|
**What to change:**
|
|
- `wzp-client/src/handshake.rs:17-81` — The `perform_handshake()` function currently sends `SignalMessage::CallOffer` directly over the QUIC transport. For featherChat integration, add an alternative mode where signaling goes through featherChat's encrypted WS channel:
|
|
1. Client serializes `SignalMessage::CallOffer` to JSON
|
|
2. Wraps it in featherChat's `WireMessage::CallSignal`
|
|
3. Encrypts with Double Ratchet (existing featherChat session)
|
|
4. Sends via featherChat WS
|
|
5. Callee decrypts, extracts `SignalMessage`, replies via same path
|
|
6. Once both parties know each other's ephemeral keys, they connect directly via QUIC for media
|
|
|
|
- `wzp-client/src/lib.rs` — Add a new public function: `perform_handshake_via_featherchat()` that takes both a `MediaTransport` (for eventual QUIC media) and a featherChat WS connection (for signaling).
|
|
|
|
**Why:** Currently WZP assumes direct QUIC connectivity for both signaling and media (`wzp-client/src/handshake.rs:45` calls `transport.send_signal()` which sends over QUIC reliable stream). This fails behind NATs without relay. Using featherChat for signaling enables NAT traversal: signaling goes through featherChat's always-reachable server, while media goes P2P or via WZP relay.
|
|
|
|
**Maps to featherChat task:** WZP-FC-1, WZP-FC-3.
|
|
|
|
---
|
|
|
|
### WZP-S-4. Room Access Control in Relay
|
|
|
|
**What to change:**
|
|
- `wzp-relay/src/room.rs:85-93` — `RoomManager::join()` currently accepts any transport. Add an `authorized_fingerprints: Option<HashSet<String>>` field to `Room` struct (room.rs:32-34). If set, reject joins from fingerprints not in the set.
|
|
- `wzp-relay/src/main.rs:215-218` — After auth (WZP-S-2), check room authorization before calling `mgr.join()`.
|
|
|
|
**Why:** featherChat groups have membership lists (`groups.rs:23-28`, `GroupInfo.members: Vec<String>`). When a group call is initiated, only group members should be able to join the corresponding WZP room. Without this, any authenticated user can join any room by guessing the room name.
|
|
|
|
**Maps to featherChat task:** WZP-FC-5.
|
|
|
|
---
|
|
|
|
### WZP-S-5. Expose Identity Public Key in Relay Handshake
|
|
|
|
**What to change:**
|
|
- `wzp-relay/src/handshake.rs:19-80` — The `accept_handshake()` function receives the caller's `identity_pub` in the `CallOffer` but does not expose it after the handshake. The return type is `(Box<dyn CryptoSession>, QualityProfile)`. Change to also return the caller's identity public key and fingerprint:
|
|
```rust
|
|
pub async fn accept_handshake(...) -> Result<(Box<dyn CryptoSession>, QualityProfile, [u8; 32], [u8; 16]), ...>
|
|
// ^^identity ^^fingerprint
|
|
```
|
|
|
|
**Why:** The relay needs to know WHO connected (not just that they have a valid session key). This fingerprint is needed for: room authorization (WZP-S-4), presence reporting back to featherChat, and logging.
|
|
|
|
**Maps to featherChat task:** WZP-FC-2, WZP-FC-5, WZP-FC-6.
|
|
|
|
---
|
|
|
|
### WZP-S-6. Web Bridge Integration with featherChat Web Client
|
|
|
|
**What to change:**
|
|
- `wzp-web/src/main.rs` — Instead of running as a standalone server (port 8080), support being mounted as a sub-router inside featherChat's axum server. This means extracting the axum `Router` and WebSocket handler into a library function that featherChat can import:
|
|
```rust
|
|
pub fn wzp_web_routes(relay_addr: SocketAddr) -> Router { ... }
|
|
```
|
|
- `wzp-web/static/index.html:85-101` — The `startCall()` function should accept an auth token from the parent page (featherChat web client) and include it in the WebSocket connection URL or as a query parameter.
|
|
- The web client currently does its own AudioWorklet setup (`index.html:172-214`). If integrated into featherChat's web client, this would need to coexist with featherChat's WASM identity and UI.
|
|
|
|
**Why:** Running two separate web servers creates deployment complexity and prevents shared authentication. featherChat's web client already has a WASM-based identity system; the WZP web bridge should leverage it instead of being anonymous.
|
|
|
|
**Maps to featherChat task:** WZP-FC-10.
|
|
|
|
---
|
|
|
|
### WZP-S-7. Add `wzp-proto` as Optional Dependency for featherChat
|
|
|
|
**What to change:**
|
|
- `wzp-proto/Cargo.toml` — Ensure `wzp-proto` can be compiled independently (no transitive dependency on `quinn`, `audiopus`, or `codec2`). Currently it depends on `bytes`, `thiserror`, `async-trait`, `tokio`, `serde` — all lightweight.
|
|
- Publish `wzp-proto` types that featherChat needs: `SignalMessage`, `HangupReason`, `QualityProfile`, `QualityReport`.
|
|
|
|
**Why:** featherChat's protocol crate (`warzone-protocol`) needs to understand `SignalMessage` to properly type the `CallSignal::signal` field. Two options:
|
|
1. Keep `signal` as opaque `Vec<u8>` (no dependency needed, but no type safety)
|
|
2. Add `wzp-proto` as optional dependency and use `SignalMessage` directly
|
|
|
|
Option 1 is simpler for Phase 1. Option 2 enables server-side call state inference.
|
|
|
|
**Maps to featherChat task:** WZP-FC-1.
|
|
|
|
---
|
|
|
|
### WZP-S-8. CLI Client Seed Input
|
|
|
|
**What to change:**
|
|
- `wzp-client/src/cli.rs:45-131` — The CLI currently has no `--seed` or `--mnemonic` flag. Add:
|
|
```
|
|
--seed <hex> Use this 32-byte hex seed for identity
|
|
--mnemonic <words> Use BIP39 mnemonic for identity (same as featherChat)
|
|
```
|
|
Currently `wzp-client/src/cli.rs:133-179` (`main()`) connects to the relay with NO identity at all — it sends unencrypted media. The handshake functions (`wzp-client/src/handshake.rs`) exist but are never called from `cli.rs`.
|
|
|
|
**Why:** For shared identity to work, the WZP CLI must accept the same BIP39 mnemonic that featherChat uses, derive the identity via `WarzoneKeyExchange::from_identity_seed()`, and perform the authenticated handshake before sending media.
|
|
|
|
**Maps to featherChat task:** WZP-FC-8.
|
|
|
|
---
|
|
|
|
### WZP-S-9. Hardcoded Assumptions That Conflict with Integration
|
|
|
|
Several WZP patterns assume standalone operation:
|
|
|
|
1. **No auth on relay** (`wzp-relay/src/main.rs:159-231`): The accept loop processes every incoming QUIC connection without any identity check. `run_participant()` at room.rs:131 starts forwarding immediately.
|
|
|
|
2. **Room names from SNI only** (`wzp-relay/src/main.rs:173-179`): Room names come from the TLS SNI field, which is unencrypted and visible to network observers. For featherChat integration, room names should be opaque hashes, not human-readable group names.
|
|
|
|
3. **No signaling before media** (`wzp-client/src/cli.rs:159`): The CLI connects and immediately starts sending media packets (`run_silence`, `run_file_mode`). No handshake, no identity verification. The handshake module exists but is unused.
|
|
|
|
4. **Self-signed TLS everywhere** (`wzp-transport/src/config.rs`): Both client and server configs use self-signed certificates with verification disabled. This is fine for testing but means the relay identity cannot be verified.
|
|
|
|
5. **Web bridge lacks codec negotiation** (`wzp-web/src/main.rs:169`): The web bridge uses `CallConfig::default()` (GOOD profile, Opus 24k) without negotiation. The relay-side `CallEncoder`/`CallDecoder` may use a different profile than the peer client, causing decode failures.
|
|
|
|
6. **No connection to featherChat key registry** (`wzp-crypto/src/handshake.rs:79-88`): `WarzoneKeyExchange::verify()` checks Ed25519 signatures but has no way to verify that the signing key belongs to a known contact. featherChat's key registry (`routes/keys.rs:74-104`, `GET /v1/keys/:fingerprint`) provides this — WZP should query it.
|
|
|
|
**Maps to featherChat tasks:** WZP-FC-4, WZP-FC-5, WZP-FC-8.
|