From 06b8d5855b3549dd083dbe42ee81bfc292f66855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85berg?= Date: Tue, 5 May 2026 10:01:17 +0200 Subject: [PATCH] Rename "proxy" to "relay" throughout the project The zero-knowledge WebSocket server is more accurately a relay than a proxy: it routes opaque ciphertext between authenticated peers without inspecting payloads. This rename aligns crate, type, and user-facing terminology with that role. Crate renames (workspace version bumped 0.11.0 -> 0.12.0): - ap-proxy -> ap-relay - ap-proxy-client -> ap-relay-client - ap-proxy-protocol -> ap-relay-protocol Binary, types, and modules: - bin ap-proxy -> ap-relay - ProxyServer -> RelayServer - ProxyClient (trait) -> RelayClient - DefaultProxyClient -> DefaultRelayClient - ProxyProtocolClient -> RelayProtocolClient - ProxyClientConfig -> RelayClientConfig - ProxyError -> RelayError - ClientError::ProxyAuthFailed -> RelayAuthFailed - src/proxy.rs -> src/relay.rs - server/proxy_server.rs -> server/relay_server.rs - tests/websocket_proxy.rs -> tests/websocket_relay.rs - js-wasm proxy_client.rs -> relay_client.rs User-facing surface: - CLI flag --proxy-url -> --relay-url, DEFAULT_PROXY_URL -> DEFAULT_RELAY_URL - Log/help strings, example bindings (Python, Swift, JS) updated - protocol-v0.md, README, CONTRIBUTING, CLAUDE.md, release.yml updated Breaking change for API consumers and crates.io: old crate names stay stale; downstream callers of ProxyClient/ProxyError/--proxy-url must update. The wire-protocol Messages enum is unchanged. --- .claude/CLAUDE.md | 122 +- .github/workflows/release.yml | 6 +- CONTRIBUTING.md | 22 +- Cargo.lock | 44 +- Cargo.toml | 22 +- README.md | 2 +- crates/ap-cli/Cargo.toml | 4 +- crates/ap-cli/src/command/connect.rs | 48 +- crates/ap-cli/src/command/listen.rs | 22 +- crates/ap-cli/src/command/mod.rs | 12 +- crates/ap-cli/src/command/output.rs | 2 +- crates/ap-cli/src/command/run.rs | 10 +- crates/ap-cli/src/command/util.rs | 8 +- crates/ap-cli/src/main.rs | 4 +- .../ap-cli/src/storage/connection_storage.rs | 2 +- crates/ap-cli/src/storage/identity_storage.rs | 2 +- crates/ap-client/Cargo.toml | 8 +- crates/ap-client/src/clients/remote_client.rs | 58 +- crates/ap-client/src/clients/user_client.rs | 52 +- crates/ap-client/src/error.rs | 14 +- crates/ap-client/src/lib.rs | 30 +- .../ap-client/src/memory_connection_store.rs | 2 +- crates/ap-client/src/{proxy.rs => relay.rs} | 34 +- crates/ap-client/src/traits.rs | 4 +- crates/ap-client/src/types.rs | 2 +- crates/ap-client/tests/pairing.rs | 160 +-- ...{websocket_proxy.rs => websocket_relay.rs} | 102 +- .../Cargo.toml | 8 +- .../README.md | 16 +- .../src/config.rs | 40 +- .../src/lib.rs | 18 +- .../src/protocol_client.rs | 196 +-- .../Cargo.toml | 4 +- .../src/auth.rs | 60 +- .../src/error.rs | 10 +- .../src/lib.rs | 8 +- .../src/messages.rs | 10 +- .../src/rendezvous.rs | 10 +- crates/{ap-proxy => ap-relay}/Cargo.toml | 12 +- crates/{ap-proxy => ap-relay}/README.md | 26 +- .../{ap-proxy => ap-relay}/src/connection.rs | 6 +- crates/{ap-proxy => ap-relay}/src/lib.rs | 20 +- crates/{ap-proxy => ap-relay}/src/main.rs | 10 +- .../src/server/handler.rs | 26 +- .../{ap-proxy => ap-relay}/src/server/mod.rs | 22 +- .../src/server/relay_server.rs} | 44 +- .../tests/client_integration.rs | 36 +- crates/ap-uniffi/Cargo.toml | 6 +- crates/ap-uniffi/src/adapters.rs | 2 +- crates/ap-uniffi/src/error.rs | 4 +- crates/ap-uniffi/src/remote_client.rs | 16 +- crates/ap-uniffi/src/types.rs | 10 +- crates/ap-uniffi/src/user_client.rs | 16 +- crates/ap-uniffi/tests/integration.rs | 36 +- examples/js-wasm/Cargo.lock | 28 +- examples/js-wasm/Cargo.toml | 4 +- examples/js-wasm/README.md | 4 +- examples/js-wasm/package-lock.json | 1179 +++++++++++++++++ examples/js-wasm/src/client.rs | 16 +- examples/js-wasm/src/lib.rs | 2 +- .../src/{proxy_client.rs => relay_client.rs} | 22 +- examples/js-wasm/src/storage.rs | 2 +- examples/js-wasm/www/agent-access.js | 20 +- examples/js-wasm/www/app.js | 16 +- examples/js-wasm/www/index.html | 18 +- examples/python-pyo3/Cargo.lock | 255 +++- examples/python-pyo3/Cargo.toml | 4 +- examples/python-pyo3/README.md | 6 +- examples/python-pyo3/get.py | 4 +- examples/python-pyo3/pair.py | 4 +- examples/python-pyo3/src/client.rs | 18 +- examples/python-pyo3/src/lib.rs | 6 +- examples/python-pyo3/src/storage.rs | 2 +- examples/python-uniffi/README.md | 8 +- examples/python-uniffi/connect_request.py | 4 +- examples/rust-listener/Cargo.lock | 251 +++- examples/rust-listener/src/main.rs | 18 +- examples/rust-remote/Cargo.lock | 251 +++- examples/rust-remote/src/main.rs | 18 +- examples/shell/reusable-psk-connect.sh | 2 +- examples/skills/agent-access/SKILL.md | 4 +- examples/swift-uniffi/README.md | 6 +- .../Sources/ApUniffiExample/main.swift | 14 +- protocol-v0.md | 68 +- 84 files changed, 2661 insertions(+), 1063 deletions(-) rename crates/ap-client/src/{proxy.rs => relay.rs} (71%) rename crates/ap-client/tests/{websocket_proxy.rs => websocket_relay.rs} (92%) rename crates/{ap-proxy-client => ap-relay-client}/Cargo.toml (79%) rename crates/{ap-proxy-client => ap-relay-client}/README.md (73%) rename crates/{ap-proxy-client => ap-relay-client}/src/config.rs (68%) rename crates/{ap-proxy-client => ap-relay-client}/src/lib.rs (69%) rename crates/{ap-proxy-client => ap-relay-client}/src/protocol_client.rs (79%) rename crates/{ap-proxy-protocol => ap-relay-protocol}/Cargo.toml (86%) rename crates/{ap-proxy-protocol => ap-relay-protocol}/src/auth.rs (94%) rename crates/{ap-proxy-protocol => ap-relay-protocol}/src/error.rs (95%) rename crates/{ap-proxy-protocol => ap-relay-protocol}/src/lib.rs (57%) rename crates/{ap-proxy-protocol => ap-relay-protocol}/src/messages.rs (93%) rename crates/{ap-proxy-protocol => ap-relay-protocol}/src/rendezvous.rs (93%) rename crates/{ap-proxy => ap-relay}/Cargo.toml (72%) rename crates/{ap-proxy => ap-relay}/README.md (64%) rename crates/{ap-proxy => ap-relay}/src/connection.rs (85%) rename crates/{ap-proxy => ap-relay}/src/lib.rs (81%) rename crates/{ap-proxy => ap-relay}/src/main.rs (73%) rename crates/{ap-proxy => ap-relay}/src/server/handler.rs (94%) rename crates/{ap-proxy => ap-relay}/src/server/mod.rs (74%) rename crates/{ap-proxy/src/server/proxy_server.rs => ap-relay/src/server/relay_server.rs} (85%) rename crates/{ap-proxy => ap-relay}/tests/client_integration.rs (92%) create mode 100644 examples/js-wasm/package-lock.json rename examples/js-wasm/src/{proxy_client.rs => relay_client.rs} (95%) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 97bef34..b5d333f 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -1,18 +1,18 @@ # Agent Access SDK - Claude Code Configuration -Rust workspace implementing secure peer-to-peer credential sharing over a WebSocket proxy with end-to-end encryption via the Noise Protocol (NNpsk2 pattern). The proxy server is zero-knowledge and cannot decrypt traffic. +Rust workspace implementing secure peer-to-peer credential sharing over a WebSocket relay with end-to-end encryption via the Noise Protocol (NNpsk2 pattern). The relay server is zero-knowledge and cannot decrypt traffic. ## Overview ### What This Project Does -- Enables secure, encrypted credential retrieval between a trusted device (UserClient) and an untrusted device (RemoteClient) through a zero-knowledge WebSocket relay proxy -- Ships two binaries: `aac` (CLI for credential operations) and `ap-proxy` (WebSocket relay server) +- Enables secure, encrypted credential retrieval between a trusted device (UserClient) and an untrusted device (RemoteClient) through a zero-knowledge WebSocket relay +- Ships two binaries: `aac` (CLI for credential operations) and `ap-relay` (WebSocket relay server) - Consumed as a library (`ap-client`) by integrators building custom credential-sharing workflows ### Key Concepts -- **Zero-knowledge proxy** — the relay server authenticates clients by identity fingerprint but never sees plaintext credentials +- **Zero-knowledge relay** — the relay server authenticates clients by identity fingerprint but never sees plaintext credentials - **Noise NNpsk2** — a Noise Protocol pattern providing encryption with optional pre-shared key authentication; the "NN" means neither side presents a static key during the handshake, and "psk2" injects a PSK after the second message -- **IdentityFingerprint** — SHA256 hash of a client's public key (32 bytes / 64 hex chars), used as a stable addressing identifier in the proxy +- **IdentityFingerprint** — SHA256 hash of a client's public key (32 bytes / 64 hex chars), used as a stable addressing identifier in the relay - **HandshakeFingerprint** — 6-character hex string derived from transport keys (`SHA256(r2i_key || i2r_key)[0..3]`), used for out-of-band MITM verification - **Rendezvous code** — 9-character alphanumeric code (format `ABC-DEF-GHI`, 36^9 entropy) with 5-minute TTL for initial pairing - **PSK token** — `<64-hex-psk>_<64-hex-fingerprint>` (129 chars) for pre-authenticated pairing without rendezvous @@ -38,12 +38,12 @@ Rust workspace implementing secure peer-to-peer credential sharing over a WebSoc |+transport| XChaCha20-Poly1305 |+transport)| +----+-----+ +-----+----+ | | - ap-proxy-client ap-proxy-client + ap-relay-client ap-relay-client | WebSocket (JSON) | +--------------+ +--------------------+ v v +--------------+ - | ap-proxy | Zero-knowledge relay + | ap-relay | Zero-knowledge relay | (WebSocket | Auth + Rendezvous + | server) | Message routing +--------------+ @@ -78,7 +78,7 @@ crates/ │ │ ├── remote_client.rs # RemoteClient + notifications/requests │ │ └── user_client.rs # UserClient + notifications/requests │ ├── traits.rs # SessionStore, IdentityProvider, AuditLog -│ ├── proxy.rs # ProxyClient trait +│ ├── relay.rs # RelayClient trait │ ├── types.rs # CredentialData, ConnectionMode, PskToken, etc. │ ├── memory_session_store.rs │ ├── error.rs # ClientError @@ -94,27 +94,27 @@ crates/ │ ├── psk.rs # Psk, PskId generation │ ├── symmetric_key.rs # SymmetricKey (ZeroizeOnDrop) │ └── error.rs # NoiseProtocolError -├── ap-proxy/ # WebSocket proxy server binary + library +├── ap-relay/ # WebSocket relay server binary + library │ └── src/ │ ├── main.rs │ ├── lib.rs │ ├── server/ -│ │ ├── proxy_server.rs # ProxyServer::run() +│ │ ├── relay_server.rs # RelayServer::run() │ │ ├── handler.rs # Per-connection auth + message routing │ │ └── mod.rs │ └── connection.rs -├── ap-proxy-client/ # WebSocket client for proxy protocol +├── ap-relay-client/ # WebSocket client for relay protocol │ └── src/ │ ├── lib.rs -│ ├── protocol_client.rs # ProxyProtocolClient (WebSocket impl) +│ ├── protocol_client.rs # RelayProtocolClient (WebSocket impl) │ └── config.rs # IncomingMessage enum -├── ap-proxy-protocol/ # Shared wire protocol types +├── ap-relay-protocol/ # Shared wire protocol types │ └── src/ │ ├── lib.rs │ ├── messages.rs # Messages enum (AuthChallenge, Send, etc.) │ ├── auth.rs # IdentityKeyPair, Identity, Challenge, etc. │ ├── rendezvous.rs # RendezvousCode -│ └── error.rs # ProxyError +│ └── error.rs # RelayError ├── ap-error/ # FlatError trait │ └── src/ │ ├── lib.rs @@ -139,22 +139,22 @@ ap-cli (binary: aac) └── ap-client (protocol client library) ├── ap-noise (Noise handshake + encrypted transport) │ └── ap-error / ap-error-macro (error infrastructure) - ├── ap-proxy-client (WebSocket client) - │ └── ap-proxy-protocol (wire protocol types) - └── ap-proxy-protocol + ├── ap-relay-client (WebSocket client) + │ └── ap-relay-protocol (wire protocol types) + └── ap-relay-protocol ``` - **ap-noise** — Noise NNpsk2 handshake, `MultiDeviceTransport` for encrypted messaging, XChaCha20-Poly1305 transport encryption, session state persistence for resumption. -- **ap-proxy** — WebSocket proxy server (`ap-proxy` binary). Three-phase protocol: authentication, rendezvous, messaging. Default listen address: `ws://localhost:8080`. -- **ap-proxy-client** — `ProxyProtocolClient` WebSocket client library for connecting to the proxy. -- **ap-proxy-protocol** — Shared wire protocol types (`Messages`, `Challenge`, `Identity`, `RendezvousCode`, etc.). -- **ap-client** — `RemoteClient` (untrusted device requesting credentials) and `UserClient` (trusted device serving credentials). Uses trait abstractions (`SessionStore`, `IdentityProvider`, `ProxyClient`) and async event/response channels. +- **ap-relay** — WebSocket relay server (`ap-relay` binary). Three-phase protocol: authentication, rendezvous, messaging. Default listen address: `ws://localhost:8080`. +- **ap-relay-client** — `RelayProtocolClient` WebSocket client library for connecting to the relay. +- **ap-relay-protocol** — Shared wire protocol types (`Messages`, `Challenge`, `Identity`, `RendezvousCode`, etc.). +- **ap-client** — `RemoteClient` (untrusted device requesting credentials) and `UserClient` (trusted device serving credentials). Uses trait abstractions (`SessionStore`, `IdentityProvider`, `RelayClient`) and async event/response channels. - **ap-cli** (`aac` binary) — CLI driver with interactive TUI (ratatui + crossterm) and non-interactive single-shot mode. Subcommands: `connect`, `listen`, `connections` (with `clear`/`list`), `run`. Integrates with `bw` CLI for credential lookup via `bw get item`. - **ap-error / ap-error-macro** — Error handling utilities ported from Bitwarden's `sdk-internal`. Provides the `FlatError` trait and `#[ap_error(flat)]` proc macro. ### Key Principles -1. **Zero-knowledge relay** — the proxy authenticates identities but never sees plaintext; all credential data is encrypted end-to-end via Noise -2. **Trait-based abstractions** — `SessionStore`, `IdentityProvider`, `ProxyClient`, and `AuditLog` decouple protocol logic from storage, transport, and auditing implementations +1. **Zero-knowledge relay** — the relay authenticates identities but never sees plaintext; all credential data is encrypted end-to-end via Noise +2. **Trait-based abstractions** — `SessionStore`, `IdentityProvider`, `RelayClient`, and `AuditLog` decouple protocol logic from storage, transport, and auditing implementations 3. **Event-response channels** — clients emit `Notification` events (fire-and-forget via `mpsc`) and `Request` events (requiring a `oneshot::Sender` reply), keeping the protocol loop decoupled from UI/business logic ### Core Patterns @@ -163,7 +163,7 @@ ap-cli (binary: aac) **Purpose**: Decouple protocol logic from storage, identity, and transport implementations so consumers can plug in their own backends. -**Key traits** (`crates/ap-client/src/traits.rs` and `crates/ap-client/src/proxy.rs`): +**Key traits** (`crates/ap-client/src/traits.rs` and `crates/ap-client/src/relay.rs`): ```rust #[async_trait] @@ -181,7 +181,7 @@ pub trait IdentityProvider: Send + Sync { } #[async_trait] -pub trait ProxyClient: Send + Sync { +pub trait RelayClient: Send + Sync { async fn connect(&mut self, identity: IdentityKeyPair) -> Result, ClientError>; async fn request_rendezvous(&self) -> Result<(), ClientError>; @@ -197,7 +197,7 @@ pub trait AuditLog: Send + Sync { } ``` -**Built-in implementations**: `MemorySessionStore`, `MemoryIdentityProvider`, `NoOpAuditLog` (all in `ap-client`); `ProxyProtocolClient` (in `ap-proxy-client`). +**Built-in implementations**: `MemorySessionStore`, `MemoryIdentityProvider`, `NoOpAuditLog` (all in `ap-client`); `RelayProtocolClient` (in `ap-relay-client`). #### Event-Response Channel Pattern @@ -229,7 +229,7 @@ pub enum UserClientRequest { **Usage**: The caller spawns a task to drain `notifications` and `requests`, replying via the `oneshot::Sender` on each request. -#### Three-Phase Proxy Protocol +#### Three-Phase Relay Protocol 1. **Authentication**: Server sends `AuthChallenge` (32-byte nonce) -> client replies with `AuthResponse` (COSE_Sign1 signature + COSE public key identity) -> server verifies and registers connection by `IdentityFingerprint`. 5-second timeout. 2. **Rendezvous** (optional, new connections only): Client sends `GetRendezvous` -> server generates 9-char code (5-minute TTL, single-use) -> discovering client sends `GetIdentity(code)` -> server returns target's `IdentityInfo`. @@ -247,12 +247,12 @@ Noise handshake and encrypted credential payloads are layered on top as `Protoco cargo build # Build all crates (debug) cargo build --release # Build all crates (release) cargo run --bin aac # Run the CLI application -cargo run --bin ap-proxy # Run the WebSocket proxy server +cargo run --bin ap-relay # Run the WebSocket relay server ``` ### Adding a New Client Trait Implementation -The most common development task is implementing a new `SessionStore`, `IdentityProvider`, or `ProxyClient` for a new platform or backend. +The most common development task is implementing a new `SessionStore`, `IdentityProvider`, or `RelayClient` for a new platform or backend. **1. Implement the trait** (e.g., `my_session_store.rs`) ```rust @@ -275,11 +275,11 @@ impl SessionStore for MySessionStore { let handle = RemoteClient::connect( Box::new(my_identity_provider), Box::new(my_session_store), - Box::new(ProxyProtocolClient::new(proxy_url)), + Box::new(RelayProtocolClient::new(relay_url)), ).await?; ``` -**3. Write tests** — see the mock proxy pattern in `crates/ap-client/tests/pairing.rs` +**3. Write tests** — see the mock relay pattern in `crates/ap-client/tests/pairing.rs` ### Adding a New CLI Subcommand @@ -364,7 +364,7 @@ pub enum ConnectionMode { ### Demo Flow -1. Start proxy: `cargo run --bin ap-proxy` +1. Start relay: `cargo run --bin ap-relay` 2. Start user-client: `cargo run --bin aac -- listen` 3. Copy the pairing token (9-char code, e.g. `ABC-DEF-GHI`) from step 2, connect: `cargo run --bin aac -- connect --token ` 4. Type domains on the connect side to request credentials; approve on the listen side @@ -422,7 +422,7 @@ pub struct SessionInfo { ### Wire Protocol Messages ```rust -// Proxy protocol (crates/ap-proxy-protocol/src/messages.rs) +// Relay protocol (crates/ap-relay-protocol/src/messages.rs) pub enum Messages { AuthChallenge(Challenge), AuthResponse(Identity, ChallengeResponse), @@ -446,7 +446,7 @@ enum ProtocolMessage { | Layer | Format | |-------|--------| -| Proxy wire protocol | JSON (WebSocket text frames) | +| Relay wire protocol | JSON (WebSocket text frames) | | Identity key files (`~/.access-protocol/*.key`) | CBOR via COSE | | Session cache (`~/.access-protocol/session_cache_*.json`) | JSON | | Transport state (inside session cache) | CBOR byte array | @@ -480,10 +480,10 @@ Storage directory: `~/.access-protocol/` |-----------|-----------|---------|----------| | `InitiatorHandshake` / `ResponderHandshake` | Noise NNpsk2 (Curve25519 or ML-KEM-768) | Key agreement | `ap-noise/src/handshake.rs` | | `MultiDeviceTransport` | XChaCha20-Poly1305 (random nonce) | Transport encryption | `ap-noise/src/transport.rs` | -| `IdentityKeyPair` | Ed25519 or ML-DSA-65 | Identity signing | `ap-proxy-protocol/src/auth.rs` | -| `Challenge::sign()` | COSE_Sign1 | Proxy authentication | `ap-proxy-protocol/src/auth.rs` | +| `IdentityKeyPair` | Ed25519 or ML-DSA-65 | Identity signing | `ap-relay-protocol/src/auth.rs` | +| `Challenge::sign()` | COSE_Sign1 | Relay authentication | `ap-relay-protocol/src/auth.rs` | | `HandshakeFingerprint` | SHA256 (first 3 bytes) | MITM verification | `ap-noise/src/handshake.rs` | -| `IdentityFingerprint` | SHA256 (full 32 bytes) | Client addressing | `ap-proxy-protocol/src/auth.rs` | +| `IdentityFingerprint` | SHA256 (full 32 bytes) | Client addressing | `ap-relay-protocol/src/auth.rs` | | `Psk::id()` | SHA256 (first 8 bytes) | Non-secret PSK lookup | `ap-noise/src/psk.rs` | ### Transport Security Constants @@ -493,27 +493,27 @@ const REKEY_INTERVAL: u64 = 86400; // 24 hours — automatic re-key inter const MAX_REKEY_GAP: u64 = 1024; // Max chain counter gap before Desynchronized error const MAX_MESSAGE_AGE: u64 = 86400; // 1 day — reject older messages const CLOCK_SKEW_TOLERANCE: u64 = 60; // 1 minute — future message tolerance -const CLIENT_INACTIVITY_TIMEOUT: Duration = Duration::from_secs(120); // Proxy disconnects idle clients +const CLIENT_INACTIVITY_TIMEOUT: Duration = Duration::from_secs(120); // Relay disconnects idle clients ``` ### Environment Configuration | Variable | Required | Description | Default | |----------|----------|-------------|---------| -| `BIND_ADDR` | No | Proxy server bind address | `127.0.0.1:8080` | -| `RUST_LOG` | No | Tracing log filter | Proxy: `INFO`, CLI: `WARN` | +| `BIND_ADDR` | No | Relay server bind address | `127.0.0.1:8080` | +| `RUST_LOG` | No | Tracing log filter | Relay: `INFO`, CLI: `WARN` | | `LLM` | No | Disables color output (set by agents) | unset | | `NO_COLOR` | No | Disables color output (standard) | unset | | `GIT_HASH` | No | Injected by CI for version string | unset | ### Feature Flags -- `experimental-post-quantum-crypto` — Enables ML-KEM-768 key exchange and ML-DSA-65 signatures. **On by default** in `ap-proxy` and `ap-proxy-client`; off by default in `ap-noise`. When enabled, `IdentityKeyPair::generate()` uses ML-DSA-65 instead of Ed25519, and the default ciphersuite becomes `PQNNpsk2_Kyber768_XChaCha20Poly1305`. -- `native-websocket` — Enables tokio-tungstenite WebSocket transport in `ap-proxy-client`. **On by default**. Disable for WASM targets. +- `experimental-post-quantum-crypto` — Enables ML-KEM-768 key exchange and ML-DSA-65 signatures. **On by default** in `ap-relay` and `ap-relay-client`; off by default in `ap-noise`. When enabled, `IdentityKeyPair::generate()` uses ML-DSA-65 instead of Ed25519, and the default ciphersuite becomes `PQNNpsk2_Kyber768_XChaCha20Poly1305`. +- `native-websocket` — Enables tokio-tungstenite WebSocket transport in `ap-relay-client`. **On by default**. Disable for WASM targets. ### Authentication & Authorization -- **Proxy authentication**: challenge-response using COSE_Sign1 signatures over a 32-byte random nonce. The server verifies the signature and registers the connection by `IdentityFingerprint`. +- **Relay authentication**: challenge-response using COSE_Sign1 signatures over a 32-byte random nonce. The server verifies the signature and registers the connection by `IdentityFingerprint`. - **Peer authentication (PSK mode)**: both sides prove knowledge of a pre-shared key during the Noise handshake. No additional verification needed. - **Peer authentication (Rendezvous mode)**: no pre-shared secret; users must verify the 6-character `HandshakeFingerprint` out-of-band to prevent MITM. - **No stored credentials on disk without encryption** — identity keypairs are COSE-encoded but not encrypted at rest (relies on filesystem permissions). @@ -526,11 +526,11 @@ const CLIENT_INACTIVITY_TIMEOUT: Duration = Duration::from_secs(120); // Proxy d ``` crates/ap-client/tests/ -├── pairing.rs # Mock-proxy integration tests (PSK, rendezvous, reconnect, backpressure) -└── websocket_proxy.rs # Real-proxy E2E tests (credential exchange, multi-device, persistence) +├── pairing.rs # Mock-relay integration tests (PSK, rendezvous, reconnect, backpressure) +└── websocket_relay.rs # Real-relay E2E tests (credential exchange, multi-device, persistence) -crates/ap-proxy/tests/ -└── client_integration.rs # Proxy protocol tests (auth, messaging, broadcast, cleanup) +crates/ap-relay/tests/ +└── client_integration.rs # Relay protocol tests (auth, messaging, broadcast, cleanup) # Unit tests are embedded in source files via #[cfg(test)] modules throughout all crates ``` @@ -539,7 +539,7 @@ crates/ap-proxy/tests/ ```bash cargo test # Run all tests -cargo test -p ap-proxy # Run proxy tests only +cargo test -p ap-relay # Run relay tests only cargo test -p ap-client # Run client tests only cargo test --test pairing # Run a specific integration test cargo test --test client_integration @@ -547,36 +547,36 @@ cargo test --test client_integration ### Writing Tests -**Mock Proxy Pattern** (for unit/integration tests without a real server): +**Mock Relay Pattern** (for unit/integration tests without a real server): ```rust // Create paired mock proxies that relay messages to each other -let (user_proxy, remote_proxy) = create_mock_proxy_pair(user_fingerprint, remote_fingerprint); +let (user_relay, remote_relay) = create_mock_relay_pair(user_fingerprint, remote_fingerprint); let UserClientHandle { client, notifications, requests } = UserClient::connect( Box::new(MemoryIdentityProvider::new()), Box::new(MemorySessionStore::new()), - Box::new(user_proxy), + Box::new(user_relay), None, ).await?; ``` -See `crates/ap-client/tests/pairing.rs` for the full `MockProxyClient` and `create_mock_proxy_pair()` implementations. +See `crates/ap-client/tests/pairing.rs` for the full `MockRelayClient` and `create_mock_relay_pair()` implementations. -**Real Proxy E2E Pattern** (for full-stack tests): +**Real Relay E2E Pattern** (for full-stack tests): ```rust -// Start an ephemeral proxy on a random port +// Start an ephemeral relay on a random port async fn start_test_server() -> SocketAddr { let listener = TcpListener::bind("127.0.0.1:0").await.expect("should bind"); let addr = listener.local_addr().expect("should get addr"); - let server = ProxyServer::new(addr); + let server = RelayServer::new(addr); tokio::spawn(async move { server.run_with_listener(listener).await.ok() }); addr } ``` -See `crates/ap-client/tests/websocket_proxy.rs` for full examples. +See `crates/ap-client/tests/websocket_relay.rs` for full examples. **Key testing conventions:** - Always wrap async operations in `tokio::time::timeout()` to prevent hanging tests @@ -586,7 +586,7 @@ See `crates/ap-client/tests/websocket_proxy.rs` for full examples. ### Test Environment -- **Dev dependencies**: `ap-proxy` (for `ProxyServer` in E2E tests), `tokio` with `rt-multi-thread`, `macros`, `time`, `test-util` +- **Dev dependencies**: `ap-relay` (for `RelayServer` in E2E tests), `tokio` with `rt-multi-thread`, `macros`, `time`, `test-util` - **No external services required** — all tests are self-contained - **No test fixtures on disk** — credentials and sessions are created in-memory per test @@ -604,7 +604,7 @@ See `crates/ap-client/tests/websocket_proxy.rs` for full examples. - `snake_case` for: variables, functions, modules, file names - `PascalCase` for: types, traits, enum variants - `SCREAMING_SNAKE_CASE` for: constants -- Binary names: `aac` (CLI), `ap-proxy` (server) +- Binary names: `aac` (CLI), `ap-relay` (server) - Crate names: `ap-` prefix for all workspace crates ### Rust Conventions @@ -687,7 +687,7 @@ Workspace version managed in root `Cargo.toml`: currently **0.7.0**. ### Publishing Crates are published to crates.io in dependency order via CI (`release.yml`, triggered by `v*` tags): -1. `ap-error-macro` -> 2. `ap-error` -> 3. `ap-noise` -> 4. `ap-proxy-protocol` -> 5. `ap-proxy-client` -> 6. `ap-proxy` -> 7. `ap-client` -> 8. `ap-cli` +1. `ap-error-macro` -> 2. `ap-error` -> 3. `ap-noise` -> 4. `ap-relay-protocol` -> 5. `ap-relay-client` -> 6. `ap-relay` -> 7. `ap-client` -> 8. `ap-cli` --- @@ -721,9 +721,9 @@ Crates are published to crates.io in dependency order via CI (`release.yml`, tri ### Debug Tips -- Set `RUST_LOG=debug` (or `--verbose` on the CLI) to see Noise handshake steps and proxy protocol messages +- Set `RUST_LOG=debug` (or `--verbose` on the CLI) to see Noise handshake steps and relay protocol messages - `RUST_LOG=ap_noise=trace` for transport-level encrypt/decrypt traces -- The proxy logs authenticated identity fingerprints at INFO level — use this to verify connections +- The relay logs authenticated identity fingerprints at INFO level — use this to verify connections - Use `aac connections list` to inspect cached sessions and their transport state --- diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2203fa9..085c757 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -174,9 +174,9 @@ jobs: publish_crate ap-error-macro publish_crate ap-error publish_crate ap-noise - publish_crate ap-proxy-protocol - publish_crate ap-proxy-client - publish_crate ap-proxy + publish_crate ap-relay-protocol + publish_crate ap-relay-client + publish_crate ap-relay publish_crate ap-client # Last crate — no need to wait for indexing diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f1f38b6..5aa1485 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,16 +11,16 @@ It contains: * An end-to-end encrypted tunnel, using Noise * A Rust SDK for establishing a tunnel, sending requests, and responding to them * A CLI tool for requesting / releasing credentials -* A proxy server for demo/development purposes +* A relay server for demo/development purposes ## Crate Structure * `ap-error` - Error handling utilities for access-protocol. Re-exports the `ap_error` proc macro and provides the `FlatError` trait. * `ap-error-macro` - Proc macro for generating error types with `FlatError` trait implementation. Simplified version of `bitwarden-error-macro` that only supports the `flat` error type for CLI use. * `ap-noise` - Multi-device Noise-based Protocol implementation using the NNpsk2 pattern for secure channel establishment with PSK-based authentication. -* `ap-proxy` - Zero-knowledge WebSocket proxy server enabling secure rendezvous between remote and user clients. Runs as a standalone binary with environment-based configuration. -* `ap-client` - Remote and user client implementations for connecting through the proxy using the Noise Protocol. -* `ap-cli` - CLI interface for connecting to a user-client through a proxy to request credentials over a secure Noise Protocol channel. Manages session caching and device keypair storage. +* `ap-relay` - Zero-knowledge WebSocket relay server enabling secure rendezvous between remote and user clients. Runs as a standalone binary with environment-based configuration. +* `ap-client` - Remote and user client implementations for connecting through the relay using the Noise Protocol. +* `ap-cli` - CLI interface for connecting to a user-client through a relay to request credentials over a secure Noise Protocol channel. Manages session caching and device keypair storage. ## Building @@ -28,15 +28,15 @@ Run `cargo build` in this directory. This is a standalone workspace and has no d ## Running -### Proxy Server +### Relay Server -Run the `ap-proxy` binary to start the WebSocket proxy server: +Run the `ap-relay` binary to start the WebSocket relay server: ```shell -cargo run -p ap-proxy +cargo run -p ap-relay ``` -The proxy binds to `127.0.0.1:8080` by default. Set the `BIND_ADDR` environment variable to override. +The relay binds to `127.0.0.1:8080` by default. Set the `BIND_ADDR` environment variable to override. ### CLI @@ -48,13 +48,13 @@ Retrieve credentials from your password manager over a secure channel Usage: aac [OPTIONS] [COMMAND] Commands: - connect Connect to proxy and request credentials + connect Connect to relay and request credentials listen Listen for remote client connections (user-client mode) connections Manage connections help Print this message or the help of the given subcommand(s) Options: - --proxy-url Proxy server URL [default: wss://ap.lesspassword.dev] + --relay-url Relay server URL [default: wss://ap.lesspassword.dev] --token Token (rendezvous code or PSK token) --session Session fingerprint to reconnect to (hex string) --no-cache Disable session caching @@ -65,7 +65,7 @@ Options: ### Demo Flow -1. Start the proxy server with `cargo run -p ap-proxy` +1. Start the relay server with `cargo run -p ap-relay` 2. Start the user-client side with `cargo run --bin aac -- listen` 3. Enter the pairing token from step 2 into the `--token` argument of `aac connect` 4. Now `aac`, taking the role of the remote client, will let you type in domains to request credentials for, and you will approve them on the `listen` side from step 2 diff --git a/Cargo.lock b/Cargo.lock index 4787450..282dd36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,12 +100,12 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "ap-cli" -version = "0.11.0" +version = "0.12.0" dependencies = [ "ap-client", "ap-noise", - "ap-proxy-client", - "ap-proxy-protocol", + "ap-relay-client", + "ap-relay-protocol", "async-trait", "clap", "color-eyre", @@ -125,13 +125,13 @@ dependencies = [ [[package]] name = "ap-client" -version = "0.11.0" +version = "0.12.0" dependencies = [ "ap-error", "ap-noise", - "ap-proxy", - "ap-proxy-client", - "ap-proxy-protocol", + "ap-relay", + "ap-relay-client", + "ap-relay-protocol", "async-trait", "base64", "futures-util", @@ -150,14 +150,14 @@ dependencies = [ [[package]] name = "ap-error" -version = "0.11.0" +version = "0.12.0" dependencies = [ "ap-error-macro", ] [[package]] name = "ap-error-macro" -version = "0.11.0" +version = "0.12.0" dependencies = [ "darling 0.20.11", "proc-macro2", @@ -167,7 +167,7 @@ dependencies = [ [[package]] name = "ap-noise" -version = "0.11.0" +version = "0.12.0" dependencies = [ "ap-error", "base64", @@ -187,11 +187,11 @@ dependencies = [ ] [[package]] -name = "ap-proxy" -version = "0.11.0" +name = "ap-relay" +version = "0.12.0" dependencies = [ - "ap-proxy-client", - "ap-proxy-protocol", + "ap-relay-client", + "ap-relay-protocol", "futures-util", "serde_json", "tokio", @@ -201,10 +201,10 @@ dependencies = [ ] [[package]] -name = "ap-proxy-client" -version = "0.11.0" +name = "ap-relay-client" +version = "0.12.0" dependencies = [ - "ap-proxy-protocol", + "ap-relay-protocol", "futures-util", "serde_json", "tokio", @@ -213,8 +213,8 @@ dependencies = [ ] [[package]] -name = "ap-proxy-protocol" -version = "0.11.0" +name = "ap-relay-protocol" +version = "0.12.0" dependencies = [ "ap-error", "coset", @@ -234,9 +234,9 @@ version = "0.1.0" dependencies = [ "ap-client", "ap-noise", - "ap-proxy", - "ap-proxy-client", - "ap-proxy-protocol", + "ap-relay", + "ap-relay-client", + "ap-relay-protocol", "async-trait", "thiserror", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 2014d41..27137e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,9 +5,9 @@ members = [ "crates/ap-error-macro", "crates/ap-client", "crates/ap-noise", - "crates/ap-proxy", - "crates/ap-proxy-client", - "crates/ap-proxy-protocol", + "crates/ap-relay", + "crates/ap-relay-client", + "crates/ap-relay-protocol", "crates/ap-cli", "crates/ap-uniffi", ] @@ -19,7 +19,7 @@ exclude = [ ] [workspace.package] -version = "0.11.0" +version = "0.12.0" authors = ["Bitwarden Inc"] edition = "2024" rust-version = "1.85" @@ -28,13 +28,13 @@ repository = "https://github.com/bitwarden/agent-access" [workspace.dependencies] # Internal crates -ap-error = { path = "crates/ap-error", version = "0.11.0" } -ap-error-macro = { path = "crates/ap-error-macro", version = "0.11.0" } -ap-client = { path = "crates/ap-client", version = "0.11.0" } -ap-noise = { path = "crates/ap-noise", version = "0.11.0" } -ap-proxy = { path = "crates/ap-proxy", version = "0.11.0" } -ap-proxy-client = { path = "crates/ap-proxy-client", version = "0.11.0", default-features = false } -ap-proxy-protocol = { path = "crates/ap-proxy-protocol", version = "0.11.0" } +ap-error = { path = "crates/ap-error", version = "0.12.0" } +ap-error-macro = { path = "crates/ap-error-macro", version = "0.12.0" } +ap-client = { path = "crates/ap-client", version = "0.12.0" } +ap-noise = { path = "crates/ap-noise", version = "0.12.0" } +ap-relay = { path = "crates/ap-relay", version = "0.12.0" } +ap-relay-client = { path = "crates/ap-relay-client", version = "0.12.0", default-features = false } +ap-relay-protocol = { path = "crates/ap-relay-protocol", version = "0.12.0" } # External dependencies async-trait = "0.1" diff --git a/README.md b/README.md index a97f48a..a9d61b8 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ It contains: * An end-to-end encrypted tunnel, using Noise * A Rust SDK for establishing a tunnel, sending requests, and responding to them * A CLI tool for requesting / releasing credentials -* A proxy server for demo/development purposes +* A relay server for demo/development purposes

diff --git a/crates/ap-cli/Cargo.toml b/crates/ap-cli/Cargo.toml index 835e409..afc82d8 100644 --- a/crates/ap-cli/Cargo.toml +++ b/crates/ap-cli/Cargo.toml @@ -14,8 +14,8 @@ path = "src/main.rs" [dependencies] ap-noise = { workspace = true } -ap-proxy-client = { workspace = true, features = ["native-websocket"] } -ap-proxy-protocol = { workspace = true } +ap-relay-client = { workspace = true, features = ["native-websocket"] } +ap-relay-protocol = { workspace = true } ap-client = { workspace = true, features = ["native-websocket"] } async-trait = { workspace = true } clap = { workspace = true } diff --git a/crates/ap-cli/src/command/connect.rs b/crates/ap-cli/src/command/connect.rs index 0608952..c357dba 100644 --- a/crates/ap-cli/src/command/connect.rs +++ b/crates/ap-cli/src/command/connect.rs @@ -1,10 +1,10 @@ //! Connect command implementation //! -//! Handles the interactive session for connecting to a proxy +//! Handles the interactive session for connecting to a relay //! and requesting credentials over a secure Noise Protocol channel. use ap_client::{ - ClientError, ConnectionInfo, ConnectionMode, ConnectionStore, DefaultProxyClient, + ClientError, ConnectionInfo, ConnectionMode, ConnectionStore, DefaultRelayClient, IdentityFingerprint, IdentityProvider, Psk, PskToken, RemoteClient, RemoteClientFingerprintReply, RemoteClientNotification, RemoteClientRequest, }; @@ -27,7 +27,7 @@ use super::util::{format_connect_notification, format_relative_time}; use crate::storage::{FileConnectionCache, FileIdentityStorage}; use ap_client::MemoryConnectionStore; -use super::DEFAULT_PROXY_URL; +use super::DEFAULT_RELAY_URL; /// Arguments for the connect command #[derive(Args)] @@ -44,9 +44,9 @@ AUTOMATION / AGENT / LLM USE: --output json returns structured JSON to stdout (status to stderr). Exit codes: 0=success, 1=error, 2=connection failed, 3=auth failed, 4=not found, 5=fingerprint mismatch")] pub struct ConnectArgs { - /// Proxy server URL - #[arg(long, default_value = DEFAULT_PROXY_URL)] - pub proxy_url: String, + /// Relay server URL + #[arg(long, default_value = DEFAULT_RELAY_URL)] + pub relay_url: String, /// Token (rendezvous code or PSK token) #[arg(long, env = "AAC_TOKEN", conflicts_with = "session")] @@ -92,7 +92,7 @@ impl ConnectArgs { if let Some(query) = query { run_single_shot( - self.proxy_url, + self.relay_url, self.token, self.session, self.ephemeral_connection, @@ -103,7 +103,7 @@ impl ConnectArgs { .await } else { run_interactive_session( - self.proxy_url, + self.relay_url, self.token, self.session, self.ephemeral_connection, @@ -241,7 +241,7 @@ fn spawn_pairing( /// Run an interactive session for requesting credentials async fn run_interactive_session( - proxy_url: String, + relay_url: String, token: Option, session_fingerprint: Option, ephemeral_connection: bool, @@ -278,7 +278,7 @@ async fn run_interactive_session( // otherwise they are consumed when the user picks a session or enters a token. let mut deferred_identity: Option> = Some(identity_provider); let mut deferred_connection_store: Option> = Some(connection_store); - let deferred_proxy_url = proxy_url; + let deferred_relay_url = relay_url; let mut notification_rx: Option> = None; let mut request_rx: Option> = None; @@ -300,7 +300,7 @@ async fn run_interactive_session( deferred_connection_store .take() .expect("connection store consumed twice"), - &deferred_proxy_url, + &deferred_relay_url, ) .await { @@ -384,7 +384,7 @@ async fn run_interactive_session( match start_connection( deferred_identity.take().expect("identity consumed twice"), deferred_connection_store.take().expect("connection store consumed twice"), - &deferred_proxy_url, + &deferred_relay_url, ).await { Ok((nrx, rrx, c)) => { pairing_task = Some(spawn_pairing(&c, &mode, verify_fingerprint)); @@ -436,7 +436,7 @@ async fn run_interactive_session( match start_connection( deferred_identity.take().expect("identity consumed twice"), deferred_connection_store.take().expect("connection store consumed twice"), - &deferred_proxy_url, + &deferred_relay_url, ).await { Ok((nrx, rrx, c)) => { pairing_task = Some(spawn_pairing(&c, &mode, verify_fingerprint)); @@ -703,12 +703,12 @@ async fn run_interactive_session( /// This is the agent/LLM-friendly code path. It never initializes ratatui, /// prints structured output to stdout, status to stderr, and exits with a /// well-defined exit code. -/// Connect to the proxy, fetch a single credential, and return it. +/// Connect to the relay, fetch a single credential, and return it. /// /// Shared by `run_single_shot` and the `run` subcommand. Returns the /// credential on success, or an error that the caller can format/handle. pub(super) async fn fetch_credential( - proxy_url: &str, + relay_url: &str, token: Option<&str>, session_fingerprint: Option<&str>, ephemeral_connection: bool, @@ -727,10 +727,10 @@ pub(super) async fn fetch_credential( let cached_connections = connection_store.list().await; let mode = resolve_connection_mode(token, session_fingerprint, &cached_connections)?; - info!("Connecting to proxy..."); + info!("Connecting to relay..."); let (mut notification_rx, _request_rx, client) = - start_connection(identity_provider, connection_store, proxy_url).await?; + start_connection(identity_provider, connection_store, relay_url).await?; // Drain notifications in background (prevents channel backpressure) tokio::spawn(async move { while notification_rx.recv().await.is_some() {} }); @@ -775,7 +775,7 @@ pub(super) async fn fetch_credential( } async fn run_single_shot( - proxy_url: String, + relay_url: String, token: Option, session_fingerprint: Option, ephemeral_connection: bool, @@ -787,7 +787,7 @@ async fn run_single_shot( let credential_timeout = timeout_secs.map(std::time::Duration::from_secs); match fetch_credential( - &proxy_url, + &relay_url, token.as_deref(), session_fingerprint.as_deref(), ephemeral_connection, @@ -819,24 +819,24 @@ async fn run_single_shot( } } -/// Connect to the proxy and return the notification/request channels + client handle. +/// Connect to the relay and return the notification/request channels + client handle. /// /// Does NOT start pairing — the caller drives pairing as a concurrent task /// so fingerprint verification requests can be handled without deadlocking. async fn start_connection( identity_provider: Box, connection_store: Box, - proxy_url: &str, + relay_url: &str, ) -> Result<( mpsc::Receiver, mpsc::Receiver, RemoteClient, )> { - let proxy_client = Box::new(DefaultProxyClient::from_url(proxy_url.to_string())); + let relay_client = Box::new(DefaultRelayClient::from_url(relay_url.to_string())); - let handle = RemoteClient::connect(identity_provider, connection_store, proxy_client) + let handle = RemoteClient::connect(identity_provider, connection_store, relay_client) .await - .map_err(|e| color_eyre::eyre::eyre!("Connection to proxy failed: {}", e))?; + .map_err(|e| color_eyre::eyre::eyre!("Connection to relay failed: {}", e))?; Ok((handle.notifications, handle.requests, handle.client)) } diff --git a/crates/ap-cli/src/command/listen.rs b/crates/ap-cli/src/command/listen.rs index 1c245ca..14c8803 100644 --- a/crates/ap-cli/src/command/listen.rs +++ b/crates/ap-cli/src/command/listen.rs @@ -7,11 +7,11 @@ use std::collections::HashMap; use std::time::{Duration, Instant}; use ap_client::{ - ConnectionInfo, ConnectionStore, CredentialData, CredentialRequestReply, DefaultProxyClient, + ConnectionInfo, ConnectionStore, CredentialData, CredentialRequestReply, DefaultRelayClient, FingerprintVerificationReply, IdentityProvider, PskStore, UserClient, UserClientNotification, UserClientRequest, }; -use ap_proxy_protocol::IdentityFingerprint; +use ap_relay_protocol::IdentityFingerprint; use clap::Args; use color_eyre::eyre::Result; use crossterm::event::{Event, EventStream, KeyEventKind}; @@ -28,7 +28,7 @@ use super::util::{format_listen_notification, format_relative_time, val_style}; use crate::providers::{CredentialProvider, LookupResult, ProviderStatus}; use crate::storage::{FileConnectionCache, FileIdentityStorage}; -use super::DEFAULT_PROXY_URL; +use super::DEFAULT_RELAY_URL; /// Slash commands available in idle mode. const IDLE_COMMANDS: &[&str] = &["/pair [name]", "/unlock", "/exit"]; @@ -86,9 +86,9 @@ impl ApprovalCache { /// Arguments for the listen command #[derive(Args)] pub struct ListenArgs { - /// Proxy server URL - #[arg(long, default_value = DEFAULT_PROXY_URL)] - pub proxy_url: String, + /// Relay server URL + #[arg(long, default_value = DEFAULT_RELAY_URL)] + pub relay_url: String, /// Use PSK (Pre-Shared Key) mode instead of rendezvous code #[arg(long, conflicts_with = "reusable_psk")] @@ -117,7 +117,7 @@ impl ListenArgs { } else { PairingMode::Rendezvous }; - run_user_client_loop(self.proxy_url, pairing_mode, &mut *provider, log_rx).await + run_user_client_loop(self.relay_url, pairing_mode, &mut *provider, log_rx).await } } @@ -138,7 +138,7 @@ enum Phase { CredentialApproval { query: ap_client::CredentialQuery, credential: CredentialData, - identity: ap_proxy_protocol::IdentityFingerprint, + identity: ap_relay_protocol::IdentityFingerprint, reply: oneshot::Sender, }, /// Waiting for the user to enter unlock input (password or session key). @@ -669,7 +669,7 @@ async fn run_event_loop( /// Run the user client interactive session async fn run_user_client_loop( - proxy_url: String, + relay_url: String, pairing_mode: PairingMode, provider: &mut dyn CredentialProvider, mut log_rx: Option, @@ -717,7 +717,7 @@ async fn run_user_client_loop( let has_cached = !cached_connections.is_empty() && !force_new_connection; - let proxy_client = Box::new(DefaultProxyClient::from_url(proxy_url.clone())); + let relay_client = Box::new(DefaultRelayClient::from_url(relay_url.clone())); // Create PSK store when reusable PSK mode is enabled. // Check for existing stored PSKs before passing ownership to connect(). @@ -737,7 +737,7 @@ async fn run_user_client_loop( let handle = UserClient::connect( identity_provider as Box, connection_store as Box, - proxy_client, + relay_client, None, psk_store, ) diff --git a/crates/ap-cli/src/command/mod.rs b/crates/ap-cli/src/command/mod.rs index 9c727fb..7bdd364 100644 --- a/crates/ap-cli/src/command/mod.rs +++ b/crates/ap-cli/src/command/mod.rs @@ -26,7 +26,7 @@ pub use connections::ConnectionsArgs; pub use listen::ListenArgs; pub use run::RunArgs; -const DEFAULT_PROXY_URL: &str = "wss://ap.lesspassword.dev"; +const DEFAULT_RELAY_URL: &str = "wss://ap.lesspassword.dev"; /// Build a version string like "0.3.0 (abc1234)" when GIT_HASH is set by CI, /// or just "0.3.0" for local dev builds. @@ -96,9 +96,9 @@ pub struct Cli { #[command(subcommand)] pub command: Option, - /// Proxy server URL - #[arg(long, default_value = DEFAULT_PROXY_URL, global = true)] - pub proxy_url: String, + /// Relay server URL + #[arg(long, default_value = DEFAULT_RELAY_URL, global = true)] + pub relay_url: String, /// Token (rendezvous code or PSK token) #[arg(long, env = "AAC_TOKEN")] @@ -135,7 +135,7 @@ pub struct Cli { #[derive(Subcommand)] pub enum Commands { - /// Connect to proxy and request credentials + /// Connect to relay and request credentials Connect(ConnectArgs), /// Listen for remote client connections (user-client mode) Listen(ListenArgs), @@ -159,7 +159,7 @@ pub async fn process_command(cli: Cli, log_rx: Option) -> Result<() { // Single-shot / shorthand connect with top-level args let args = ConnectArgs { - proxy_url: cli.proxy_url, + relay_url: cli.relay_url, token: cli.token, session: cli.session, ephemeral_connection: cli.ephemeral_connection, diff --git a/crates/ap-cli/src/command/output.rs b/crates/ap-cli/src/command/output.rs index d6bad96..daf329e 100644 --- a/crates/ap-cli/src/command/output.rs +++ b/crates/ap-cli/src/command/output.rs @@ -32,7 +32,7 @@ pub fn exit_code_for_error(err: &ClientError) -> i32 { ClientError::ConnectionFailed(_) | ClientError::WebSocket(_) => { exit_code::CONNECTION_FAILED } - ClientError::ProxyAuthFailed(_) + ClientError::RelayAuthFailed(_) | ClientError::HandshakeFailed(_) | ClientError::NoiseProtocol(_) | ClientError::Timeout(_) diff --git a/crates/ap-cli/src/command/run.rs b/crates/ap-cli/src/command/run.rs index a47a6ae..b7b8461 100644 --- a/crates/ap-cli/src/command/run.rs +++ b/crates/ap-cli/src/command/run.rs @@ -9,7 +9,7 @@ use ap_client::CredentialData; use clap::Args; use color_eyre::eyre::{Result, bail}; -use super::DEFAULT_PROXY_URL; +use super::DEFAULT_RELAY_URL; use super::connect::fetch_credential; use super::output::{exit_code, exit_code_for_error}; @@ -41,9 +41,9 @@ The token can be passed via --token or the AAC_TOKEN env var. VALID FIELDS: username, password, totp, uri, notes, domain, credential_id")] pub struct RunArgs { - /// Proxy server URL - #[arg(long, default_value = DEFAULT_PROXY_URL)] - pub proxy_url: String, + /// Relay server URL + #[arg(long, default_value = DEFAULT_RELAY_URL)] + pub relay_url: String, /// Domain to request credentials for #[arg(long, conflicts_with = "id")] @@ -172,7 +172,7 @@ impl RunArgs { // Fetch the credential let credential_timeout = self.timeout.map(std::time::Duration::from_secs); let credential = match fetch_credential( - &self.proxy_url, + &self.relay_url, self.token.as_deref(), self.session.as_deref(), self.ephemeral_connection, diff --git a/crates/ap-cli/src/command/util.rs b/crates/ap-cli/src/command/util.rs index 92cef6b..3788d0f 100644 --- a/crates/ap-cli/src/command/util.rs +++ b/crates/ap-cli/src/command/util.rs @@ -68,7 +68,7 @@ pub fn format_connect_notification(notification: &RemoteClientNotification) -> O match notification { RemoteClientNotification::Connecting => Some(Message::rich( MessageKind::Status, - vec![Span::styled("Connecting to proxy...", text())], + vec![Span::styled("Connecting to relay...", text())], )), RemoteClientNotification::Connected { fingerprint } => { let fp_hex = hex::encode(fingerprint.0); @@ -313,13 +313,13 @@ pub fn format_listen_notification(notification: &UserClientNotification) -> Opti UserClientNotification::ClientDisconnected {} => Some(Message::new( MessageKind::Warning, - "Proxy connection lost — attempting to reconnect...", + "Relay connection lost — attempting to reconnect...", )), UserClientNotification::Reconnecting { attempt } => Some(Message::rich( MessageKind::Status, vec![ - Span::styled("Reconnecting to proxy (attempt ", dim()), + Span::styled("Reconnecting to relay (attempt ", dim()), Span::styled( attempt.to_string(), Style::default() @@ -332,7 +332,7 @@ pub fn format_listen_notification(notification: &UserClientNotification) -> Opti UserClientNotification::Reconnected {} => Some(Message::new( MessageKind::Success, - "Reconnected to proxy server", + "Reconnected to relay server", )), UserClientNotification::Error { message, context } => { diff --git a/crates/ap-cli/src/main.rs b/crates/ap-cli/src/main.rs index 465309e..29be518 100644 --- a/crates/ap-cli/src/main.rs +++ b/crates/ap-cli/src/main.rs @@ -1,6 +1,6 @@ //! aac CLI //! -//! A CLI interface for connecting to a user-client through a proxy +//! A CLI interface for connecting to a user-client through a relay //! to request credentials over a secure Noise Protocol channel. mod command; @@ -38,7 +38,7 @@ async fn main() -> Result<()> { // Initialize logging with appropriate level // - verbose: DEBUG (all messages) // - JSON output: WARN (suppress status messages that would pollute structured output) - // - default: INFO (show status messages like "Connecting to proxy...") + // - default: INFO (show status messages like "Connecting to relay...") let log_level = if cli.verbose { tracing::Level::DEBUG } else if matches!(cli.output, command::output::OutputFormat::Json) { diff --git a/crates/ap-cli/src/storage/connection_storage.rs b/crates/ap-cli/src/storage/connection_storage.rs index 85d0231..670be85 100644 --- a/crates/ap-cli/src/storage/connection_storage.rs +++ b/crates/ap-cli/src/storage/connection_storage.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use ap_client::{ClientError, ConnectionInfo, ConnectionStore, ConnectionUpdate}; use ap_noise::{MultiDeviceTransport, PersistentTransportState}; -use ap_proxy_protocol::IdentityFingerprint; +use ap_relay_protocol::IdentityFingerprint; use async_trait::async_trait; use serde::{Deserialize, Serialize}; use tracing::debug; diff --git a/crates/ap-cli/src/storage/identity_storage.rs b/crates/ap-cli/src/storage/identity_storage.rs index 36e3d74..5dde02a 100644 --- a/crates/ap-cli/src/storage/identity_storage.rs +++ b/crates/ap-cli/src/storage/identity_storage.rs @@ -2,7 +2,7 @@ use std::fs; use std::path::{Path, PathBuf}; use ap_client::{ClientError, IdentityProvider}; -use ap_proxy_protocol::{IdentityFingerprint, IdentityKeyPair}; +use ap_relay_protocol::{IdentityFingerprint, IdentityKeyPair}; use async_trait::async_trait; use tracing::debug; diff --git a/crates/ap-client/Cargo.toml b/crates/ap-client/Cargo.toml index 4c94550..fd31bf1 100644 --- a/crates/ap-client/Cargo.toml +++ b/crates/ap-client/Cargo.toml @@ -10,15 +10,15 @@ repository.workspace = true [features] default = ["native-websocket"] -native-websocket = ["ap-proxy-client/native-websocket"] +native-websocket = ["ap-relay-client/native-websocket"] [dependencies] async-trait = { workspace = true } base64 = { workspace = true } ap-error = { workspace = true } ap-noise = { workspace = true } -ap-proxy-client = { workspace = true, default-features = false } -ap-proxy-protocol = { workspace = true } +ap-relay-client = { workspace = true, default-features = false } +ap-relay-protocol = { workspace = true } futures-util = { workspace = true } hex = { workspace = true } rand = { workspace = true } @@ -41,7 +41,7 @@ wasm-bindgen-futures = "0.4" web-time = "1" [dev-dependencies] -ap-proxy = { workspace = true } +ap-relay = { workspace = true } tokio = { workspace = true, features = [ "rt-multi-thread", "macros", diff --git a/crates/ap-client/src/clients/remote_client.rs b/crates/ap-client/src/clients/remote_client.rs index 0db94b3..39b272d 100644 --- a/crates/ap-client/src/clients/remote_client.rs +++ b/crates/ap-client/src/clients/remote_client.rs @@ -1,13 +1,13 @@ use std::time::Duration; use ap_noise::{InitiatorHandshake, MultiDeviceTransport, Psk}; -use ap_proxy_client::IncomingMessage; -use ap_proxy_protocol::{IdentityFingerprint, RendezvousCode}; +use ap_relay_client::IncomingMessage; +use ap_relay_protocol::{IdentityFingerprint, RendezvousCode}; use base64::{Engine, engine::general_purpose::STANDARD}; use rand::RngCore; use crate::compat::{now_millis, timeout}; -use crate::proxy::ProxyClient; +use crate::relay::RelayClient; use tokio::sync::{mpsc, oneshot}; use tracing::{debug, warn}; @@ -30,9 +30,9 @@ const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(120); /// Fire-and-forget status updates emitted by the remote client. #[derive(Debug, Clone)] pub enum RemoteClientNotification { - /// Connecting to the proxy server + /// Connecting to the relay server Connecting, - /// Successfully connected to the proxy + /// Successfully connected to the relay Connected { /// The device's identity fingerprint (hex-encoded) fingerprint: IdentityFingerprint, @@ -166,12 +166,12 @@ enum RemoteClientCommand { /// A cloneable handle for controlling the remote client. /// -/// Obtained from [`RemoteClient::connect()`], which authenticates with the proxy, +/// Obtained from [`RemoteClient::connect()`], which authenticates with the relay, /// spawns the event loop internally, and returns this handle. All methods /// communicate with the event loop through an internal command channel. /// /// `Clone` and `Send` — share freely across tasks and threads. -/// Dropping all handles shuts down the event loop and disconnects from the proxy. +/// Dropping all handles shuts down the event loop and disconnects from the relay. /// Handle returned by [`RemoteClient::connect()`] containing the client and its /// notification/request channels. pub struct RemoteClientHandle { @@ -186,10 +186,10 @@ pub struct RemoteClient { } impl RemoteClient { - /// Connect to the proxy server, spawn the event loop, and return a handle. + /// Connect to the relay server, spawn the event loop, and return a handle. /// /// This is the single entry point. After `connect()` returns, the client is - /// authenticated with the proxy and ready for pairing. Use one of the pairing + /// authenticated with the relay and ready for pairing. Use one of the pairing /// methods to establish a secure channel: /// - [`pair_with_handshake()`](Self::pair_with_handshake) for rendezvous-based pairing /// - [`pair_with_psk()`](Self::pair_with_psk) for PSK-based pairing @@ -197,19 +197,19 @@ impl RemoteClient { pub async fn connect( identity_provider: Box, connection_store: Box, - mut proxy_client: Box, + mut relay_client: Box, ) -> Result { let identity_keypair = identity_provider.identity().await; let own_fingerprint = identity_keypair.identity().fingerprint(); - debug!("Connecting to proxy with identity {:?}", own_fingerprint); + debug!("Connecting to relay with identity {:?}", own_fingerprint); let (notification_tx, notification_rx) = mpsc::channel(32); let (request_tx, request_rx) = mpsc::channel(32); notify!(notification_tx, RemoteClientNotification::Connecting); - let incoming_rx = proxy_client.connect(identity_keypair).await?; + let incoming_rx = relay_client.connect(identity_keypair).await?; notify!( notification_tx, @@ -218,7 +218,7 @@ impl RemoteClient { } ); - debug!("Connected to proxy successfully"); + debug!("Connected to relay successfully"); // Create command channel let (command_tx, command_rx) = mpsc::channel(32); @@ -226,7 +226,7 @@ impl RemoteClient { // Build inner state let inner = RemoteClientInner { connection_store, - proxy_client, + relay_client, transport: None, remote_fingerprint: None, }; @@ -367,7 +367,7 @@ impl RemoteClient { /// All mutable state for the remote client, owned by the spawned event loop task. struct RemoteClientInner { connection_store: Box, - proxy_client: Box, + relay_client: Box, transport: Option, remote_fingerprint: Option, } @@ -390,9 +390,9 @@ impl RemoteClientInner { debug!("Received message while idle"); } None => { - // Proxy disconnected + // Relay disconnected notify!(notification_tx, RemoteClientNotification::Disconnected { - reason: Some("Proxy connection closed".to_string()), + reason: Some("Relay connection closed".to_string()), }); return; } @@ -411,7 +411,7 @@ impl RemoteClientInner { None => { // All handles dropped — shut down debug!("All RemoteClient handles dropped, shutting down event loop"); - self.proxy_client.disconnect().await.ok(); + self.relay_client.disconnect().await.ok(); return; } } @@ -504,7 +504,7 @@ impl RemoteClientInner { ); let remote_fingerprint = - Self::resolve_rendezvous(self.proxy_client.as_ref(), incoming_rx, &rendezvous_code) + Self::resolve_rendezvous(self.relay_client.as_ref(), incoming_rx, &rendezvous_code) .await?; notify!( @@ -518,7 +518,7 @@ impl RemoteClientInner { notify!(notification_tx, RemoteClientNotification::HandshakeStart); let (transport, fingerprint_str) = Self::perform_handshake( - self.proxy_client.as_ref(), + self.relay_client.as_ref(), incoming_rx, remote_fingerprint, None, @@ -558,7 +558,7 @@ impl RemoteClientInner { ); } Ok(Ok(RemoteClientFingerprintReply { approved: false })) => { - self.proxy_client.disconnect().await.ok(); + self.relay_client.disconnect().await.ok(); notify!( notification_tx, RemoteClientNotification::FingerprintRejected { @@ -571,7 +571,7 @@ impl RemoteClientInner { return Err(ClientError::ChannelClosed); } Err(_) => { - self.proxy_client.disconnect().await.ok(); + self.relay_client.disconnect().await.ok(); return Err(ClientError::Timeout( "Fingerprint verification timeout".to_string(), )); @@ -606,7 +606,7 @@ impl RemoteClientInner { notify!(notification_tx, RemoteClientNotification::HandshakeStart); let (transport, _fingerprint_str) = Self::perform_handshake( - self.proxy_client.as_ref(), + self.relay_client.as_ref(), incoming_rx, remote_fingerprint, Some(psk), @@ -759,9 +759,9 @@ impl RemoteClientInner { encrypted: encrypted_data, }; - // Send via proxy + // Send via relay let msg_json = serde_json::to_string(&msg)?; - self.proxy_client + self.relay_client .send_to(remote_fingerprint, msg_json.into_bytes()) .await?; @@ -890,12 +890,12 @@ impl RemoteClientInner { /// Resolve rendezvous code to identity fingerprint. async fn resolve_rendezvous( - proxy_client: &dyn ProxyClient, + relay_client: &dyn RelayClient, incoming_rx: &mut mpsc::UnboundedReceiver, rendezvous_code: &str, ) -> Result { // Send GetIdentity request - proxy_client + relay_client .request_identity(RendezvousCode::from_string(rendezvous_code.to_string())) .await .map_err(|e| ClientError::RendezvousResolutionFailed(e.to_string()))?; @@ -924,7 +924,7 @@ impl RemoteClientInner { /// Perform Noise handshake as initiator. async fn perform_handshake( - proxy_client: &dyn ProxyClient, + relay_client: &dyn RelayClient, incoming_rx: &mut mpsc::UnboundedReceiver, remote_fingerprint: IdentityFingerprint, psk: Option, @@ -950,7 +950,7 @@ impl RemoteClientInner { }; let msg_json = serde_json::to_string(&msg)?; - proxy_client + relay_client .send_to(remote_fingerprint, msg_json.into_bytes()) .await?; diff --git a/crates/ap-client/src/clients/user_client.rs b/crates/ap-client/src/clients/user_client.rs index 8f7aca9..07bbf74 100644 --- a/crates/ap-client/src/clients/user_client.rs +++ b/crates/ap-client/src/clients/user_client.rs @@ -9,14 +9,14 @@ use std::time::Instant; use web_time::Instant; use ap_noise::{Ciphersuite, MultiDeviceTransport, Psk, ResponderHandshake}; -use ap_proxy_client::IncomingMessage; -use ap_proxy_protocol::{IdentityFingerprint, IdentityKeyPair, RendezvousCode}; +use ap_relay_client::IncomingMessage; +use ap_relay_protocol::{IdentityFingerprint, IdentityKeyPair, RendezvousCode}; use base64::{Engine, engine::general_purpose::STANDARD}; use futures_util::StreamExt; use futures_util::stream::FuturesUnordered; use tokio::sync::oneshot; -use crate::proxy::ProxyClient; +use crate::relay::RelayClient; use crate::types::{CredentialData, PskId, PskToken}; use tokio::sync::mpsc; use tracing::{debug, warn}; @@ -199,14 +199,14 @@ pub enum UserClientNotification { /// The remote device's identity fingerprint fingerprint: IdentityFingerprint, }, - /// Client disconnected from proxy + /// Client disconnected from relay ClientDisconnected {}, - /// Attempting to reconnect to proxy + /// Attempting to reconnect to relay Reconnecting { /// Current reconnection attempt number attempt: u32, }, - /// Successfully reconnected to proxy + /// Successfully reconnected to relay Reconnected {}, /// An error occurred Error { @@ -295,7 +295,7 @@ enum UserClientCommand { reusable: bool, reply: oneshot::Sender>, }, - /// Request a rendezvous code from the proxy and register a pending pairing. + /// Request a rendezvous code from the relay and register a pending pairing. GetRendezvousToken { name: Option, reply: oneshot::Sender>, @@ -304,7 +304,7 @@ enum UserClientCommand { /// A cloneable handle for controlling the user client. /// -/// Obtained from [`UserClient::connect()`], which authenticates with the proxy, +/// Obtained from [`UserClient::connect()`], which authenticates with the relay, /// spawns the event loop internally, and returns this handle. All methods /// communicate with the event loop through an internal command channel. /// @@ -323,7 +323,7 @@ pub struct UserClient { } impl UserClient { - /// Connect to the proxy server, spawn the event loop, and return a handle. + /// Connect to the relay server, spawn the event loop, and return a handle. /// /// This is the single entry point. After `connect()` returns, the client is /// already listening for incoming connections. Use `get_psk_token()` or @@ -336,16 +336,16 @@ impl UserClient { pub async fn connect( identity_provider: Box, connection_store: Box, - mut proxy_client: Box, + mut relay_client: Box, audit_log: Option>, psk_store: Option>, ) -> Result { - // Extract identity once — used for proxy auth, reconnection, and own fingerprint + // Extract identity once — used for relay auth, reconnection, and own fingerprint let identity_keypair = identity_provider.identity().await; let own_fingerprint = identity_keypair.identity().fingerprint(); - // Authenticate with the proxy (the async part — before spawn) - let incoming_rx = proxy_client.connect(identity_keypair.clone()).await?; + // Authenticate with the relay (the async part — before spawn) + let incoming_rx = relay_client.connect(identity_keypair.clone()).await?; // Create channels let (notification_tx, notification_rx) = mpsc::channel(32); @@ -375,7 +375,7 @@ impl UserClient { let inner = UserClientInner { connection_store, - proxy_client, + relay_client, identity_keypair, own_fingerprint, transports: HashMap::new(), @@ -427,7 +427,7 @@ impl UserClient { rx.await.map_err(|_| ClientError::ChannelClosed)? } - /// Request a rendezvous code from the proxy for a new pairing. + /// Request a rendezvous code from the relay for a new pairing. /// /// Only one rendezvous pairing at a time — there's no way to distinguish /// incoming rendezvous handshakes. @@ -451,7 +451,7 @@ impl UserClient { /// All mutable state for the user client, owned by the spawned event loop task. struct UserClientInner { connection_store: Box, - proxy_client: Box, + relay_client: Box, /// Our identity keypair (needed for reconnection). identity_keypair: IdentityKeyPair, /// Our own identity fingerprint (cached at construction time). @@ -498,7 +498,7 @@ impl UserClientInner { } } None => { - // Incoming channel closed — proxy connection lost + // Incoming channel closed — relay connection lost notify!(notification_tx, UserClientNotification::ClientDisconnected {}); match self.attempt_reconnection(¬ification_tx).await { Ok(new_rx) => { @@ -547,7 +547,7 @@ impl UserClientInner { } } - /// Attempt to reconnect to the proxy server with exponential backoff. + /// Attempt to reconnect to the relay server with exponential backoff. async fn attempt_reconnection( &mut self, notification_tx: &mpsc::Sender, @@ -561,15 +561,15 @@ impl UserClientInner { attempt = attempt.saturating_add(1); // Disconnect (ignore errors — connection may already be dead) - let _ = self.proxy_client.disconnect().await; + let _ = self.relay_client.disconnect().await; match self - .proxy_client + .relay_client .connect(self.identity_keypair.clone()) .await { Ok(new_rx) => { - debug!("Reconnected to proxy on attempt {}", attempt); + debug!("Reconnected to relay on attempt {}", attempt); return Ok(new_rx); } Err(e) => { @@ -597,7 +597,7 @@ impl UserClientInner { } } - /// Handle incoming messages from proxy. + /// Handle incoming messages from relay. async fn handle_incoming( &mut self, msg: IncomingMessage, @@ -996,7 +996,7 @@ impl UserClientInner { let _ = reply.send(result); } UserClientCommand::GetRendezvousToken { name, reply } => { - if let Err(e) = self.proxy_client.request_rendezvous().await { + if let Err(e) = self.relay_client.request_rendezvous().await { let _ = reply.send(Err(e)); return; } @@ -1016,7 +1016,7 @@ impl UserClientInner { notify!( notification_tx, UserClientNotification::HandshakeProgress { - message: "Requesting rendezvous code from proxy...".to_string(), + message: "Requesting rendezvous code from relay...".to_string(), } ); } @@ -1229,7 +1229,7 @@ impl UserClientInner { let msg_json = serde_json::to_string(&msg)?; - self.proxy_client + self.relay_client .send_to(source, msg_json.into_bytes()) .await?; @@ -1318,7 +1318,7 @@ impl UserClientInner { let msg_json = serde_json::to_string(&msg)?; - self.proxy_client + self.relay_client .send_to(remote_fingerprint, msg_json.into_bytes()) .await?; diff --git a/crates/ap-client/src/error.rs b/crates/ap-client/src/error.rs index 411d493..3c9e780 100644 --- a/crates/ap-client/src/error.rs +++ b/crates/ap-client/src/error.rs @@ -7,17 +7,17 @@ use thiserror::Error; #[ap_error(flat)] #[derive(Debug, Error)] pub enum ClientError { - /// Failed to connect to the proxy server - #[error("Failed to connect to proxy: {0}")] + /// Failed to connect to the relay server + #[error("Failed to connect to relay: {0}")] ConnectionFailed(String), /// WebSocket error occurred #[error("WebSocket error: {0}")] WebSocket(String), - /// Authentication with proxy failed - #[error("Proxy authentication failed: {0}")] - ProxyAuthFailed(String), + /// Authentication with relay failed + #[error("Relay authentication failed: {0}")] + RelayAuthFailed(String), /// Invalid pairing code format #[error("Invalid pairing code: {0}")] @@ -100,8 +100,8 @@ impl From for ClientError { } } -impl From for ClientError { - fn from(err: ap_proxy_protocol::ProxyError) -> Self { +impl From for ClientError { + fn from(err: ap_relay_protocol::RelayError) -> Self { ClientError::ConnectionFailed(err.to_string()) } } diff --git a/crates/ap-client/src/lib.rs b/crates/ap-client/src/lib.rs index 88a3f94..54675d2 100644 --- a/crates/ap-client/src/lib.rs +++ b/crates/ap-client/src/lib.rs @@ -1,7 +1,7 @@ //! Noise Protocol Clients for access-protocol //! //! This crate provides both remote and user client implementations for -//! connecting through a proxy using the Noise Protocol. +//! connecting through a relay using the Noise Protocol. //! //! ## Features //! @@ -13,14 +13,14 @@ //! ## Remote Client Usage (untrusted device) //! //! ```ignore -//! use ap_client::{RemoteClient, RemoteClientHandle, DefaultProxyClient, IdentityProvider, ConnectionStore}; +//! use ap_client::{RemoteClient, RemoteClientHandle, DefaultRelayClient, IdentityProvider, ConnectionStore}; //! -//! // Create proxy client — identity is wired internally by connect() -//! let proxy_client = Box::new(DefaultProxyClient::from_url("ws://localhost:8080".to_string())); +//! // Create relay client — identity is wired internally by connect() +//! let relay_client = Box::new(DefaultRelayClient::from_url("ws://localhost:8080".to_string())); //! //! // Connect — spawns event loop internally, returns handle with channels //! let RemoteClientHandle { client, mut notifications, mut requests } = -//! RemoteClient::connect(identity_provider, connection_store, proxy_client).await?; +//! RemoteClient::connect(identity_provider, connection_store, relay_client).await?; //! //! // Pair with rendezvous code //! client.pair_with_handshake("ABCDEF123".to_string(), false).await?; @@ -32,14 +32,14 @@ //! ## User Client Usage (trusted device) //! //! ```ignore -//! use ap_client::{DefaultProxyClient, IdentityProvider, UserClient, UserClientHandle}; +//! use ap_client::{DefaultRelayClient, IdentityProvider, UserClient, UserClientHandle}; //! -//! // Create proxy client — identity is wired internally by connect() -//! let proxy_client = Box::new(DefaultProxyClient::from_url("ws://localhost:8080".to_string())); +//! // Create relay client — identity is wired internally by connect() +//! let relay_client = Box::new(DefaultRelayClient::from_url("ws://localhost:8080".to_string())); //! //! // Connect — spawns event loop internally, returns handle with channels //! let UserClientHandle { client, mut notifications, mut requests } = -//! UserClient::connect(identity_provider, connection_store, proxy_client, None, None).await?; +//! UserClient::connect(identity_provider, connection_store, relay_client, None, None).await?; //! //! // Already listening. Just use it. //! let token = client.get_psk_token(None, false).await?; @@ -48,8 +48,8 @@ /// Error types pub mod error; -/// Proxy client trait and default implementation -pub mod proxy; +/// Relay client trait and default implementation +pub mod relay; /// Traits for storage implementations pub mod traits; /// Protocol types and events @@ -72,15 +72,15 @@ pub use error::ClientError; pub use memory_connection_store::MemoryConnectionStore; pub use memory_psk_store::MemoryPskStore; #[cfg(feature = "native-websocket")] -pub use proxy::DefaultProxyClient; -pub use proxy::ProxyClient; +pub use relay::DefaultRelayClient; +pub use relay::RelayClient; pub use traits::{ AuditConnectionType, AuditEvent, AuditLog, ConnectionInfo, ConnectionStore, ConnectionUpdate, CredentialFieldSet, IdentityProvider, MemoryIdentityProvider, NoOpAuditLog, PskEntry, PskStore, }; pub use types::{ConnectionMode, CredentialData, CredentialQuery, PskId, PskToken}; -// Re-export ap-proxy-protocol types -pub use ap_proxy_protocol::{IdentityFingerprint, RendezvousCode}; +// Re-export ap-relay-protocol types +pub use ap_relay_protocol::{IdentityFingerprint, RendezvousCode}; // Re-export PSK type from noise protocol pub use ap_noise::{MultiDeviceTransport, Psk}; diff --git a/crates/ap-client/src/memory_connection_store.rs b/crates/ap-client/src/memory_connection_store.rs index bd6b81d..d394e82 100644 --- a/crates/ap-client/src/memory_connection_store.rs +++ b/crates/ap-client/src/memory_connection_store.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use ap_proxy_protocol::IdentityFingerprint; +use ap_relay_protocol::IdentityFingerprint; use async_trait::async_trait; use crate::error::ClientError; diff --git a/crates/ap-client/src/proxy.rs b/crates/ap-client/src/relay.rs similarity index 71% rename from crates/ap-client/src/proxy.rs rename to crates/ap-client/src/relay.rs index 3ed6cca..65d8618 100644 --- a/crates/ap-client/src/proxy.rs +++ b/crates/ap-client/src/relay.rs @@ -1,27 +1,27 @@ -//! Proxy client trait and default implementation +//! Relay client trait and default implementation //! -//! This module provides the `ProxyClient` trait for abstracting proxy communication, +//! This module provides the `RelayClient` trait for abstracting relay communication, //! enabling dependency injection and easier testing. -use ap_proxy_client::IncomingMessage; +use ap_relay_client::IncomingMessage; #[cfg(feature = "native-websocket")] -use ap_proxy_client::ProxyProtocolClient; -use ap_proxy_protocol::{IdentityFingerprint, IdentityKeyPair, RendezvousCode}; +use ap_relay_client::RelayProtocolClient; +use ap_relay_protocol::{IdentityFingerprint, IdentityKeyPair, RendezvousCode}; use async_trait::async_trait; use tokio::sync::mpsc; use crate::error::ClientError; -/// Trait abstracting the proxy client for communication between devices +/// Trait abstracting the relay client for communication between devices #[async_trait] -pub trait ProxyClient: Send + Sync { - /// Connect to the proxy server, returning a receiver for incoming messages +pub trait RelayClient: Send + Sync { + /// Connect to the relay server, returning a receiver for incoming messages async fn connect( &mut self, identity: IdentityKeyPair, ) -> Result, ClientError>; - /// Request a rendezvous code from the proxy server + /// Request a rendezvous code from the relay server async fn request_rendezvous(&self) -> Result<(), ClientError>; /// Request the identity associated with a rendezvous code @@ -34,28 +34,28 @@ pub trait ProxyClient: Send + Sync { data: Vec, ) -> Result<(), ClientError>; - /// Disconnect from the proxy server + /// Disconnect from the relay server async fn disconnect(&mut self) -> Result<(), ClientError>; } -/// Default implementation using ProxyProtocolClient from ap-proxy +/// Default implementation using RelayProtocolClient from ap-relay #[cfg(feature = "native-websocket")] -pub struct DefaultProxyClient { - inner: ProxyProtocolClient, +pub struct DefaultRelayClient { + inner: RelayProtocolClient, } #[cfg(feature = "native-websocket")] -impl DefaultProxyClient { - pub fn from_url(proxy_url: String) -> Self { +impl DefaultRelayClient { + pub fn from_url(relay_url: String) -> Self { Self { - inner: ProxyProtocolClient::from_url(proxy_url), + inner: RelayProtocolClient::from_url(relay_url), } } } #[cfg(feature = "native-websocket")] #[async_trait] -impl ProxyClient for DefaultProxyClient { +impl RelayClient for DefaultRelayClient { async fn connect( &mut self, identity: IdentityKeyPair, diff --git a/crates/ap-client/src/traits.rs b/crates/ap-client/src/traits.rs index 1ba4ece..e342556 100644 --- a/crates/ap-client/src/traits.rs +++ b/crates/ap-client/src/traits.rs @@ -1,5 +1,5 @@ use ap_noise::{MultiDeviceTransport, Psk}; -use ap_proxy_protocol::{IdentityFingerprint, IdentityKeyPair}; +use ap_relay_protocol::{IdentityFingerprint, IdentityKeyPair}; use async_trait::async_trait; use crate::error::ClientError; @@ -71,7 +71,7 @@ pub trait IdentityProvider: Send + Sync { /// /// ``` /// use ap_client::MemoryIdentityProvider; -/// use ap_proxy_protocol::IdentityKeyPair; +/// use ap_relay_protocol::IdentityKeyPair; /// /// let keypair = IdentityKeyPair::generate(); /// let identity = MemoryIdentityProvider::from_keypair(keypair); diff --git a/crates/ap-client/src/types.rs b/crates/ap-client/src/types.rs index 8f145e0..0de8f82 100644 --- a/crates/ap-client/src/types.rs +++ b/crates/ap-client/src/types.rs @@ -3,7 +3,7 @@ use std::fmt; use ap_noise::Psk; -use ap_proxy_protocol::IdentityFingerprint; +use ap_relay_protocol::IdentityFingerprint; use serde::{Deserialize, Serialize}; use zeroize::Zeroizing; diff --git a/crates/ap-client/tests/pairing.rs b/crates/ap-client/tests/pairing.rs index 361dfc9..5ea0ecd 100644 --- a/crates/ap-client/tests/pairing.rs +++ b/crates/ap-client/tests/pairing.rs @@ -1,19 +1,19 @@ //! Integration tests for ap-client pairing flows //! //! These tests verify the PSK and fingerprint-based pairing modes -//! using mock implementations of the identity provider, connection store, and proxy. +//! using mock implementations of the identity provider, connection store, and relay. use std::sync::Arc; use std::sync::atomic::{AtomicU32, Ordering}; use ap_client::{ ClientError, CredentialRequestReply, FingerprintVerificationReply, IdentityProvider, - MemoryConnectionStore, MemoryIdentityProvider, ProxyClient, PskToken, RemoteClient, + MemoryConnectionStore, MemoryIdentityProvider, PskToken, RelayClient, RemoteClient, RemoteClientFingerprintReply, RemoteClientHandle, RemoteClientNotification, RemoteClientRequest, UserClient, UserClientHandle, UserClientNotification, UserClientRequest, }; -use ap_proxy_client::IncomingMessage; -use ap_proxy_protocol::{IdentityFingerprint, IdentityKeyPair, RendezvousCode}; +use ap_relay_client::IncomingMessage; +use ap_relay_protocol::{IdentityFingerprint, IdentityKeyPair, RendezvousCode}; use async_trait::async_trait; use tokio::sync::mpsc; use tokio::time::{Duration, timeout}; @@ -25,24 +25,24 @@ use zeroize::Zeroizing; // Uses MemoryIdentityProvider from the library instead of a local mock -/// Mock proxy client that relays messages through channels -struct MockProxyClient { +/// Mock relay client that relays messages through channels +struct MockRelayClient { /// Our identity fingerprint #[allow(dead_code)] own_fingerprint: IdentityFingerprint, - /// Sender for outgoing messages (to the paired proxy) + /// Sender for outgoing messages (to the paired relay) outgoing_tx: mpsc::UnboundedSender<(IdentityFingerprint, Vec)>, - /// Receiver for incoming messages (from the paired proxy) + /// Receiver for incoming messages (from the paired relay) incoming_rx: Option>, /// Sender to push incoming messages to ourselves incoming_tx: mpsc::UnboundedSender, - /// The paired proxy's fingerprint (for routing) + /// The paired relay's fingerprint (for routing) peer_fingerprint: Option, /// Rendezvous code (for user client) rendezvous_code: Option, } -impl MockProxyClient { +impl MockRelayClient { fn new( own_fingerprint: IdentityFingerprint, outgoing_tx: mpsc::UnboundedSender<(IdentityFingerprint, Vec)>, @@ -69,7 +69,7 @@ impl MockProxyClient { } #[async_trait] -impl ProxyClient for MockProxyClient { +impl RelayClient for MockRelayClient { async fn connect( &mut self, _identity: IdentityKeyPair, @@ -123,10 +123,10 @@ impl ProxyClient for MockProxyClient { } /// Creates a pair of connected mock proxies that relay messages between each other -fn create_mock_proxy_pair( +fn create_mock_relay_pair( user_fingerprint: IdentityFingerprint, remote_fingerprint: IdentityFingerprint, -) -> (MockProxyClient, MockProxyClient) { +) -> (MockRelayClient, MockRelayClient) { // Channels for user -> remote communication let (user_to_remote_tx, mut user_to_remote_rx) = mpsc::unbounded_channel::<(IdentityFingerprint, Vec)>(); @@ -139,21 +139,21 @@ fn create_mock_proxy_pair( let (remote_incoming_tx, remote_incoming_rx) = mpsc::unbounded_channel::(); // Create mock proxies - let mut user_proxy = MockProxyClient::new( + let mut user_relay = MockRelayClient::new( user_fingerprint, user_to_remote_tx, user_incoming_rx, user_incoming_tx.clone(), ); - user_proxy.set_peer_fingerprint(remote_fingerprint); + user_relay.set_peer_fingerprint(remote_fingerprint); - let mut remote_proxy = MockProxyClient::new( + let mut remote_relay = MockRelayClient::new( remote_fingerprint, remote_to_user_tx, remote_incoming_rx, remote_incoming_tx.clone(), ); - remote_proxy.set_peer_fingerprint(user_fingerprint); + remote_relay.set_peer_fingerprint(user_fingerprint); // Spawn relay tasks // user -> remote relay @@ -180,7 +180,7 @@ fn create_mock_proxy_pair( } }); - (user_proxy, remote_proxy) + (user_relay, remote_relay) } // ============================================================================ @@ -196,8 +196,8 @@ async fn test_psk_pairing() { let user_fingerprint = user_identity.fingerprint().await; let remote_fingerprint = remote_identity.fingerprint().await; - // Create mock proxy pair - let (user_proxy, remote_proxy) = create_mock_proxy_pair(user_fingerprint, remote_fingerprint); + // Create mock relay pair + let (user_relay, remote_relay) = create_mock_relay_pair(user_fingerprint, remote_fingerprint); // Create connection stores let user_connection_store = MemoryConnectionStore::new(); @@ -211,7 +211,7 @@ async fn test_psk_pairing() { } = UserClient::connect( Box::new(user_identity), Box::new(user_connection_store), - Box::new(user_proxy), + Box::new(user_relay), None, None, ) @@ -237,7 +237,7 @@ async fn test_psk_pairing() { } = RemoteClient::connect( Box::new(remote_identity), Box::new(remote_connection_store), - Box::new(remote_proxy), + Box::new(remote_relay), ) .await .expect("RemoteClient should connect"); @@ -304,14 +304,14 @@ async fn test_fingerprint_pairing() { let user_fingerprint = user_identity.fingerprint().await; let remote_fingerprint = remote_identity.fingerprint().await; - // Create mock proxy pair - let (mut user_proxy, mut remote_proxy) = - create_mock_proxy_pair(user_fingerprint, remote_fingerprint); + // Create mock relay pair + let (mut user_relay, mut remote_relay) = + create_mock_relay_pair(user_fingerprint, remote_fingerprint); // Set up rendezvous code let rendezvous_code = RendezvousCode::from_string("ABCDEF123".to_string()); - user_proxy.set_rendezvous_code(rendezvous_code.clone()); - remote_proxy.set_peer_fingerprint(user_fingerprint); + user_relay.set_rendezvous_code(rendezvous_code.clone()); + remote_relay.set_peer_fingerprint(user_fingerprint); // Create connection stores let user_connection_store = MemoryConnectionStore::new(); @@ -325,7 +325,7 @@ async fn test_fingerprint_pairing() { } = UserClient::connect( Box::new(user_identity), Box::new(user_connection_store), - Box::new(user_proxy), + Box::new(user_relay), None, None, ) @@ -347,7 +347,7 @@ async fn test_fingerprint_pairing() { } = RemoteClient::connect( Box::new(remote_identity), Box::new(remote_connection_store), - Box::new(remote_proxy), + Box::new(remote_relay), ) .await .expect("RemoteClient should connect"); @@ -423,26 +423,26 @@ async fn test_fingerprint_pairing() { } // ============================================================================ -// Reconnecting Mock Proxy Client +// Reconnecting Mock Relay Client // ============================================================================ -/// A mock proxy client that simulates connection drops and reconnections. +/// A mock relay client that simulates connection drops and reconnections. /// /// - First `connect()` succeeds normally. /// - The incoming channel is closed after a configurable delay to simulate a drop. /// - Subsequent `connect()` calls fail `fail_count` times, then succeed. -struct ReconnectingMockProxyClient { +struct ReconnectingMockRelayClient { /// How many reconnection attempts should fail before succeeding fail_count: u32, /// Tracks total connect() calls (including the initial one) connect_calls: Arc, /// Channel for pushing incoming messages after reconnection incoming_tx: Option>, - /// Delay before closing the first incoming channel (simulates proxy drop) + /// Delay before closing the first incoming channel (simulates relay drop) drop_delay: Duration, } -impl ReconnectingMockProxyClient { +impl ReconnectingMockRelayClient { fn new(fail_count: u32, drop_delay: Duration) -> Self { Self { fail_count, @@ -454,7 +454,7 @@ impl ReconnectingMockProxyClient { } #[async_trait] -impl ProxyClient for ReconnectingMockProxyClient { +impl RelayClient for ReconnectingMockRelayClient { async fn connect( &mut self, _identity: IdentityKeyPair, @@ -467,7 +467,7 @@ impl ProxyClient for ReconnectingMockProxyClient { let drop_delay = self.drop_delay; tokio::spawn(async move { tokio::time::sleep(drop_delay).await; - drop(tx); // Closes the receiver, simulating proxy drop + drop(tx); // Closes the receiver, simulating relay drop }); return Ok(rx); } @@ -521,8 +521,8 @@ async fn run_reconnection_test(fail_count: u32) -> (Vec, let identity = MemoryIdentityProvider::new(); let connection_store = MemoryConnectionStore::new(); - let proxy = ReconnectingMockProxyClient::new(fail_count, Duration::from_millis(10)); - let connect_calls = Arc::clone(&proxy.connect_calls); + let relay = ReconnectingMockRelayClient::new(fail_count, Duration::from_millis(10)); + let connect_calls = Arc::clone(&relay.connect_calls); let UserClientHandle { client, @@ -531,7 +531,7 @@ async fn run_reconnection_test(fail_count: u32) -> (Vec, } = UserClient::connect( Box::new(identity), Box::new(connection_store), - Box::new(proxy), + Box::new(relay), None, None, ) @@ -571,7 +571,7 @@ async fn run_reconnection_test(fail_count: u32) -> (Vec, (events, calls) } -/// Test that UserClient reconnects immediately when proxy drops and first retry succeeds. +/// Test that UserClient reconnects immediately when relay drops and first retry succeeds. /// /// Verifies: ClientDisconnected → Reconnected (no Reconnecting events) #[tokio::test(flavor = "current_thread")] @@ -644,14 +644,14 @@ async fn test_fingerprint_pairing_both_sides_verify() { let user_fingerprint = user_identity.fingerprint().await; let remote_fingerprint = remote_identity.fingerprint().await; - // Create mock proxy pair - let (mut user_proxy, mut remote_proxy) = - create_mock_proxy_pair(user_fingerprint, remote_fingerprint); + // Create mock relay pair + let (mut user_relay, mut remote_relay) = + create_mock_relay_pair(user_fingerprint, remote_fingerprint); // Set up rendezvous code let rendezvous_code = RendezvousCode::from_string("XYZW56789".to_string()); - user_proxy.set_rendezvous_code(rendezvous_code.clone()); - remote_proxy.set_peer_fingerprint(user_fingerprint); + user_relay.set_rendezvous_code(rendezvous_code.clone()); + remote_relay.set_peer_fingerprint(user_fingerprint); // Create connection stores let user_connection_store = MemoryConnectionStore::new(); @@ -665,7 +665,7 @@ async fn test_fingerprint_pairing_both_sides_verify() { } = UserClient::connect( Box::new(user_identity), Box::new(user_connection_store), - Box::new(user_proxy), + Box::new(user_relay), None, None, ) @@ -687,7 +687,7 @@ async fn test_fingerprint_pairing_both_sides_verify() { } = RemoteClient::connect( Box::new(remote_identity), Box::new(remote_connection_store), - Box::new(remote_proxy), + Box::new(remote_relay), ) .await .expect("RemoteClient should connect"); @@ -789,11 +789,11 @@ async fn test_dual_mode_psk_pairing_with_both_modes_pending() { let user_fingerprint = user_identity.fingerprint().await; let remote_fingerprint = remote_identity.fingerprint().await; - // Create mock proxy pair - let (mut user_proxy, remote_proxy) = - create_mock_proxy_pair(user_fingerprint, remote_fingerprint); + // Create mock relay pair + let (mut user_relay, remote_relay) = + create_mock_relay_pair(user_fingerprint, remote_fingerprint); // Set up rendezvous so get_rendezvous_token doesn't hang - user_proxy.set_rendezvous_code(RendezvousCode::from_string("DUAL12345".to_string())); + user_relay.set_rendezvous_code(RendezvousCode::from_string("DUAL12345".to_string())); let user_connection_store = MemoryConnectionStore::new(); let remote_connection_store = MemoryConnectionStore::new(); @@ -806,7 +806,7 @@ async fn test_dual_mode_psk_pairing_with_both_modes_pending() { } = UserClient::connect( Box::new(user_identity), Box::new(user_connection_store), - Box::new(user_proxy), + Box::new(user_relay), None, None, ) @@ -837,7 +837,7 @@ async fn test_dual_mode_psk_pairing_with_both_modes_pending() { } = RemoteClient::connect( Box::new(remote_identity), Box::new(remote_connection_store), - Box::new(remote_proxy), + Box::new(remote_relay), ) .await .expect("RemoteClient should connect"); @@ -897,7 +897,7 @@ async fn test_notification_channel_not_blocking_event_loop() { let user_fingerprint = user_identity.fingerprint().await; let remote_fingerprint = remote_identity.fingerprint().await; - let (user_proxy, remote_proxy) = create_mock_proxy_pair(user_fingerprint, remote_fingerprint); + let (user_relay, remote_relay) = create_mock_relay_pair(user_fingerprint, remote_fingerprint); let user_connection_store = MemoryConnectionStore::new(); let remote_connection_store = MemoryConnectionStore::new(); @@ -910,7 +910,7 @@ async fn test_notification_channel_not_blocking_event_loop() { } = UserClient::connect( Box::new(user_identity), Box::new(user_connection_store), - Box::new(user_proxy), + Box::new(user_relay), None, None, ) @@ -935,7 +935,7 @@ async fn test_notification_channel_not_blocking_event_loop() { } = RemoteClient::connect( Box::new(remote_identity), Box::new(remote_connection_store), - Box::new(remote_proxy), + Box::new(remote_relay), ) .await .expect("RemoteClient should connect"); @@ -1010,7 +1010,7 @@ async fn test_request_channel_backpressure() { let user_fingerprint = user_identity.fingerprint().await; let remote_fingerprint = remote_identity.fingerprint().await; - let (user_proxy, remote_proxy) = create_mock_proxy_pair(user_fingerprint, remote_fingerprint); + let (user_relay, remote_relay) = create_mock_relay_pair(user_fingerprint, remote_fingerprint); let user_connection_store = MemoryConnectionStore::new(); let remote_connection_store = MemoryConnectionStore::new(); @@ -1023,7 +1023,7 @@ async fn test_request_channel_backpressure() { } = UserClient::connect( Box::new(user_identity), Box::new(user_connection_store), - Box::new(user_proxy), + Box::new(user_relay), None, None, ) @@ -1048,7 +1048,7 @@ async fn test_request_channel_backpressure() { } = RemoteClient::connect( Box::new(remote_identity), Box::new(remote_connection_store), - Box::new(remote_proxy), + Box::new(remote_relay), ) .await .expect("RemoteClient should connect"); @@ -1106,14 +1106,14 @@ async fn test_credential_request_buffered_during_fingerprint_verification() { let user_fingerprint = user_identity.fingerprint().await; let remote_fingerprint = remote_identity.fingerprint().await; - // Create mock proxy pair - let (mut user_proxy, mut remote_proxy) = - create_mock_proxy_pair(user_fingerprint, remote_fingerprint); + // Create mock relay pair + let (mut user_relay, mut remote_relay) = + create_mock_relay_pair(user_fingerprint, remote_fingerprint); // Set up rendezvous code let rendezvous_code = RendezvousCode::from_string("BUF123456".to_string()); - user_proxy.set_rendezvous_code(rendezvous_code.clone()); - remote_proxy.set_peer_fingerprint(user_fingerprint); + user_relay.set_rendezvous_code(rendezvous_code.clone()); + remote_relay.set_peer_fingerprint(user_fingerprint); // Create connection stores let user_connection_store = MemoryConnectionStore::new(); @@ -1127,7 +1127,7 @@ async fn test_credential_request_buffered_during_fingerprint_verification() { } = UserClient::connect( Box::new(user_identity), Box::new(user_connection_store), - Box::new(user_proxy), + Box::new(user_relay), None, None, ) @@ -1149,7 +1149,7 @@ async fn test_credential_request_buffered_during_fingerprint_verification() { } = RemoteClient::connect( Box::new(remote_identity), Box::new(remote_connection_store), - Box::new(remote_proxy), + Box::new(remote_relay), ) .await .expect("RemoteClient should connect"); @@ -1262,8 +1262,8 @@ async fn test_reusable_psk_pairing() { let remote_fingerprint2 = remote_identity2.fingerprint().await; // --- First connection: remote1 --- - let (user_proxy1, remote_proxy1) = - create_mock_proxy_pair(user_fingerprint, remote_fingerprint1); + let (user_relay1, remote_relay1) = + create_mock_relay_pair(user_fingerprint, remote_fingerprint1); let user_connection_store = MemoryConnectionStore::new(); let remote_connection_store1 = MemoryConnectionStore::new(); @@ -1276,7 +1276,7 @@ async fn test_reusable_psk_pairing() { } = UserClient::connect( Box::new(user_identity.clone()), Box::new(user_connection_store), - Box::new(user_proxy1), + Box::new(user_relay1), None, Some(Box::new(psk_store)), ) @@ -1302,7 +1302,7 @@ async fn test_reusable_psk_pairing() { } = RemoteClient::connect( Box::new(remote_identity1), Box::new(remote_connection_store1), - Box::new(remote_proxy1), + Box::new(remote_relay1), ) .await .expect("RemoteClient 1 should connect"); @@ -1338,9 +1338,9 @@ async fn test_reusable_psk_pairing() { drop(user_client); drop(notification_rx); - // Re-create user client with a fresh proxy pair for remote2 - let (user_proxy2, remote_proxy2) = - create_mock_proxy_pair(user_fingerprint, remote_fingerprint2); + // Re-create user client with a fresh relay pair for remote2 + let (user_relay2, remote_relay2) = + create_mock_relay_pair(user_fingerprint, remote_fingerprint2); let user_connection_store2 = MemoryConnectionStore::new(); let remote_connection_store2 = MemoryConnectionStore::new(); @@ -1369,7 +1369,7 @@ async fn test_reusable_psk_pairing() { } = UserClient::connect( Box::new(user_identity), Box::new(user_connection_store2), - Box::new(user_proxy2), + Box::new(user_relay2), None, Some(Box::new(psk_store2)), ) @@ -1384,7 +1384,7 @@ async fn test_reusable_psk_pairing() { } = RemoteClient::connect( Box::new(remote_identity2), Box::new(remote_connection_store2), - Box::new(remote_proxy2), + Box::new(remote_relay2), ) .await .expect("RemoteClient 2 should connect"); @@ -1447,7 +1447,7 @@ async fn test_reusable_psk_with_cached_connection() { let user_fingerprint = user_identity.fingerprint().await; let remote_fingerprint = remote_identity.fingerprint().await; - let (user_proxy, remote_proxy) = create_mock_proxy_pair(user_fingerprint, remote_fingerprint); + let (user_relay, remote_relay) = create_mock_relay_pair(user_fingerprint, remote_fingerprint); // Pre-populate the connection store with the remote's fingerprint // (simulates a previous successful connection) @@ -1474,7 +1474,7 @@ async fn test_reusable_psk_with_cached_connection() { } = UserClient::connect( Box::new(user_identity), Box::new(user_connection_store), - Box::new(user_proxy), + Box::new(user_relay), None, Some(Box::new(psk_store)), ) @@ -1499,7 +1499,7 @@ async fn test_reusable_psk_with_cached_connection() { } = RemoteClient::connect( Box::new(remote_identity), Box::new(MemoryConnectionStore::new()), - Box::new(remote_proxy), + Box::new(remote_relay), ) .await .expect("RemoteClient should connect"); @@ -1557,7 +1557,7 @@ async fn test_reusable_psk_no_store_error() { let user_fingerprint = user_identity.fingerprint().await; let remote_fingerprint = remote_identity.fingerprint().await; - let (user_proxy, _remote_proxy) = create_mock_proxy_pair(user_fingerprint, remote_fingerprint); + let (user_relay, _remote_relay) = create_mock_relay_pair(user_fingerprint, remote_fingerprint); let UserClientHandle { client: user_client, @@ -1566,7 +1566,7 @@ async fn test_reusable_psk_no_store_error() { } = UserClient::connect( Box::new(user_identity), Box::new(MemoryConnectionStore::new()), - Box::new(user_proxy), + Box::new(user_relay), None, None, // No PskStore ) diff --git a/crates/ap-client/tests/websocket_proxy.rs b/crates/ap-client/tests/websocket_relay.rs similarity index 92% rename from crates/ap-client/tests/websocket_proxy.rs rename to crates/ap-client/tests/websocket_relay.rs index be69dc6..ade0485 100644 --- a/crates/ap-client/tests/websocket_proxy.rs +++ b/crates/ap-client/tests/websocket_relay.rs @@ -1,6 +1,6 @@ -//! End-to-end integration tests for WebSocket proxy, pairing, and credential exchange +//! End-to-end integration tests for WebSocket relay, pairing, and credential exchange //! -//! These tests exercise the complete protocol stack using a real WebSocket proxy server, +//! These tests exercise the complete protocol stack using a real WebSocket relay server, //! covering PSK and fingerprint-based pairing modes as well as credential exchange. use std::net::SocketAddr; @@ -8,12 +8,12 @@ use std::sync::Arc; use ap_client::{ ConnectionInfo, ConnectionStore, ConnectionUpdate, CredentialData, CredentialRequestReply, - DefaultProxyClient, FingerprintVerificationReply, MemoryConnectionStore, + DefaultRelayClient, FingerprintVerificationReply, MemoryConnectionStore, MemoryIdentityProvider, PskToken, RemoteClient, RemoteClientHandle, RemoteClientNotification, UserClient, UserClientHandle, UserClientNotification, UserClientRequest, }; -use ap_proxy::server::ProxyServer; -use ap_proxy_protocol::{IdentityFingerprint, IdentityKeyPair}; +use ap_relay::server::RelayServer; +use ap_relay_protocol::{IdentityFingerprint, IdentityKeyPair}; use tokio::time::{Duration, timeout}; use zeroize::Zeroizing; @@ -52,22 +52,22 @@ impl ConnectionStore for SharedConnectionStore { // Test Infrastructure - Helper Functions // ============================================================================ -/// Start a real proxy server for testing and return its address +/// Start a real relay server for testing and return its address async fn start_test_server() -> SocketAddr { let listener = tokio::net::TcpListener::bind("127.0.0.1:0") .await .expect("should bind to localhost"); let addr = listener.local_addr().expect("should get local address"); - let server = ProxyServer::new(addr); + let server = RelayServer::new(addr); tokio::spawn(async move { server.run_with_listener(listener).await.ok() }); addr } -/// Create a DefaultProxyClient connected to the given address -fn create_proxy_client(addr: SocketAddr) -> DefaultProxyClient { - DefaultProxyClient::from_url(format!("ws://{addr}")) +/// Create a DefaultRelayClient connected to the given address +fn create_relay_client(addr: SocketAddr) -> DefaultRelayClient { + DefaultRelayClient::from_url(format!("ws://{addr}")) } /// Create a test credential for use in tests @@ -89,15 +89,15 @@ fn test_credential() -> CredentialData { #[tokio::test] async fn test_e2e_psk_pairing_and_credential_request() { - // 1. Start real proxy server + // 1. Start real relay server let addr = start_test_server().await; // 2. Create identities let user_identity = MemoryIdentityProvider::new(); let remote_identity = MemoryIdentityProvider::new(); - // 3. Create UserClient with DefaultProxyClient - let user_proxy = create_proxy_client(addr); + // 3. Create UserClient with DefaultRelayClient + let user_relay = create_relay_client(addr); let user_connection_store = MemoryConnectionStore::new(); let UserClientHandle { @@ -107,7 +107,7 @@ async fn test_e2e_psk_pairing_and_credential_request() { } = UserClient::connect( Box::new(user_identity), Box::new(user_connection_store), - Box::new(user_proxy), + Box::new(user_relay), None, None, ) @@ -125,8 +125,8 @@ async fn test_e2e_psk_pairing_and_credential_request() { .expect("Should parse PSK token") .into_parts(); - // 5. Create RemoteClient with DefaultProxyClient - let remote_proxy = create_proxy_client(addr); + // 5. Create RemoteClient with DefaultRelayClient + let remote_relay = create_relay_client(addr); let remote_connection_store = MemoryConnectionStore::new(); let RemoteClientHandle { @@ -136,7 +136,7 @@ async fn test_e2e_psk_pairing_and_credential_request() { } = RemoteClient::connect( Box::new(remote_identity), Box::new(remote_connection_store), - Box::new(remote_proxy), + Box::new(remote_relay), ) .await .expect("RemoteClient should connect"); @@ -231,15 +231,15 @@ async fn test_e2e_psk_pairing_and_credential_request() { #[tokio::test] async fn test_e2e_fingerprint_pairing_and_credential_request() { - // 1. Start real proxy server + // 1. Start real relay server let addr = start_test_server().await; // 2. Create identities let user_identity = MemoryIdentityProvider::new(); let remote_identity = MemoryIdentityProvider::new(); - // 3. Create UserClient with DefaultProxyClient - let user_proxy = create_proxy_client(addr); + // 3. Create UserClient with DefaultRelayClient + let user_relay = create_relay_client(addr); let user_connection_store = MemoryConnectionStore::new(); let UserClientHandle { @@ -249,7 +249,7 @@ async fn test_e2e_fingerprint_pairing_and_credential_request() { } = UserClient::connect( Box::new(user_identity), Box::new(user_connection_store), - Box::new(user_proxy), + Box::new(user_relay), None, None, ) @@ -263,8 +263,8 @@ async fn test_e2e_fingerprint_pairing_and_credential_request() { .expect("Should get rendezvous token"); let code = code.as_str().to_string(); - // 5. Create RemoteClient with DefaultProxyClient - let remote_proxy = create_proxy_client(addr); + // 5. Create RemoteClient with DefaultRelayClient + let remote_relay = create_relay_client(addr); let remote_connection_store = MemoryConnectionStore::new(); let RemoteClientHandle { @@ -274,7 +274,7 @@ async fn test_e2e_fingerprint_pairing_and_credential_request() { } = RemoteClient::connect( Box::new(remote_identity), Box::new(remote_connection_store), - Box::new(remote_proxy), + Box::new(remote_relay), ) .await .expect("RemoteClient should connect"); @@ -380,15 +380,15 @@ async fn test_e2e_fingerprint_pairing_and_credential_request() { #[tokio::test] async fn test_e2e_credential_request_denied() { - // 1. Start real proxy server + // 1. Start real relay server let addr = start_test_server().await; // 2. Create identities let user_identity = MemoryIdentityProvider::new(); let remote_identity = MemoryIdentityProvider::new(); - // 3. Create UserClient with DefaultProxyClient - let user_proxy = create_proxy_client(addr); + // 3. Create UserClient with DefaultRelayClient + let user_relay = create_relay_client(addr); let user_connection_store = MemoryConnectionStore::new(); let UserClientHandle { @@ -398,7 +398,7 @@ async fn test_e2e_credential_request_denied() { } = UserClient::connect( Box::new(user_identity), Box::new(user_connection_store), - Box::new(user_proxy), + Box::new(user_relay), None, None, ) @@ -417,7 +417,7 @@ async fn test_e2e_credential_request_denied() { .into_parts(); // 5. Create RemoteClient - let remote_proxy = create_proxy_client(addr); + let remote_relay = create_relay_client(addr); let remote_connection_store = MemoryConnectionStore::new(); let RemoteClientHandle { @@ -427,7 +427,7 @@ async fn test_e2e_credential_request_denied() { } = RemoteClient::connect( Box::new(remote_identity), Box::new(remote_connection_store), - Box::new(remote_proxy), + Box::new(remote_relay), ) .await .expect("RemoteClient should connect"); @@ -488,15 +488,15 @@ async fn test_e2e_credential_request_denied() { #[tokio::test] async fn test_e2e_multiple_credential_requests() { - // 1. Start real proxy server + // 1. Start real relay server let addr = start_test_server().await; // 2. Create identities let user_identity = MemoryIdentityProvider::new(); let remote_identity = MemoryIdentityProvider::new(); - // 3. Create UserClient with DefaultProxyClient - let user_proxy = create_proxy_client(addr); + // 3. Create UserClient with DefaultRelayClient + let user_relay = create_relay_client(addr); let user_connection_store = MemoryConnectionStore::new(); let UserClientHandle { @@ -506,7 +506,7 @@ async fn test_e2e_multiple_credential_requests() { } = UserClient::connect( Box::new(user_identity), Box::new(user_connection_store), - Box::new(user_proxy), + Box::new(user_relay), None, None, ) @@ -525,7 +525,7 @@ async fn test_e2e_multiple_credential_requests() { .into_parts(); // 5. Create RemoteClient - let remote_proxy = create_proxy_client(addr); + let remote_relay = create_relay_client(addr); let remote_connection_store = MemoryConnectionStore::new(); let RemoteClientHandle { @@ -535,7 +535,7 @@ async fn test_e2e_multiple_credential_requests() { } = RemoteClient::connect( Box::new(remote_identity), Box::new(remote_connection_store), - Box::new(remote_proxy), + Box::new(remote_relay), ) .await .expect("RemoteClient should connect"); @@ -623,15 +623,15 @@ async fn test_e2e_multiple_credential_requests() { #[tokio::test] async fn test_e2e_transport_state_persistence() { - // 1. Start real proxy server + // 1. Start real relay server let addr = start_test_server().await; // 2. Create identities let user_identity = MemoryIdentityProvider::new(); let remote_identity = MemoryIdentityProvider::new(); - // 3. Create UserClient with DefaultProxyClient - let user_proxy = create_proxy_client(addr); + // 3. Create UserClient with DefaultRelayClient + let user_relay = create_relay_client(addr); let user_connection_store = MemoryConnectionStore::new(); let UserClientHandle { @@ -641,7 +641,7 @@ async fn test_e2e_transport_state_persistence() { } = UserClient::connect( Box::new(user_identity), Box::new(user_connection_store), - Box::new(user_proxy), + Box::new(user_relay), None, None, ) @@ -660,7 +660,7 @@ async fn test_e2e_transport_state_persistence() { .into_parts(); // 5. Create RemoteClient with shared connection store for later access - let remote_proxy = create_proxy_client(addr); + let remote_relay = create_relay_client(addr); let remote_connection_store = Arc::new(tokio::sync::Mutex::new(MemoryConnectionStore::new())); let connection_store_clone = Arc::clone(&remote_connection_store); @@ -672,7 +672,7 @@ async fn test_e2e_transport_state_persistence() { } = RemoteClient::connect( Box::new(remote_identity), Box::new(SharedConnectionStore(remote_connection_store)), - Box::new(remote_proxy), + Box::new(remote_relay), ) .await .expect("RemoteClient should connect"); @@ -754,15 +754,15 @@ async fn test_e2e_transport_state_persistence() { #[tokio::test] async fn test_e2e_multi_device_credential_response() { - // 1. Start real proxy server + // 1. Start real relay server let addr = start_test_server().await; // 2. Create identities - same user identity for both devices let user_keypair = IdentityKeyPair::generate(); let user_keypair_device2 = user_keypair.clone(); - // 3. Create UserClient Device 1 with DefaultProxyClient - let user_proxy1 = create_proxy_client(addr); + // 3. Create UserClient Device 1 with DefaultRelayClient + let user_relay1 = create_relay_client(addr); // Use Arc for shared access between multiple UserClient devices let user_connection_store1 = Arc::new(tokio::sync::Mutex::new(MemoryConnectionStore::new())); @@ -775,7 +775,7 @@ async fn test_e2e_multi_device_credential_response() { } = UserClient::connect( Box::new(MemoryIdentityProvider::from_keypair(user_keypair)), Box::new(SharedConnectionStore(Arc::clone(&user_connection_store1))), - Box::new(user_proxy1), + Box::new(user_relay1), None, None, ) @@ -794,7 +794,7 @@ async fn test_e2e_multi_device_credential_response() { .into_parts(); // 6. Create RemoteClient - let remote_proxy = create_proxy_client(addr); + let remote_relay = create_relay_client(addr); let RemoteClientHandle { client: remote_client, notifications: mut remote_notification_rx, @@ -802,7 +802,7 @@ async fn test_e2e_multi_device_credential_response() { } = RemoteClient::connect( Box::new(MemoryIdentityProvider::new()), Box::new(MemoryConnectionStore::new()), - Box::new(remote_proxy), + Box::new(remote_relay), ) .await .expect("RemoteClient should connect"); @@ -831,7 +831,7 @@ async fn test_e2e_multi_device_credential_response() { } // 9. Create UserClient Device 2 with SHARED connection store - let user_proxy2 = create_proxy_client(addr); + let user_relay2 = create_relay_client(addr); let UserClientHandle { client: user_client2, notifications: mut notification_rx2, @@ -839,7 +839,7 @@ async fn test_e2e_multi_device_credential_response() { } = UserClient::connect( Box::new(MemoryIdentityProvider::from_keypair(user_keypair_device2)), Box::new(SharedConnectionStore(Arc::clone(&connection_store_clone))), - Box::new(user_proxy2), + Box::new(user_relay2), None, None, ) @@ -858,7 +858,7 @@ async fn test_e2e_multi_device_credential_response() { .expect("Device 2 should listen"); // 10. Spawn response handlers for both devices. - // Both handlers always approve. Since the proxy broadcasts credential + // Both handlers always approve. Since the relay broadcasts credential // requests to all connections with the same identity, both devices // receive every request. Each device independently responds via its // own oneshot. The remote client uses the first response that arrives. diff --git a/crates/ap-proxy-client/Cargo.toml b/crates/ap-relay-client/Cargo.toml similarity index 79% rename from crates/ap-proxy-client/Cargo.toml rename to crates/ap-relay-client/Cargo.toml index 110cc37..07551b1 100644 --- a/crates/ap-proxy-client/Cargo.toml +++ b/crates/ap-relay-client/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "ap-proxy-client" -description = "Client library for connecting to the ap-proxy WebSocket server" +name = "ap-relay-client" +description = "Client library for connecting to the ap-relay WebSocket server" version.workspace = true authors.workspace = true edition.workspace = true @@ -10,11 +10,11 @@ repository.workspace = true [features] default = ["experimental-post-quantum-crypto", "native-websocket"] -experimental-post-quantum-crypto = ["ap-proxy-protocol/experimental-post-quantum-crypto"] +experimental-post-quantum-crypto = ["ap-relay-protocol/experimental-post-quantum-crypto"] native-websocket = ["dep:futures-util", "dep:tokio-tungstenite", "dep:tracing", "tokio/time", "tokio/rt", "tokio/macros"] [dependencies] -ap-proxy-protocol = { workspace = true } +ap-relay-protocol = { workspace = true } futures-util = { workspace = true, optional = true } serde_json = { workspace = true } tokio = { version = "1.36.0", default-features = false, features = ["sync"] } diff --git a/crates/ap-proxy-client/README.md b/crates/ap-relay-client/README.md similarity index 73% rename from crates/ap-proxy-client/README.md rename to crates/ap-relay-client/README.md index 9f518d9..5999efd 100644 --- a/crates/ap-proxy-client/README.md +++ b/crates/ap-relay-client/README.md @@ -1,10 +1,10 @@ -# ap-proxy-client +# ap-relay-client -A client library for connecting to an `ap-proxy` WebSocket server. +A client library for connecting to an `ap-relay` WebSocket server. ## Overview -`ap-proxy-client` provides `ProxyProtocolClient` for connecting to a proxy server, authenticating with cryptographic identities, and exchanging messages with other clients. It uses rustls for TLS. +`ap-relay-client` provides `RelayProtocolClient` for connecting to a relay server, authenticating with cryptographic identities, and exchanging messages with other clients. It uses rustls for TLS. ## Quick Start @@ -12,22 +12,22 @@ Add to your `Cargo.toml`: ```toml [dependencies] -ap-proxy-client = "0.1.0" +ap-relay-client = "0.1.0" ``` Basic client example: ```rust -use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient, IncomingMessage}; +use ap_relay_client::{RelayClientConfig, RelayProtocolClient, IncomingMessage}; #[tokio::main] async fn main() -> Result<(), Box> { - let config = ProxyClientConfig { - proxy_url: "ws://localhost:8080".to_string(), + let config = RelayClientConfig { + relay_url: "ws://localhost:8080".to_string(), identity_keypair: None, // Generates a new identity }; - let mut client = ProxyProtocolClient::new(config); + let mut client = RelayProtocolClient::new(config); let mut incoming = client.connect().await?; println!("Connected! Fingerprint: {:?}", client.fingerprint()); diff --git a/crates/ap-proxy-client/src/config.rs b/crates/ap-relay-client/src/config.rs similarity index 68% rename from crates/ap-proxy-client/src/config.rs rename to crates/ap-relay-client/src/config.rs index faccec8..1ab8450 100644 --- a/crates/ap-proxy-client/src/config.rs +++ b/crates/ap-relay-client/src/config.rs @@ -1,42 +1,42 @@ -use ap_proxy_protocol::{Identity, IdentityFingerprint, RendezvousCode}; +use ap_relay_protocol::{Identity, IdentityFingerprint, RendezvousCode}; -/// Configuration for creating a proxy client. +/// Configuration for creating a relay client. /// /// # Examples /// /// ``` -/// use ap_proxy_client::ProxyClientConfig; +/// use ap_relay_client::RelayClientConfig; /// -/// let config = ProxyClientConfig { -/// proxy_url: "ws://localhost:8080".to_string(), +/// let config = RelayClientConfig { +/// relay_url: "ws://localhost:8080".to_string(), /// }; /// ``` -pub struct ProxyClientConfig { - /// WebSocket URL of the proxy server. +pub struct RelayClientConfig { + /// WebSocket URL of the relay server. /// /// Format: `ws://host:port` or `wss://host:port` for TLS. /// /// # Examples /// - `"ws://localhost:8080"` - Local development - /// - `"wss://proxy.example.com:443"` - Production with TLS - pub proxy_url: String, + /// - `"wss://relay.example.com:443"` - Production with TLS + pub relay_url: String, } -/// Messages received by the client from the proxy server. +/// Messages received by the client from the relay server. /// /// These messages are delivered via the channel returned by -/// [`ProxyProtocolClient::connect()`](crate::ProxyProtocolClient::connect). +/// [`RelayProtocolClient::connect()`](crate::RelayProtocolClient::connect). /// /// # Examples /// /// ```no_run -/// use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient, IncomingMessage, IdentityKeyPair}; +/// use ap_relay_client::{RelayClientConfig, RelayProtocolClient, IncomingMessage, IdentityKeyPair}; /// /// # async fn example() -> Result<(), Box> { -/// let config = ProxyClientConfig { -/// proxy_url: "ws://localhost:8080".to_string(), +/// let config = RelayClientConfig { +/// relay_url: "ws://localhost:8080".to_string(), /// }; -/// let mut client = ProxyProtocolClient::new(config); +/// let mut client = RelayProtocolClient::new(config); /// let mut incoming = client.connect(IdentityKeyPair::generate()).await?; /// /// while let Some(msg) = incoming.recv().await { @@ -59,7 +59,7 @@ pub struct ProxyClientConfig { pub enum IncomingMessage { /// Server responded with a rendezvous code. /// - /// Received in response to [`ProxyProtocolClient::request_rendezvous()`](crate::ProxyProtocolClient::request_rendezvous). + /// Received in response to [`RelayProtocolClient::request_rendezvous()`](crate::RelayProtocolClient::request_rendezvous). /// The code can be shared with other clients to enable them to discover your identity. /// /// Codes expire after 5 minutes and are single-use. @@ -67,7 +67,7 @@ pub enum IncomingMessage { /// Server responded with a peer's identity. /// - /// Received in response to [`ProxyProtocolClient::request_identity()`](crate::ProxyProtocolClient::request_identity). + /// Received in response to [`RelayProtocolClient::request_identity()`](crate::RelayProtocolClient::request_identity). /// Contains the full identity and fingerprint of the peer who created the rendezvous code. /// /// After receiving this, you can send messages to the peer using their fingerprint. @@ -80,11 +80,11 @@ pub enum IncomingMessage { /// Received a message from another client. /// - /// The `source` is cryptographically verified by the proxy server - it cannot be forged. - /// The `payload` should be decrypted or validated by the receiving client, as the proxy + /// The `source` is cryptographically verified by the relay server - it cannot be forged. + /// The `payload` should be decrypted or validated by the receiving client, as the relay /// does not inspect message contents. Send { - /// The sender's fingerprint (validated by proxy) + /// The sender's fingerprint (validated by relay) source: IdentityFingerprint, /// Your fingerprint (the recipient) destination: IdentityFingerprint, diff --git a/crates/ap-proxy-client/src/lib.rs b/crates/ap-relay-client/src/lib.rs similarity index 69% rename from crates/ap-proxy-client/src/lib.rs rename to crates/ap-relay-client/src/lib.rs index 9e01cd4..82cd0f3 100644 --- a/crates/ap-proxy-client/src/lib.rs +++ b/crates/ap-relay-client/src/lib.rs @@ -1,15 +1,15 @@ -//! Client library for connecting to an ap-proxy WebSocket server. +//! Client library for connecting to an ap-relay WebSocket server. //! -//! This crate provides [`ProxyProtocolClient`] for connecting to a proxy server, +//! This crate provides [`RelayProtocolClient`] for connecting to a relay server, //! authenticating, and sending/receiving messages. //! //! # Example //! //! ```no_run -//! use ap_proxy_client::{ProxyProtocolClient, IncomingMessage, IdentityKeyPair}; +//! use ap_relay_client::{RelayProtocolClient, IncomingMessage, IdentityKeyPair}; //! //! # async fn example() -> Result<(), Box> { -//! let mut client = ProxyProtocolClient::from_url("ws://localhost:8080".to_string()); +//! let mut client = RelayProtocolClient::from_url("ws://localhost:8080".to_string()); //! let mut incoming = client.connect(IdentityKeyPair::generate()).await?; //! //! tokio::spawn(async move { @@ -37,12 +37,12 @@ mod config; #[cfg(feature = "native-websocket")] mod protocol_client; -pub use config::{IncomingMessage, ProxyClientConfig}; +pub use config::{IncomingMessage, RelayClientConfig}; #[cfg(feature = "native-websocket")] -pub use protocol_client::ProxyProtocolClient; +pub use protocol_client::RelayProtocolClient; -// Re-export key types from ap-proxy-protocol for ergonomics -pub use ap_proxy_protocol::{ +// Re-export key types from ap-relay-protocol for ergonomics +pub use ap_relay_protocol::{ Challenge, ChallengeResponse, Identity, IdentityFingerprint, IdentityKeyPair, Messages, - ProxyError, RendezvousCode, SignatureAlgorithm, + RelayError, RendezvousCode, SignatureAlgorithm, }; diff --git a/crates/ap-proxy-client/src/protocol_client.rs b/crates/ap-relay-client/src/protocol_client.rs similarity index 79% rename from crates/ap-proxy-client/src/protocol_client.rs rename to crates/ap-relay-client/src/protocol_client.rs index 002f5fb..5c6e759 100644 --- a/crates/ap-proxy-client/src/protocol_client.rs +++ b/crates/ap-relay-client/src/protocol_client.rs @@ -1,5 +1,5 @@ -use ap_proxy_protocol::{ - IdentityFingerprint, IdentityKeyPair, Messages, ProxyError, RendezvousCode, +use ap_relay_protocol::{ + IdentityFingerprint, IdentityKeyPair, Messages, RelayError, RendezvousCode, }; use futures_util::{SinkExt, StreamExt}; use std::sync::Arc; @@ -14,43 +14,43 @@ const PING_INTERVAL: Duration = Duration::from_secs(30); /// Maximum time without a pong before the connection is considered dead. const PONG_TIMEOUT: Duration = Duration::from_secs(60); -use super::config::{ClientState, IncomingMessage, ProxyClientConfig}; +use super::config::{ClientState, IncomingMessage, RelayClientConfig}; -/// Convert tungstenite errors into ProxyError (replaces the From impl that -/// was removed from ap-proxy-protocol to keep it free of tungstenite deps). -fn ws_err(e: tokio_tungstenite::tungstenite::Error) -> ProxyError { - ProxyError::WebSocket(e.to_string()) +/// Convert tungstenite errors into RelayError (replaces the From impl that +/// was removed from ap-relay-protocol to keep it free of tungstenite deps). +fn ws_err(e: tokio_tungstenite::tungstenite::Error) -> RelayError { + RelayError::WebSocket(e.to_string()) } type WsStream = WebSocketStream>; type WsSink = futures_util::stream::SplitSink; type WsSource = futures_util::stream::SplitStream; -/// Client for connecting to and communicating through a ap-proxy server. +/// Client for connecting to and communicating through a ap-relay server. /// -/// This is the main client API for connecting to a proxy server, authenticating, +/// This is the main client API for connecting to a relay server, authenticating, /// discovering peers via rendezvous codes, and sending messages. /// /// # Lifecycle /// -/// 1. Create client with [`new()`](ProxyProtocolClient::new) -/// 2. Connect and authenticate with [`connect()`](ProxyProtocolClient::connect) +/// 1. Create client with [`new()`](RelayProtocolClient::new) +/// 2. Connect and authenticate with [`connect()`](RelayProtocolClient::connect) /// 3. Perform operations (send messages, request rendezvous codes, etc.) -/// 4. Disconnect with [`disconnect()`](ProxyProtocolClient::disconnect) +/// 4. Disconnect with [`disconnect()`](RelayProtocolClient::disconnect) /// /// # Examples /// /// Basic usage: /// /// ```no_run -/// use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient, IncomingMessage, IdentityKeyPair}; +/// use ap_relay_client::{RelayClientConfig, RelayProtocolClient, IncomingMessage, IdentityKeyPair}; /// /// # async fn example() -> Result<(), Box> { /// // Create and connect -/// let config = ProxyClientConfig { -/// proxy_url: "ws://localhost:8080".to_string(), +/// let config = RelayClientConfig { +/// relay_url: "ws://localhost:8080".to_string(), /// }; -/// let mut client = ProxyProtocolClient::new(config); +/// let mut client = RelayProtocolClient::new(config); /// let mut incoming = client.connect(IdentityKeyPair::generate()).await?; /// /// // Handle messages @@ -70,9 +70,9 @@ type WsSource = futures_util::stream::SplitStream; /// # Ok(()) /// # } /// ``` -pub struct ProxyProtocolClient { +pub struct RelayProtocolClient { // Configuration - config: ProxyClientConfig, + config: RelayClientConfig, identity: Option>, // Connection state @@ -86,23 +86,23 @@ pub struct ProxyProtocolClient { write_task_handle: Option>, } -impl ProxyProtocolClient { - /// Create a new proxy client with the given configuration. +impl RelayProtocolClient { + /// Create a new relay client with the given configuration. /// - /// This does not establish a connection - call [`connect()`](ProxyProtocolClient::connect) + /// This does not establish a connection - call [`connect()`](RelayProtocolClient::connect) /// to connect and authenticate with an identity. /// /// # Examples /// /// ``` - /// use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient}; + /// use ap_relay_client::{RelayClientConfig, RelayProtocolClient}; /// - /// let config = ProxyClientConfig { - /// proxy_url: "ws://localhost:8080".to_string(), + /// let config = RelayClientConfig { + /// relay_url: "ws://localhost:8080".to_string(), /// }; - /// let client = ProxyProtocolClient::new(config); + /// let client = RelayProtocolClient::new(config); /// ``` - pub fn new(config: ProxyClientConfig) -> Self { + pub fn new(config: RelayClientConfig) -> Self { Self { config, identity: None, @@ -113,14 +113,14 @@ impl ProxyProtocolClient { } } - /// Create a new proxy client from just a URL. + /// Create a new relay client from just a URL. /// - /// Convenience constructor equivalent to `new(ProxyClientConfig { proxy_url })`. - pub fn from_url(proxy_url: String) -> Self { - Self::new(ProxyClientConfig { proxy_url }) + /// Convenience constructor equivalent to `new(RelayClientConfig { relay_url })`. + pub fn from_url(relay_url: String) -> Self { + Self::new(RelayClientConfig { relay_url }) } - /// Connect to the proxy server and perform authentication. + /// Connect to the relay server and perform authentication. /// /// Establishes a WebSocket connection, completes the challenge-response authentication /// using the provided identity, and returns a channel for receiving incoming messages. @@ -136,25 +136,25 @@ impl ProxyProtocolClient { /// # Timeout /// /// Authentication must complete within 5 seconds or this method returns - /// [`ProxyError::AuthenticationTimeout`]. + /// [`RelayError::AuthenticationTimeout`]. /// /// # Errors /// - /// - [`ProxyError::AlreadyConnected`] if already connected - /// - [`ProxyError::WebSocket`] if connection fails - /// - [`ProxyError::AuthenticationFailed`] if signature verification fails - /// - [`ProxyError::AuthenticationTimeout`] if authentication takes too long + /// - [`RelayError::AlreadyConnected`] if already connected + /// - [`RelayError::WebSocket`] if connection fails + /// - [`RelayError::AuthenticationFailed`] if signature verification fails + /// - [`RelayError::AuthenticationTimeout`] if authentication takes too long /// /// # Examples /// /// ```no_run - /// use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient, IncomingMessage, IdentityKeyPair}; + /// use ap_relay_client::{RelayClientConfig, RelayProtocolClient, IncomingMessage, IdentityKeyPair}; /// /// # async fn example() -> Result<(), Box> { - /// let config = ProxyClientConfig { - /// proxy_url: "ws://localhost:8080".to_string(), + /// let config = RelayClientConfig { + /// relay_url: "ws://localhost:8080".to_string(), /// }; - /// let mut client = ProxyProtocolClient::new(config); + /// let mut client = RelayProtocolClient::new(config); /// /// // Connect and get incoming message channel /// let mut incoming = client.connect(IdentityKeyPair::generate()).await?; @@ -169,12 +169,12 @@ impl ProxyProtocolClient { pub async fn connect( &mut self, identity: IdentityKeyPair, - ) -> Result, ProxyError> { + ) -> Result, RelayError> { // Check not already connected { let state = self.state.lock().await; if !matches!(*state, ClientState::Disconnected) { - return Err(ProxyError::AlreadyConnected); + return Err(RelayError::AlreadyConnected); } } @@ -183,7 +183,7 @@ impl ProxyProtocolClient { self.identity = Some(Arc::clone(&identity)); // Connect WebSocket - let (ws_stream, _) = connect_async(&self.config.proxy_url) + let (ws_stream, _) = connect_async(&self.config.relay_url) .await .map_err(ws_err)?; @@ -196,7 +196,7 @@ impl ProxyProtocolClient { // Create channels let (outgoing_tx, outgoing_rx) = mpsc::unbounded_channel::(); let (incoming_tx, incoming_rx) = mpsc::unbounded_channel::(); - let (auth_tx, mut auth_rx) = mpsc::unbounded_channel::>(); + let (auth_tx, mut auth_rx) = mpsc::unbounded_channel::>(); // Shared pong tracker for keep-alive let last_pong = Arc::new(Mutex::new(Instant::now())); @@ -236,7 +236,7 @@ impl ProxyProtocolClient { self.read_task_handle = Some(read_handle); self.write_task_handle = Some(write_handle); self.disconnect().await?; - return Err(ProxyError::AuthenticationTimeout); + return Err(RelayError::AuthenticationTimeout); } } @@ -250,35 +250,35 @@ impl ProxyProtocolClient { /// Send a message to another authenticated client. /// - /// The message is routed through the proxy server to the destination client. - /// The proxy validates the source identity but cannot inspect the payload. + /// The message is routed through the relay server to the destination client. + /// The relay validates the source identity but cannot inspect the payload. /// /// # Authentication Required /// /// This method requires an active authenticated connection. Call - /// [`connect()`](ProxyProtocolClient::connect) first. + /// [`connect()`](RelayProtocolClient::connect) first. /// /// # Payload Encryption /// - /// The proxy does not encrypt message payloads. Clients should implement + /// The relay does not encrypt message payloads. Clients should implement /// end-to-end encryption (e.g., using the Noise protocol) before calling this method. /// /// # Errors /// - /// - [`ProxyError::NotConnected`] if not connected or not authenticated - /// - [`ProxyError::DestinationNotFound`] if the destination client is not connected - /// - [`ProxyError::Serialization`] if message encoding fails + /// - [`RelayError::NotConnected`] if not connected or not authenticated + /// - [`RelayError::DestinationNotFound`] if the destination client is not connected + /// - [`RelayError::Serialization`] if message encoding fails /// /// # Examples /// /// ```no_run - /// use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient, IdentityKeyPair}; + /// use ap_relay_client::{RelayClientConfig, RelayProtocolClient, IdentityKeyPair}; /// /// # async fn example() -> Result<(), Box> { - /// # let config = ProxyClientConfig { - /// # proxy_url: "ws://localhost:8080".to_string(), + /// # let config = RelayClientConfig { + /// # relay_url: "ws://localhost:8080".to_string(), /// # }; - /// let mut client = ProxyProtocolClient::new(config); + /// let mut client = RelayProtocolClient::new(config); /// client.connect(IdentityKeyPair::generate()).await?; /// /// // Get destination fingerprint from rendezvous lookup @@ -294,12 +294,12 @@ impl ProxyProtocolClient { &self, destination: IdentityFingerprint, payload: Vec, - ) -> Result<(), ProxyError> { + ) -> Result<(), RelayError> { // Check authenticated { let state = self.state.lock().await; if !matches!(*state, ClientState::Authenticated { .. }) { - return Err(ProxyError::NotConnected); + return Err(RelayError::NotConnected); } } @@ -315,10 +315,10 @@ impl ProxyProtocolClient { // Send via outgoing_tx channel if let Some(tx) = &self.outgoing_tx { tx.send(Message::Text(json)) - .map_err(|_| ProxyError::ChannelSendFailed)?; + .map_err(|_| RelayError::ChannelSendFailed)?; Ok(()) } else { - Err(ProxyError::NotConnected) + Err(RelayError::NotConnected) } } @@ -326,7 +326,7 @@ impl ProxyProtocolClient { /// /// The server will generate a temporary code (format: "ABC-DEF-GHI") that maps to your /// identity. The code will be delivered via [`IncomingMessage::RendezvousInfo`] on the - /// channel returned by [`connect()`](ProxyProtocolClient::connect). + /// channel returned by [`connect()`](RelayProtocolClient::connect). /// /// # Rendezvous Code Properties /// @@ -340,7 +340,7 @@ impl ProxyProtocolClient { /// 1. Call this method to request a code /// 2. Receive the code via [`IncomingMessage::RendezvousInfo`] /// 3. Share the code with a peer (e.g., display as QR code) - /// 4. Peer uses [`request_identity()`](ProxyProtocolClient::request_identity) to look up your identity + /// 4. Peer uses [`request_identity()`](RelayProtocolClient::request_identity) to look up your identity /// /// # Authentication Required /// @@ -348,15 +348,15 @@ impl ProxyProtocolClient { /// /// # Errors /// - /// - [`ProxyError::NotConnected`] if not connected or not authenticated + /// - [`RelayError::NotConnected`] if not connected or not authenticated /// /// # Examples /// /// ```no_run - /// use ap_proxy_client::{ProxyProtocolClient, IncomingMessage, IdentityKeyPair}; + /// use ap_relay_client::{RelayProtocolClient, IncomingMessage, IdentityKeyPair}; /// /// # async fn example() -> Result<(), Box> { - /// let mut client = ProxyProtocolClient::from_url("ws://localhost:8080".to_string()); + /// let mut client = RelayProtocolClient::from_url("ws://localhost:8080".to_string()); /// let mut incoming = client.connect(IdentityKeyPair::generate()).await?; /// /// // Request a code @@ -370,12 +370,12 @@ impl ProxyProtocolClient { /// # Ok(()) /// # } /// ``` - pub async fn request_rendezvous(&self) -> Result<(), ProxyError> { + pub async fn request_rendezvous(&self) -> Result<(), RelayError> { // Check authenticated { let state = self.state.lock().await; if !matches!(*state, ClientState::Authenticated { .. }) { - return Err(ProxyError::NotConnected); + return Err(RelayError::NotConnected); } } @@ -386,10 +386,10 @@ impl ProxyProtocolClient { // Send via outgoing_tx channel if let Some(tx) = &self.outgoing_tx { tx.send(Message::Text(json)) - .map_err(|_| ProxyError::ChannelSendFailed)?; + .map_err(|_| RelayError::ChannelSendFailed)?; Ok(()) } else { - Err(ProxyError::NotConnected) + Err(RelayError::NotConnected) } } @@ -410,7 +410,7 @@ impl ProxyProtocolClient { /// /// # Errors /// - /// - [`ProxyError::NotConnected`] if not connected or not authenticated + /// - [`RelayError::NotConnected`] if not connected or not authenticated /// /// The server may not respond if the code is invalid, expired, or already used. /// Implement a timeout when waiting for the response. @@ -418,10 +418,10 @@ impl ProxyProtocolClient { /// # Examples /// /// ```no_run - /// use ap_proxy_client::{ProxyProtocolClient, IncomingMessage, IdentityKeyPair, RendezvousCode}; + /// use ap_relay_client::{RelayProtocolClient, IncomingMessage, IdentityKeyPair, RendezvousCode}; /// /// # async fn example() -> Result<(), Box> { - /// let mut client = ProxyProtocolClient::from_url("ws://localhost:8080".to_string()); + /// let mut client = RelayProtocolClient::from_url("ws://localhost:8080".to_string()); /// let mut incoming = client.connect(IdentityKeyPair::generate()).await?; /// /// // Get code from user (e.g., QR scan, text input) @@ -450,12 +450,12 @@ impl ProxyProtocolClient { pub async fn request_identity( &self, rendezvous_code: RendezvousCode, - ) -> Result<(), ProxyError> { + ) -> Result<(), RelayError> { // Check authenticated { let state = self.state.lock().await; if !matches!(*state, ClientState::Authenticated { .. }) { - return Err(ProxyError::NotConnected); + return Err(RelayError::NotConnected); } } @@ -466,10 +466,10 @@ impl ProxyProtocolClient { // Send via outgoing_tx channel if let Some(tx) = &self.outgoing_tx { tx.send(Message::Text(json)) - .map_err(|_| ProxyError::ChannelSendFailed)?; + .map_err(|_| RelayError::ChannelSendFailed)?; Ok(()) } else { - Err(ProxyError::NotConnected) + Err(RelayError::NotConnected) } } @@ -484,13 +484,13 @@ impl ProxyProtocolClient { /// # Examples /// /// ```no_run - /// use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient, IdentityKeyPair}; + /// use ap_relay_client::{RelayClientConfig, RelayProtocolClient, IdentityKeyPair}; /// /// # async fn example() -> Result<(), Box> { - /// let config = ProxyClientConfig { - /// proxy_url: "ws://localhost:8080".to_string(), + /// let config = RelayClientConfig { + /// relay_url: "ws://localhost:8080".to_string(), /// }; - /// let mut client = ProxyProtocolClient::new(config); + /// let mut client = RelayProtocolClient::new(config); /// client.connect(IdentityKeyPair::generate()).await?; /// println!("My fingerprint: {:?}", client.fingerprint()); /// # Ok(()) @@ -512,13 +512,13 @@ impl ProxyProtocolClient { /// # Examples /// /// ```no_run - /// use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient, IdentityKeyPair}; + /// use ap_relay_client::{RelayClientConfig, RelayProtocolClient, IdentityKeyPair}; /// /// # async fn example() -> Result<(), Box> { - /// let config = ProxyClientConfig { - /// proxy_url: "ws://localhost:8080".to_string(), + /// let config = RelayClientConfig { + /// relay_url: "ws://localhost:8080".to_string(), /// }; - /// let mut client = ProxyProtocolClient::new(config); + /// let mut client = RelayProtocolClient::new(config); /// /// assert!(!client.is_authenticated().await); /// @@ -534,10 +534,10 @@ impl ProxyProtocolClient { matches!(*self.state.lock().await, ClientState::Authenticated { .. }) } - /// Disconnect from the proxy server and clean up resources. + /// Disconnect from the relay server and clean up resources. /// /// Aborts background tasks, closes the WebSocket connection, and resets state. - /// After disconnecting, you can call [`connect()`](ProxyProtocolClient::connect) + /// After disconnecting, you can call [`connect()`](RelayProtocolClient::connect) /// again to reconnect. /// /// This method is automatically called when the client is dropped, but calling it @@ -546,13 +546,13 @@ impl ProxyProtocolClient { /// # Examples /// /// ```no_run - /// use ap_proxy_client::{ProxyClientConfig, ProxyProtocolClient, IdentityKeyPair}; + /// use ap_relay_client::{RelayClientConfig, RelayProtocolClient, IdentityKeyPair}; /// /// # async fn example() -> Result<(), Box> { - /// # let config = ProxyClientConfig { - /// # proxy_url: "ws://localhost:8080".to_string(), + /// # let config = RelayClientConfig { + /// # relay_url: "ws://localhost:8080".to_string(), /// # }; - /// let mut client = ProxyProtocolClient::new(config); + /// let mut client = RelayProtocolClient::new(config); /// client.connect(IdentityKeyPair::generate()).await?; /// /// // Do work... @@ -562,7 +562,7 @@ impl ProxyProtocolClient { /// # Ok(()) /// # } /// ``` - pub async fn disconnect(&mut self) -> Result<(), ProxyError> { + pub async fn disconnect(&mut self) -> Result<(), RelayError> { // Abort tasks if let Some(handle) = self.read_task_handle.take() { handle.abort(); @@ -623,7 +623,7 @@ impl ProxyProtocolClient { incoming_tx: mpsc::UnboundedSender, identity: Arc, state: Arc>, - auth_tx: mpsc::UnboundedSender>, + auth_tx: mpsc::UnboundedSender>, last_pong: Arc>, ) { // Handle authentication @@ -655,20 +655,20 @@ impl ProxyProtocolClient { ws_source: &mut WsSource, outgoing_tx: &mpsc::UnboundedSender, identity: &Arc, - ) -> Result { + ) -> Result { // Receive AuthChallenge let challenge_msg = ws_source .next() .await - .ok_or(ProxyError::ConnectionClosed)? + .ok_or(RelayError::ConnectionClosed)? .map_err(ws_err)?; let challenge = match challenge_msg { Message::Text(text) => match serde_json::from_str::(&text)? { Messages::AuthChallenge(c) => c, - _ => return Err(ProxyError::InvalidMessage("Expected AuthChallenge".into())), + _ => return Err(RelayError::InvalidMessage("Expected AuthChallenge".into())), }, - _ => return Err(ProxyError::InvalidMessage("Expected text message".into())), + _ => return Err(RelayError::InvalidMessage("Expected text message".into())), }; // Sign challenge @@ -679,7 +679,7 @@ impl ProxyProtocolClient { // Send auth response outgoing_tx .send(Message::Text(auth_json)) - .map_err(|_| ProxyError::ChannelSendFailed)?; + .map_err(|_| RelayError::ChannelSendFailed)?; // Authentication complete - server doesn't send confirmation Ok(identity.identity().fingerprint()) @@ -690,7 +690,7 @@ impl ProxyProtocolClient { mut ws_source: WsSource, incoming_tx: mpsc::UnboundedSender, last_pong: Arc>, - ) -> Result<(), ProxyError> { + ) -> Result<(), RelayError> { while let Some(msg_result) = ws_source.next().await { let msg = msg_result.map_err(ws_err)?; diff --git a/crates/ap-proxy-protocol/Cargo.toml b/crates/ap-relay-protocol/Cargo.toml similarity index 86% rename from crates/ap-proxy-protocol/Cargo.toml rename to crates/ap-relay-protocol/Cargo.toml index 857571d..b821163 100644 --- a/crates/ap-proxy-protocol/Cargo.toml +++ b/crates/ap-relay-protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "ap-proxy-protocol" -description = "Shared wire protocol types for the ap-proxy WebSocket server" +name = "ap-relay-protocol" +description = "Shared wire protocol types for the ap-relay WebSocket server" version.workspace = true authors.workspace = true edition.workspace = true diff --git a/crates/ap-proxy-protocol/src/auth.rs b/crates/ap-relay-protocol/src/auth.rs similarity index 94% rename from crates/ap-proxy-protocol/src/auth.rs rename to crates/ap-relay-protocol/src/auth.rs index 30ae292..92879e4 100644 --- a/crates/ap-proxy-protocol/src/auth.rs +++ b/crates/ap-relay-protocol/src/auth.rs @@ -1,6 +1,6 @@ -//! Authentication module for the ap-proxy crate. Authentication works by creating a cryptographic -//! identity - a signature key-pair. The identity is the public key. It is proven to the proxy, by -//! signing a challenge provided by the proxy using the signature key. +//! Authentication module for the ap-relay crate. Authentication works by creating a cryptographic +//! identity - a signature key-pair. The identity is the public key. It is proven to the relay, by +//! signing a challenge provided by the relay using the signature key. use coset::{ CborSerializable, CoseKey, CoseKeyBuilder, CoseSign1, HeaderBuilder, Label, @@ -13,7 +13,7 @@ use rand::RngCore; use serde::{Deserialize, Serialize}; use sha2::Digest; -use crate::error::ProxyError; +use crate::error::RelayError; /// Signature algorithm selection for key generation. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -153,12 +153,12 @@ impl IdentityKeyPair { } /// Deserialize a key pair from COSE key format. - pub fn from_cose(cose_bytes: &[u8]) -> Result { + pub fn from_cose(cose_bytes: &[u8]) -> Result { let cose_key = CoseKey::from_slice(cose_bytes) - .map_err(|_| ProxyError::InvalidMessage("Invalid COSE key encoding".to_string()))?; + .map_err(|_| RelayError::InvalidMessage("Invalid COSE key encoding".to_string()))?; let alg = cose_key.alg.as_ref().ok_or_else(|| { - ProxyError::InvalidMessage("Missing algorithm in COSE key".to_string()) + RelayError::InvalidMessage("Missing algorithm in COSE key".to_string()) })?; match alg { @@ -178,7 +178,7 @@ impl IdentityKeyPair { } let seed = seed.ok_or_else(|| { - ProxyError::InvalidMessage( + RelayError::InvalidMessage( "Missing Ed25519 private key seed in COSE key".to_string(), ) })?; @@ -210,7 +210,7 @@ impl IdentityKeyPair { } let seed = seed.ok_or_else(|| { - ProxyError::InvalidMessage( + RelayError::InvalidMessage( "Missing ML-DSA-65 private key seed in COSE key".to_string(), ) })?; @@ -224,7 +224,7 @@ impl IdentityKeyPair { public_key: Box::new(public_key.clone()), }) } - _ => Err(ProxyError::InvalidMessage( + _ => Err(RelayError::InvalidMessage( "Unsupported algorithm in COSE key".to_string(), )), } @@ -233,12 +233,12 @@ impl IdentityKeyPair { /// Get the public identity corresponding to this key pair. /// /// The public [`Identity`] contains only the public key and can be shared freely. - /// It is used to verify signatures and identify clients to the proxy. + /// It is used to verify signatures and identify clients to the relay. /// /// # Examples /// /// ``` - /// use ap_proxy_protocol::IdentityKeyPair; + /// use ap_relay_protocol::IdentityKeyPair; /// /// let keypair = IdentityKeyPair::generate(); /// let public_identity = keypair.identity(); @@ -254,12 +254,12 @@ impl IdentityKeyPair { /// A public cryptographic identity. /// /// Contains the COSE-encoded public key that identifies a client. This can be shared freely -/// and is used by the proxy to verify challenge-response signatures. +/// and is used by the relay to verify challenge-response signatures. /// /// # Examples /// /// ``` -/// use ap_proxy_protocol::IdentityKeyPair; +/// use ap_relay_protocol::IdentityKeyPair; /// /// let keypair = IdentityKeyPair::generate(); /// let identity = keypair.identity(); @@ -369,7 +369,7 @@ impl Identity { /// and uniform-length identifier. Fingerprints are used for: /// - Identifying clients in message routing /// - Displaying identities to users - /// - Indexing connections in the proxy server + /// - Indexing connections in the relay server /// /// The fingerprint is deterministic - the same identity always produces /// the same fingerprint. @@ -377,7 +377,7 @@ impl Identity { /// # Examples /// /// ``` - /// use ap_proxy_protocol::IdentityKeyPair; + /// use ap_relay_protocol::IdentityKeyPair; /// /// let keypair = IdentityKeyPair::generate(); /// let identity = keypair.identity(); @@ -399,12 +399,12 @@ impl Identity { /// /// Fingerprints are 32-byte hashes of public keys, providing a uniform-length /// identifier that is easier to work with than full public keys. They are used -/// throughout the proxy protocol for addressing clients. +/// throughout the relay protocol for addressing clients. /// /// # Examples /// /// ``` -/// use ap_proxy_protocol::IdentityKeyPair; +/// use ap_relay_protocol::IdentityKeyPair; /// use std::collections::HashMap; /// /// let keypair = IdentityKeyPair::generate(); @@ -428,27 +428,27 @@ impl IdentityFingerprint { /// /// # Errors /// - /// Returns [`ProxyError::InvalidMessage`] if the string is not exactly 64 + /// Returns [`RelayError::InvalidMessage`] if the string is not exactly 64 /// hex characters or contains non-hex characters. /// /// # Examples /// /// ``` - /// use ap_proxy_protocol::IdentityFingerprint; + /// use ap_relay_protocol::IdentityFingerprint; /// /// let hex_str = "a".repeat(64); /// let fp = IdentityFingerprint::from_hex(&hex_str).unwrap(); /// assert_eq!(fp.to_hex(), hex_str); /// ``` - pub fn from_hex(s: &str) -> Result { + pub fn from_hex(s: &str) -> Result { if s.len() != 64 { - return Err(crate::error::ProxyError::InvalidMessage(format!( + return Err(crate::error::RelayError::InvalidMessage(format!( "Fingerprint hex must be exactly 64 characters, got {}", s.len() ))); } let bytes = hex::decode(s).map_err(|e| { - crate::error::ProxyError::InvalidMessage(format!("Invalid hex in fingerprint: {e}")) + crate::error::RelayError::InvalidMessage(format!("Invalid hex in fingerprint: {e}")) })?; let mut arr = [0u8; 32]; arr.copy_from_slice(&bytes); @@ -461,7 +461,7 @@ impl IdentityFingerprint { } } -/// A cryptographic challenge issued by the proxy server for authentication. +/// A cryptographic challenge issued by the relay server for authentication. /// /// The server sends a random challenge to newly connected clients. Clients must /// sign this challenge with their private key to prove their identity without @@ -480,7 +480,7 @@ impl IdentityFingerprint { /// Server-side challenge generation: /// /// ``` -/// use ap_proxy_protocol::Challenge; +/// use ap_relay_protocol::Challenge; /// /// let challenge = Challenge::new(); /// // Send to client for signing @@ -489,7 +489,7 @@ impl IdentityFingerprint { /// Client-side challenge signing: /// /// ``` -/// use ap_proxy_protocol::{Challenge, IdentityKeyPair}; +/// use ap_relay_protocol::{Challenge, IdentityKeyPair}; /// /// let keypair = IdentityKeyPair::generate(); /// # let challenge = Challenge::new(); @@ -514,7 +514,7 @@ impl Challenge { /// # Examples /// /// ``` - /// use ap_proxy_protocol::Challenge; + /// use ap_relay_protocol::Challenge; /// /// let challenge = Challenge::new(); /// // Each call produces a different random challenge @@ -532,7 +532,7 @@ impl Challenge { /// # Examples /// /// ``` - /// use ap_proxy_protocol::{Challenge, IdentityKeyPair}; + /// use ap_relay_protocol::{Challenge, IdentityKeyPair}; /// /// let keypair = IdentityKeyPair::generate(); /// let challenge = Challenge::new(); @@ -606,7 +606,7 @@ impl Challenge { /// Create and verify a challenge response: /// /// ``` -/// use ap_proxy_protocol::{Challenge, IdentityKeyPair}; +/// use ap_relay_protocol::{Challenge, IdentityKeyPair}; /// /// // Client signs challenge /// let keypair = IdentityKeyPair::generate(); @@ -643,7 +643,7 @@ impl ChallengeResponse { /// # Examples /// /// ``` - /// use ap_proxy_protocol::{Challenge, IdentityKeyPair}; + /// use ap_relay_protocol::{Challenge, IdentityKeyPair}; /// /// let keypair = IdentityKeyPair::generate(); /// let challenge = Challenge::new(); diff --git a/crates/ap-proxy-protocol/src/error.rs b/crates/ap-relay-protocol/src/error.rs similarity index 95% rename from crates/ap-proxy-protocol/src/error.rs rename to crates/ap-relay-protocol/src/error.rs index b70b202..541c2ea 100644 --- a/crates/ap-proxy-protocol/src/error.rs +++ b/crates/ap-relay-protocol/src/error.rs @@ -1,14 +1,14 @@ -//! Error types for proxy operations. +//! Error types for relay operations. //! -//! This module defines all error conditions that can occur during proxy client +//! This module defines all error conditions that can occur during relay client //! and server operations. use crate::auth::IdentityFingerprint; use thiserror::Error; -/// Errors that can occur during proxy client or server operations. +/// Errors that can occur during relay client or server operations. #[derive(Debug, Error)] -pub enum ProxyError { +pub enum RelayError { /// WebSocket connection or communication error. /// /// Occurs when the underlying WebSocket connection fails, including network @@ -38,7 +38,7 @@ pub enum ProxyError { /// Attempted to send a message to a client that is not connected. /// /// Occurs when sending a message to a fingerprint that: - /// - Never connected to the proxy + /// - Never connected to the relay /// - Has disconnected /// - Does not exist /// diff --git a/crates/ap-proxy-protocol/src/lib.rs b/crates/ap-relay-protocol/src/lib.rs similarity index 57% rename from crates/ap-proxy-protocol/src/lib.rs rename to crates/ap-relay-protocol/src/lib.rs index f31585a..bbfc79c 100644 --- a/crates/ap-proxy-protocol/src/lib.rs +++ b/crates/ap-relay-protocol/src/lib.rs @@ -1,7 +1,7 @@ -//! Shared wire protocol types for the ap-proxy WebSocket server. +//! Shared wire protocol types for the ap-relay WebSocket server. //! -//! This crate contains the protocol types used by both the proxy server -//! and proxy client, with zero TLS dependencies. +//! This crate contains the protocol types used by both the relay server +//! and relay client, with zero TLS dependencies. pub mod auth; pub mod error; @@ -12,6 +12,6 @@ pub use auth::{ Challenge, ChallengeResponse, Identity, IdentityFingerprint, IdentityKeyPair, SignatureAlgorithm, }; -pub use error::ProxyError; +pub use error::RelayError; pub use messages::Messages; pub use rendezvous::RendezvousCode; diff --git a/crates/ap-proxy-protocol/src/messages.rs b/crates/ap-relay-protocol/src/messages.rs similarity index 93% rename from crates/ap-proxy-protocol/src/messages.rs rename to crates/ap-relay-protocol/src/messages.rs index 8a6b1c1..3ba7a6f 100644 --- a/crates/ap-proxy-protocol/src/messages.rs +++ b/crates/ap-relay-protocol/src/messages.rs @@ -1,4 +1,4 @@ -//! Protocol message types for client-proxy communication. +//! Protocol message types for client-relay communication. //! //! This module defines the message types used in the three-phase protocol: //! @@ -26,7 +26,7 @@ use crate::{ }; use serde::{Deserialize, Serialize}; -/// Protocol messages exchanged between clients and the proxy server. +/// Protocol messages exchanged between clients and the relay server. /// /// Messages flow through three distinct phases: authentication, optional rendezvous /// for peer discovery, and message routing between authenticated clients. @@ -73,13 +73,13 @@ pub enum Messages { identity: Identity, }, - /// A message routed from one client to another through the proxy. + /// A message routed from one client to another through the relay. /// /// When sent by clients, only contains destination and payload. The source is - /// automatically set by the proxy based on the authenticated identity. + /// automatically set by the relay based on the authenticated identity. /// When forwarded to recipients, includes the validated source fingerprint. Send { - /// The authenticated sender's fingerprint (added by proxy) + /// The authenticated sender's fingerprint (added by relay) #[serde(skip_serializing_if = "Option::is_none")] source: Option, /// The recipient's fingerprint diff --git a/crates/ap-proxy-protocol/src/rendezvous.rs b/crates/ap-relay-protocol/src/rendezvous.rs similarity index 93% rename from crates/ap-proxy-protocol/src/rendezvous.rs rename to crates/ap-relay-protocol/src/rendezvous.rs index 3b539c6..61938b9 100644 --- a/crates/ap-proxy-protocol/src/rendezvous.rs +++ b/crates/ap-relay-protocol/src/rendezvous.rs @@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize}; /// A temporary rendezvous code for peer discovery. /// /// Rendezvous codes are short, human-readable identifiers (format: "ABC-DEF-GHI") that -/// temporarily map to a client's identity on the proxy server. +/// temporarily map to a client's identity on the relay server. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RendezvousCode { code: String, @@ -42,7 +42,7 @@ impl RendezvousCode { /// # Examples /// /// ``` - /// use ap_proxy_protocol::RendezvousCode; + /// use ap_relay_protocol::RendezvousCode; /// /// let code1 = RendezvousCode::new(); /// let code2 = RendezvousCode::new(); @@ -59,7 +59,7 @@ impl RendezvousCode { let mut rng = rand::thread_rng(); // The code has an alphabet of size 36. With 9 characters, that's - // 36^9 = 101,559,956,668,416 possible codes. The codes are short-lived, and the connections to the proxy are rate-limited, + // 36^9 = 101,559,956,668,416 possible codes. The codes are short-lived, and the connections to the relay are rate-limited, // which is why this is considered sufficient. let code = Alphanumeric.sample_string(&mut rng, 9); let code = code.to_ascii_uppercase(); @@ -84,7 +84,7 @@ impl RendezvousCode { /// # Examples /// /// ``` - /// use ap_proxy_protocol::RendezvousCode; + /// use ap_relay_protocol::RendezvousCode; /// /// let code = RendezvousCode::from_string("ABC-DEF-GHI".to_string()); /// assert_eq!(code.as_str(), "ABC-DEF-GHI"); @@ -101,7 +101,7 @@ impl RendezvousCode { /// # Examples /// /// ``` - /// use ap_proxy_protocol::RendezvousCode; + /// use ap_relay_protocol::RendezvousCode; /// /// let code = RendezvousCode::new(); /// println!("Your code: {}", code.as_str()); diff --git a/crates/ap-proxy/Cargo.toml b/crates/ap-relay/Cargo.toml similarity index 72% rename from crates/ap-proxy/Cargo.toml rename to crates/ap-relay/Cargo.toml index 47e2402..90a7c2b 100644 --- a/crates/ap-proxy/Cargo.toml +++ b/crates/ap-relay/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "ap-proxy" -description = "Zero-knowledge WebSocket proxy server for access-protocol" +name = "ap-relay" +description = "Zero-knowledge WebSocket relay server for access-protocol" version.workspace = true authors.workspace = true edition.workspace = true @@ -10,14 +10,14 @@ repository.workspace = true [features] default = ["experimental-post-quantum-crypto"] -experimental-post-quantum-crypto = ["ap-proxy-protocol/experimental-post-quantum-crypto"] +experimental-post-quantum-crypto = ["ap-relay-protocol/experimental-post-quantum-crypto"] [[bin]] -name = "ap-proxy" +name = "ap-relay" path = "src/main.rs" [dependencies] -ap-proxy-protocol = { workspace = true } +ap-relay-protocol = { workspace = true } futures-util = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true, features = ["net", "signal", "sync", "time"] } @@ -26,7 +26,7 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true } [dev-dependencies] -ap-proxy-client = { workspace = true, features = ["native-websocket"] } +ap-relay-client = { workspace = true, features = ["native-websocket"] } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } [lints] diff --git a/crates/ap-proxy/README.md b/crates/ap-relay/README.md similarity index 64% rename from crates/ap-proxy/README.md rename to crates/ap-relay/README.md index daeecaf..1c3a00d 100644 --- a/crates/ap-proxy/README.md +++ b/crates/ap-relay/README.md @@ -1,19 +1,19 @@ -# ap-proxy +# ap-relay -A WebSocket proxy server for `aac` (ap-cli) that routes messages between authenticated clients without access to message contents. +A WebSocket relay server for `aac` (ap-cli) that routes messages between authenticated clients without access to message contents. > [!IMPORTANT] -> This proxy is not hardened for production. There are known reliability & safety improvements to be made. +> This relay is not hardened for production. There are known reliability & safety improvements to be made. -For the client library, see [`ap-proxy-client`](../ap-proxy-client/). -For shared protocol types, see [`ap-proxy-protocol`](../ap-proxy-protocol/). +For the client library, see [`ap-relay-client`](../ap-relay-client/). +For shared protocol types, see [`ap-relay-protocol`](../ap-relay-protocol/). ## Quick Start -### Running the Proxy Server +### Running the Relay Server ```bash -cargo run --bin ap-proxy +cargo run --bin ap-relay ``` The server will start listening on `ws://localhost:8080` by default. @@ -21,13 +21,13 @@ The server will start listening on `ws://localhost:8080` by default. ### Embedding in Your Application ```rust -use ap_proxy::server::ProxyServer; +use ap_relay::server::RelayServer; use std::net::SocketAddr; #[tokio::main] async fn main() -> Result<(), Box> { let addr: SocketAddr = "127.0.0.1:8080".parse()?; - let server = ProxyServer::new(addr); + let server = RelayServer::new(addr); server.run().await?; Ok(()) } @@ -35,11 +35,11 @@ async fn main() -> Result<(), Box> { ## Architecture -The proxy implements a three-phase protocol: +The relay implements a three-phase protocol: ### 1. Authentication Phase -- Client connects to proxy via WebSocket +- Client connects to relay via WebSocket - Server sends a cryptographic challenge - Client signs the challenge with its cryptographic identity - Server verifies the signature and authenticates the client @@ -54,5 +54,5 @@ The proxy implements a three-phase protocol: ### 3. Messaging Phase - Authenticated clients can send messages to other clients by fingerprint -- Messages are routed through the proxy server -- The proxy validates the source identity but cannot decrypt message contents +- Messages are routed through the relay server +- The relay validates the source identity but cannot decrypt message contents diff --git a/crates/ap-proxy/src/connection.rs b/crates/ap-relay/src/connection.rs similarity index 85% rename from crates/ap-proxy/src/connection.rs rename to crates/ap-relay/src/connection.rs index ccf2837..2cdfafc 100644 --- a/crates/ap-proxy/src/connection.rs +++ b/crates/ap-relay/src/connection.rs @@ -1,16 +1,16 @@ //! Internal connection state tracking. //! -//! This module contains internal types used by the proxy server to track authenticated +//! This module contains internal types used by the relay server to track authenticated //! client connections. These types are not part of the public API. -use ap_proxy_protocol::{Identity, IdentityFingerprint}; +use ap_relay_protocol::{Identity, IdentityFingerprint}; use std::time::SystemTime; use tokio::sync::mpsc; use tokio_tungstenite::tungstenite::Message; /// Internal state for an authenticated client connection. /// -/// The proxy server maintains one of these for each authenticated client to: +/// The relay server maintains one of these for each authenticated client to: /// - Track the client's identity and fingerprint /// - Send messages back to the client via the WebSocket channel /// - Record connection time for debugging and monitoring diff --git a/crates/ap-proxy/src/lib.rs b/crates/ap-relay/src/lib.rs similarity index 81% rename from crates/ap-proxy/src/lib.rs rename to crates/ap-relay/src/lib.rs index 7750567..b1106d7 100644 --- a/crates/ap-proxy/src/lib.rs +++ b/crates/ap-relay/src/lib.rs @@ -1,18 +1,18 @@ -//! WebSocket proxy server for secure peer-to-peer messaging. +//! WebSocket relay server for secure peer-to-peer messaging. //! -//! This crate provides the proxy server that accepts WebSocket connections, +//! This crate provides the relay server that accepts WebSocket connections, //! authenticates clients, and routes messages between them. The server is //! zero-knowledge and cannot decrypt client payloads. //! -//! For the client library, see the `ap-proxy-client` crate. -//! For shared protocol types, see the `ap-proxy-protocol` crate. +//! For the client library, see the `ap-relay-client` crate. +//! For shared protocol types, see the `ap-relay-protocol` crate. //! //! # Architecture //! -//! The proxy routes messages between authenticated clients: +//! The relay routes messages between authenticated clients: //! //! ```text -//! Client A Proxy Server Client B +//! Client A Relay Server Client B //! | | | //! |---(1) WebSocket Connect---->| | //! |<--(2) Auth Challenge--------| | @@ -49,24 +49,24 @@ //! ### Phase 3: Messaging //! //! Once authenticated, clients can send messages to other clients by their identity fingerprint. -//! The proxy validates the source identity and routes messages to the destination. The proxy +//! The relay validates the source identity and routes messages to the destination. The relay //! cannot decrypt message contents — clients should implement end-to-end encryption separately. //! //! # Running as a Binary //! //! ```bash -//! cargo run --bin ap-proxy +//! cargo run --bin ap-relay //! ``` //! //! # Embedding in Your Application //! //! ```no_run -//! use ap_proxy::server::ProxyServer; +//! use ap_relay::server::RelayServer; //! use std::net::SocketAddr; //! //! # async fn example() -> Result<(), Box> { //! let addr: SocketAddr = "127.0.0.1:8080".parse()?; -//! let server = ProxyServer::new(addr); +//! let server = RelayServer::new(addr); //! server.run().await?; //! # Ok(()) //! # } diff --git a/crates/ap-proxy/src/main.rs b/crates/ap-relay/src/main.rs similarity index 73% rename from crates/ap-proxy/src/main.rs rename to crates/ap-relay/src/main.rs index 50696f0..76b2262 100644 --- a/crates/ap-proxy/src/main.rs +++ b/crates/ap-relay/src/main.rs @@ -1,10 +1,10 @@ -use ap_proxy::server::ProxyServer; -use ap_proxy_protocol::ProxyError; +use ap_relay::server::RelayServer; +use ap_relay_protocol::RelayError; use std::env; use tracing_subscriber::{EnvFilter, fmt}; #[tokio::main] -async fn main() -> Result<(), ProxyError> { +async fn main() -> Result<(), RelayError> { fmt() .with_env_filter(EnvFilter::from_default_env().add_directive(tracing::Level::INFO.into())) .init(); @@ -14,9 +14,9 @@ async fn main() -> Result<(), ProxyError> { .parse() .expect("Invalid BIND_ADDR"); - tracing::info!("Starting proxy server on {}", bind_addr); + tracing::info!("Starting relay server on {}", bind_addr); - let server = ProxyServer::new(bind_addr); + let server = RelayServer::new(bind_addr); tokio::select! { result = server.run() => { diff --git a/crates/ap-proxy/src/server/handler.rs b/crates/ap-relay/src/server/handler.rs similarity index 94% rename from crates/ap-proxy/src/server/handler.rs rename to crates/ap-relay/src/server/handler.rs index a0fd48f..64e79a1 100644 --- a/crates/ap-proxy/src/server/handler.rs +++ b/crates/ap-relay/src/server/handler.rs @@ -1,9 +1,9 @@ -use ap_proxy_protocol::{Challenge, IdentityFingerprint, Messages, ProxyError, RendezvousCode}; +use ap_relay_protocol::{Challenge, IdentityFingerprint, Messages, RelayError, RendezvousCode}; -use crate::{connection::AuthenticatedConnection, server::proxy_server::ServerState}; +use crate::{connection::AuthenticatedConnection, server::relay_server::ServerState}; -fn ws_err(e: tokio_tungstenite::tungstenite::Error) -> ProxyError { - ProxyError::WebSocket(e.to_string()) +fn ws_err(e: tokio_tungstenite::tungstenite::Error) -> RelayError { + RelayError::WebSocket(e.to_string()) } use futures_util::{SinkExt, StreamExt}; use std::sync::Arc; @@ -35,7 +35,7 @@ impl ConnectionHandler { } } - pub async fn handle(self) -> Result<(), ProxyError> { + pub async fn handle(self) -> Result<(), RelayError> { let conn_id = self.conn_id; let state = self.state; @@ -54,7 +54,7 @@ impl ConnectionHandler { let challenge = Challenge::new(); let challenge_msg = serde_json::to_string(&Messages::AuthChallenge(challenge.clone()))?; if tx.send(Message::Text(challenge_msg)).is_err() { - return Err(ProxyError::ConnectionClosed); + return Err(RelayError::ConnectionClosed); } tracing::debug!("Connection #{}: Sent auth challenge", conn_id); @@ -64,19 +64,19 @@ impl ConnectionHandler { { Ok(Some(Ok(Message::Text(text)))) => text, Ok(Some(Ok(_))) => { - return Err(ProxyError::InvalidMessage( + return Err(RelayError::InvalidMessage( "Expected text message for auth".to_string(), )); } Ok(Some(Err(e))) => return Err(ws_err(e)), - Ok(None) => return Err(ProxyError::ConnectionClosed), - Err(_) => return Err(ProxyError::AuthenticationTimeout), + Ok(None) => return Err(RelayError::ConnectionClosed), + Err(_) => return Err(RelayError::AuthenticationTimeout), }; let (identity, fingerprint) = match serde_json::from_str::(&auth_response)? { Messages::AuthResponse(identity, response) => { if !response.verify(&challenge, &identity) { - return Err(ProxyError::AuthenticationFailed( + return Err(RelayError::AuthenticationFailed( "Invalid signature".to_string(), )); } @@ -89,7 +89,7 @@ impl ConnectionHandler { (identity, fingerprint) } _ => { - return Err(ProxyError::AuthenticationFailed( + return Err(RelayError::AuthenticationFailed( "Expected AuthResponse".to_string(), )); } @@ -139,7 +139,7 @@ impl ConnectionHandler { ws_read: &mut futures_util::stream::SplitStream>, fingerprint: IdentityFingerprint, conn_id: u64, - ) -> Result<(), ProxyError> { + ) -> Result<(), RelayError> { let inactivity_deadline = tokio::time::sleep(CLIENT_INACTIVITY_TIMEOUT); tokio::pin!(inactivity_deadline); @@ -204,7 +204,7 @@ impl ConnectionHandler { // Store mapping in rendezvous_map { - use crate::server::proxy_server::RendezvousEntry; + use crate::server::relay_server::RendezvousEntry; let entry = RendezvousEntry { fingerprint, created_at: SystemTime::now(), diff --git a/crates/ap-proxy/src/server/mod.rs b/crates/ap-relay/src/server/mod.rs similarity index 74% rename from crates/ap-proxy/src/server/mod.rs rename to crates/ap-relay/src/server/mod.rs index 4fb54b6..298e6dd 100644 --- a/crates/ap-proxy/src/server/mod.rs +++ b/crates/ap-relay/src/server/mod.rs @@ -1,27 +1,27 @@ -//! Proxy server implementation. +//! Relay server implementation. //! -//! This module provides the server-side implementation of the ap-proxy server. +//! This module provides the server-side implementation of the ap-relay server. //! The server can be run standalone using the binary, or embedded in custom applications. //! //! # Running as a Binary //! -//! The simplest way to run the proxy server: +//! The simplest way to run the relay server: //! //! ```bash -//! cargo run --bin ap-proxy +//! cargo run --bin ap-relay //! ``` //! //! # Embedding in Your Application //! -//! You can embed the proxy server in your own application: +//! You can embed the relay server in your own application: //! //! ```no_run -//! use ap_proxy::server::ProxyServer; +//! use ap_relay::server::RelayServer; //! use std::net::SocketAddr; //! //! # async fn example() -> Result<(), Box> { //! let addr: SocketAddr = "127.0.0.1:8080".parse()?; -//! let server = ProxyServer::new(addr); +//! let server = RelayServer::new(addr); //! //! // Run the server (blocks until shutdown) //! server.run().await?; @@ -31,7 +31,7 @@ //! //! # Server Responsibilities //! -//! The proxy server: +//! The relay server: //! - Accepts WebSocket connections from clients //! - Authenticates clients using MlDsa65 challenge-response //! - Manages rendezvous codes for peer discovery @@ -40,13 +40,13 @@ //! //! # Security Considerations //! -//! The server operates as a zero-knowledge proxy: +//! The server operates as a zero-knowledge relay: //! - Verifies client identities via cryptographic signatures //! - Routes messages based on fingerprints //! - Does not decrypt or inspect message payloads //! - Sees metadata: source, destination, timing, message size mod handler; -mod proxy_server; +mod relay_server; -pub use proxy_server::ProxyServer; +pub use relay_server::RelayServer; diff --git a/crates/ap-proxy/src/server/proxy_server.rs b/crates/ap-relay/src/server/relay_server.rs similarity index 85% rename from crates/ap-proxy/src/server/proxy_server.rs rename to crates/ap-relay/src/server/relay_server.rs index 8ea1436..b95c6cf 100644 --- a/crates/ap-proxy/src/server/proxy_server.rs +++ b/crates/ap-relay/src/server/relay_server.rs @@ -1,4 +1,4 @@ -use ap_proxy_protocol::{IdentityFingerprint, ProxyError}; +use ap_relay_protocol::{IdentityFingerprint, RelayError}; use crate::connection::AuthenticatedConnection; use crate::server::handler::ConnectionHandler; @@ -37,7 +37,7 @@ impl ServerState { } } -/// The proxy server that accepts client connections and relays messages. +/// The relay server that accepts client connections and relays messages. /// /// This server handles: /// - Client authentication using MlDsa65 challenge-response @@ -50,14 +50,14 @@ impl ServerState { /// Run a standalone server: /// /// ```no_run -/// use ap_proxy::server::ProxyServer; +/// use ap_relay::server::RelayServer; /// use std::net::SocketAddr; /// /// # async fn example() -> Result<(), Box> { /// let addr: SocketAddr = "127.0.0.1:8080".parse()?; -/// let server = ProxyServer::new(addr); +/// let server = RelayServer::new(addr); /// -/// println!("Starting proxy server on {}", addr); +/// println!("Starting relay server on {}", addr); /// server.run().await?; /// # Ok(()) /// # } @@ -66,13 +66,13 @@ impl ServerState { /// Embed in an application with cancellation: /// /// ```no_run -/// use ap_proxy::server::ProxyServer; +/// use ap_relay::server::RelayServer; /// use std::net::SocketAddr; /// use tokio::signal; /// /// # async fn example() -> Result<(), Box> { /// let addr: SocketAddr = "127.0.0.1:8080".parse()?; -/// let server = ProxyServer::new(addr); +/// let server = RelayServer::new(addr); /// /// tokio::select! { /// result = server.run() => { @@ -85,26 +85,26 @@ impl ServerState { /// # Ok(()) /// # } /// ``` -pub struct ProxyServer { +pub struct RelayServer { bind_addr: SocketAddr, state: Arc, conn_counter: AtomicU64, } -impl ProxyServer { - /// Create a new proxy server that will listen on the given address. +impl RelayServer { + /// Create a new relay server that will listen on the given address. /// - /// This does not start the server - call [`run()`](ProxyServer::run) to begin + /// This does not start the server - call [`run()`](RelayServer::run) to begin /// accepting connections. /// /// # Examples /// /// ``` - /// use ap_proxy::server::ProxyServer; + /// use ap_relay::server::RelayServer; /// use std::net::SocketAddr; /// /// let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); - /// let server = ProxyServer::new(addr); + /// let server = RelayServer::new(addr); /// ``` pub fn new(bind_addr: SocketAddr) -> Self { Self { @@ -114,7 +114,7 @@ impl ProxyServer { } } - /// Run the proxy server, accepting and handling connections. + /// Run the relay server, accepting and handling connections. /// /// This method: /// 1. Binds to the configured address @@ -128,13 +128,13 @@ impl ProxyServer { /// Use `tokio::select!` or similar to cancel the server: /// /// ```no_run - /// use ap_proxy::server::ProxyServer; + /// use ap_relay::server::RelayServer; /// use std::net::SocketAddr; /// use tokio::signal; /// /// # async fn example() -> Result<(), Box> { /// let addr: SocketAddr = "127.0.0.1:8080".parse()?; - /// let server = ProxyServer::new(addr); + /// let server = RelayServer::new(addr); /// /// tokio::select! { /// result = server.run() => result?, @@ -156,27 +156,27 @@ impl ProxyServer { /// # Examples /// /// ```no_run - /// use ap_proxy::server::ProxyServer; + /// use ap_relay::server::RelayServer; /// use std::net::SocketAddr; /// /// # async fn example() -> Result<(), Box> { /// let addr: SocketAddr = "127.0.0.1:8080".parse()?; - /// let server = ProxyServer::new(addr); + /// let server = RelayServer::new(addr); /// server.run().await?; /// # Ok(()) /// # } /// ``` - pub async fn run(&self) -> Result<(), ProxyError> { + pub async fn run(&self) -> Result<(), RelayError> { let listener = TcpListener::bind(self.bind_addr).await?; - tracing::info!("Proxy server listening on {}", self.bind_addr); + tracing::info!("Relay server listening on {}", self.bind_addr); self.run_with_listener(listener).await } - /// Run the proxy server using an already-bound `TcpListener`. + /// Run the relay server using an already-bound `TcpListener`. /// /// This is useful in tests to avoid the race condition of binding a port, /// dropping the listener, and re-binding. - pub async fn run_with_listener(&self, listener: TcpListener) -> Result<(), ProxyError> { + pub async fn run_with_listener(&self, listener: TcpListener) -> Result<(), RelayError> { // Spawn background cleanup task for expired rendezvous codes let cleanup_state = Arc::clone(&self.state); tokio::spawn(async move { diff --git a/crates/ap-proxy/tests/client_integration.rs b/crates/ap-relay/tests/client_integration.rs similarity index 92% rename from crates/ap-proxy/tests/client_integration.rs rename to crates/ap-relay/tests/client_integration.rs index aa4ec26..1c14c54 100644 --- a/crates/ap-proxy/tests/client_integration.rs +++ b/crates/ap-relay/tests/client_integration.rs @@ -1,5 +1,5 @@ -use ap_proxy::server::ProxyServer; -use ap_proxy_client::{IdentityKeyPair, IncomingMessage, ProxyProtocolClient}; +use ap_relay::server::RelayServer; +use ap_relay_client::{IdentityKeyPair, IncomingMessage, RelayProtocolClient}; use std::net::SocketAddr; /// Small delay to allow the server to finish registering a connection after @@ -14,7 +14,7 @@ async fn start_test_server() -> SocketAddr { .expect("should bind to localhost"); let addr = listener.local_addr().expect("should get local address"); - let server = ProxyServer::new(addr); + let server = RelayServer::new(addr); tokio::spawn(async move { server.run_with_listener(listener).await.ok() }); addr @@ -24,7 +24,7 @@ async fn start_test_server() -> SocketAddr { async fn test_client_connect_and_authenticate() { let addr = start_test_server().await; - let mut client = ProxyProtocolClient::from_url(format!("ws://{addr}")); + let mut client = RelayProtocolClient::from_url(format!("ws://{addr}")); let _incoming = client .connect(IdentityKeyPair::generate()) .await @@ -43,8 +43,8 @@ async fn test_client_connect_and_authenticate() { async fn test_two_clients_messaging() { let addr = start_test_server().await; - let mut client_a = ProxyProtocolClient::from_url(format!("ws://{addr}")); - let mut client_b = ProxyProtocolClient::from_url(format!("ws://{addr}")); + let mut client_a = RelayProtocolClient::from_url(format!("ws://{addr}")); + let mut client_b = RelayProtocolClient::from_url(format!("ws://{addr}")); let mut incoming_a = client_a .connect(IdentityKeyPair::generate()) @@ -129,7 +129,7 @@ async fn test_two_clients_messaging() { async fn test_rendezvous_request() { let addr = start_test_server().await; - let mut client = ProxyProtocolClient::from_url(format!("ws://{addr}")); + let mut client = RelayProtocolClient::from_url(format!("ws://{addr}")); let mut incoming = client .connect(IdentityKeyPair::generate()) .await @@ -161,7 +161,7 @@ async fn test_rendezvous_request() { async fn test_disconnect_cleanup() { let addr = start_test_server().await; - let mut client = ProxyProtocolClient::from_url(format!("ws://{addr}")); + let mut client = RelayProtocolClient::from_url(format!("ws://{addr}")); client .connect(IdentityKeyPair::generate()) .await @@ -178,8 +178,8 @@ async fn test_disconnect_cleanup() { async fn test_multiple_messages() { let addr = start_test_server().await; - let mut client_a = ProxyProtocolClient::from_url(format!("ws://{addr}")); - let mut client_b = ProxyProtocolClient::from_url(format!("ws://{addr}")); + let mut client_a = RelayProtocolClient::from_url(format!("ws://{addr}")); + let mut client_b = RelayProtocolClient::from_url(format!("ws://{addr}")); let _incoming_a = client_a .connect(IdentityKeyPair::generate()) @@ -242,8 +242,8 @@ async fn test_multiple_clients_same_identity_can_connect() { let shared_keypair = IdentityKeyPair::generate(); let cose_bytes = shared_keypair.to_cose(); - let mut client_a = ProxyProtocolClient::from_url(format!("ws://{addr}")); - let mut client_b = ProxyProtocolClient::from_url(format!("ws://{addr}")); + let mut client_a = RelayProtocolClient::from_url(format!("ws://{addr}")); + let mut client_b = RelayProtocolClient::from_url(format!("ws://{addr}")); let _incoming_a = client_a .connect(IdentityKeyPair::from_cose(&cose_bytes).unwrap()) @@ -281,9 +281,9 @@ async fn test_messages_broadcast_to_all_same_identity_connections() { let user_keypair = IdentityKeyPair::generate(); let user_cose = user_keypair.to_cose(); - let mut user_client_a = ProxyProtocolClient::from_url(format!("ws://{addr}")); - let mut user_client_b = ProxyProtocolClient::from_url(format!("ws://{addr}")); - let mut sender_client = ProxyProtocolClient::from_url(format!("ws://{addr}")); + let mut user_client_a = RelayProtocolClient::from_url(format!("ws://{addr}")); + let mut user_client_b = RelayProtocolClient::from_url(format!("ws://{addr}")); + let mut sender_client = RelayProtocolClient::from_url(format!("ws://{addr}")); let mut incoming_user_a = user_client_a .connect(IdentityKeyPair::from_cose(&user_cose).unwrap()) @@ -372,9 +372,9 @@ async fn test_cleanup_when_one_connection_disconnects() { let user_keypair = IdentityKeyPair::generate(); let user_cose = user_keypair.to_cose(); - let mut user_client_a = ProxyProtocolClient::from_url(format!("ws://{addr}")); - let mut user_client_b = ProxyProtocolClient::from_url(format!("ws://{addr}")); - let mut sender_client = ProxyProtocolClient::from_url(format!("ws://{addr}")); + let mut user_client_a = RelayProtocolClient::from_url(format!("ws://{addr}")); + let mut user_client_b = RelayProtocolClient::from_url(format!("ws://{addr}")); + let mut sender_client = RelayProtocolClient::from_url(format!("ws://{addr}")); let _incoming_user_a = user_client_a .connect(IdentityKeyPair::from_cose(&user_cose).unwrap()) diff --git a/crates/ap-uniffi/Cargo.toml b/crates/ap-uniffi/Cargo.toml index 31c275f..4f56400 100644 --- a/crates/ap-uniffi/Cargo.toml +++ b/crates/ap-uniffi/Cargo.toml @@ -16,8 +16,8 @@ path = "uniffi-bindgen.rs" [dependencies] ap-client = { path = "../ap-client", features = ["native-websocket"] } -ap-proxy-client = { path = "../ap-proxy-client", default-features = false, features = ["native-websocket"] } -ap-proxy-protocol = { path = "../ap-proxy-protocol" } +ap-relay-client = { path = "../ap-relay-client", default-features = false, features = ["native-websocket"] } +ap-relay-protocol = { path = "../ap-relay-protocol" } ap-noise = { path = "../ap-noise" } async-trait = "0.1" thiserror = "1.0" @@ -28,7 +28,7 @@ uniffi = { version = "0.28", features = ["cli", "tokio"] } zeroize = { version = "1.8", features = ["zeroize_derive"] } [dev-dependencies] -ap-proxy = { path = "../ap-proxy" } +ap-relay = { path = "../ap-relay" } tokio = { version = "1.36.0", features = ["rt-multi-thread", "sync", "macros", "time"] } zeroize = { version = "1.8", features = ["zeroize_derive"] } diff --git a/crates/ap-uniffi/src/adapters.rs b/crates/ap-uniffi/src/adapters.rs index 501dec1..86317bd 100644 --- a/crates/ap-uniffi/src/adapters.rs +++ b/crates/ap-uniffi/src/adapters.rs @@ -7,7 +7,7 @@ use ap_client::{ ConnectionUpdate, IdentityProvider, PskEntry, PskStore, }; use ap_noise::{MultiDeviceTransport, PersistentTransportState, Psk}; -use ap_proxy_protocol::{IdentityFingerprint, IdentityKeyPair}; +use ap_relay_protocol::{IdentityFingerprint, IdentityKeyPair}; use async_trait::async_trait; use crate::callbacks::{ diff --git a/crates/ap-uniffi/src/error.rs b/crates/ap-uniffi/src/error.rs index 4fd5b6d..3a25993 100644 --- a/crates/ap-uniffi/src/error.rs +++ b/crates/ap-uniffi/src/error.rs @@ -24,7 +24,7 @@ impl From for ClientError { ClientError::ConnectionFailed { message } } - ap_client::ClientError::ProxyAuthFailed(_) + ap_client::ClientError::RelayAuthFailed(_) | ap_client::ClientError::HandshakeFailed(_) | ap_client::ClientError::NoiseProtocol(_) | ap_client::ClientError::FingerprintRejected => { @@ -78,7 +78,7 @@ mod tests { #[test] fn handshake_errors_map_correctly() { let cases = vec![ - ap_client::ClientError::ProxyAuthFailed("bad auth".to_string()), + ap_client::ClientError::RelayAuthFailed("bad auth".to_string()), ap_client::ClientError::HandshakeFailed("noise error".to_string()), ap_client::ClientError::NoiseProtocol("decrypt failed".to_string()), ap_client::ClientError::FingerprintRejected, diff --git a/crates/ap-uniffi/src/remote_client.rs b/crates/ap-uniffi/src/remote_client.rs index a4bb52c..9fdc790 100644 --- a/crates/ap-uniffi/src/remote_client.rs +++ b/crates/ap-uniffi/src/remote_client.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use std::time::Duration; use ap_client::{ - DefaultProxyClient, IdentityFingerprint, PskToken, RemoteClientFingerprintReply, + DefaultRelayClient, IdentityFingerprint, PskToken, RemoteClientFingerprintReply, RemoteClientHandle, RemoteClientNotification, RemoteClientRequest, }; use tokio::sync::{Mutex, mpsc}; @@ -30,14 +30,14 @@ pub struct RemoteClient { fingerprint_verifier: Option>, identity_storage: Arc, connection_storage: Arc, - proxy_url: String, + relay_url: String, } #[uniffi::export(async_runtime = "tokio")] impl RemoteClient { /// Create a new RemoteClient. /// - /// * `proxy_url` — WebSocket URL of the proxy server (e.g. "ws://localhost:8080"). + /// * `relay_url` — WebSocket URL of the relay server (e.g. "ws://localhost:8080"). /// * `identity_storage` — Callback for persistent identity keypair storage. /// * `connection_storage` — Callback for persistent connection cache storage. /// * `event_handler` — Optional callback for receiving status notifications. @@ -47,7 +47,7 @@ impl RemoteClient { /// PSK connections and headless/agent scenarios skip verification. #[uniffi::constructor] pub fn new( - proxy_url: String, + relay_url: String, identity_storage: Box, connection_storage: Box, event_handler: Option>, @@ -61,11 +61,11 @@ impl RemoteClient { fingerprint_verifier: fingerprint_verifier.map(Arc::from), identity_storage: Arc::from(identity_storage), connection_storage: Arc::from(connection_storage), - proxy_url, + relay_url, }) } - /// Connect to the proxy server and authenticate. + /// Connect to the relay server and authenticate. /// /// After this, call one of the pairing methods to establish a secure channel: /// `pair_with_handshake()`, `pair_with_psk()`, or `load_existing_connection()`. @@ -77,7 +77,7 @@ impl RemoteClient { let session_store = CallbackConnectionStore::new(Arc::clone(&self.connection_storage)); - let proxy_client = Box::new(DefaultProxyClient::from_url(self.proxy_url.clone())); + let relay_client = Box::new(DefaultRelayClient::from_url(self.relay_url.clone())); let RemoteClientHandle { client, @@ -86,7 +86,7 @@ impl RemoteClient { } = ap_client::RemoteClient::connect( Box::new(identity), Box::new(session_store), - proxy_client, + relay_client, ) .await .map_err(ClientError::from)?; diff --git a/crates/ap-uniffi/src/types.rs b/crates/ap-uniffi/src/types.rs index 1f9e52b..8b9306a 100644 --- a/crates/ap-uniffi/src/types.rs +++ b/crates/ap-uniffi/src/types.rs @@ -102,9 +102,9 @@ impl From for CredentialData { /// FFI-friendly enum. All fingerprints are hex-encoded strings. #[derive(Clone, uniffi::Enum)] pub enum FfiEvent { - /// Connecting to the proxy server. + /// Connecting to the relay server. Connecting, - /// Successfully connected to the proxy. + /// Successfully connected to the relay. Connected { fingerprint: String }, /// Started listening for incoming connections. Listening, @@ -149,11 +149,11 @@ pub enum FfiEvent { RendezvousResolved { fingerprint: String }, /// Using PSK mode for connection. PskMode { fingerprint: String }, - /// Client disconnected from proxy. + /// Client disconnected from relay. Disconnected { reason: Option }, - /// Attempting to reconnect to proxy. + /// Attempting to reconnect to relay. Reconnecting { attempt: u32 }, - /// Successfully reconnected to proxy. + /// Successfully reconnected to relay. Reconnected, /// An error occurred. Error { diff --git a/crates/ap-uniffi/src/user_client.rs b/crates/ap-uniffi/src/user_client.rs index 6d21f8c..b7a364d 100644 --- a/crates/ap-uniffi/src/user_client.rs +++ b/crates/ap-uniffi/src/user_client.rs @@ -11,7 +11,7 @@ use crate::callbacks::{ use crate::error::ClientError; use crate::types::{FfiCredentialQuery, FfiEvent}; use ap_client::{ - CredentialData, CredentialRequestReply, DefaultProxyClient, FingerprintVerificationReply, + CredentialData, CredentialRequestReply, DefaultRelayClient, FingerprintVerificationReply, UserClientHandle, UserClientNotification, UserClientRequest, }; use tokio::sync::{Mutex, mpsc}; @@ -31,14 +31,14 @@ pub struct UserClient { psk_storage: Option>, identity_storage: Arc, connection_storage: Arc, - proxy_url: String, + relay_url: String, } #[uniffi::export(async_runtime = "tokio")] impl UserClient { /// Create a new UserClient. /// - /// * `proxy_url` — WebSocket URL of the proxy server. + /// * `relay_url` — WebSocket URL of the relay server. /// * `identity_storage` — Callback for persistent identity keypair storage. /// * `connection_storage` — Callback for persistent connection cache storage. /// * `handler` — Callback for credential requests. @@ -51,7 +51,7 @@ impl UserClient { #[uniffi::constructor] #[allow(clippy::too_many_arguments)] pub fn new( - proxy_url: String, + relay_url: String, identity_storage: Box, connection_storage: Box, handler: Box, @@ -71,11 +71,11 @@ impl UserClient { psk_storage: psk_storage.map(Arc::from), identity_storage: Arc::from(identity_storage), connection_storage: Arc::from(connection_storage), - proxy_url, + relay_url, }) } - /// Connect to the proxy server and start listening for incoming connections. + /// Connect to the relay server and start listening for incoming connections. /// /// Spawns a background event loop that dispatches credential requests and /// fingerprint verifications to the `CredentialProvider` callback. @@ -87,7 +87,7 @@ impl UserClient { let session_store = CallbackConnectionStore::new(Arc::clone(&self.connection_storage)); - let proxy_client = Box::new(DefaultProxyClient::from_url(self.proxy_url.clone())); + let relay_client = Box::new(DefaultRelayClient::from_url(self.relay_url.clone())); let audit_log: Option> = self.audit_logger.as_ref().map(|logger| { @@ -106,7 +106,7 @@ impl UserClient { } = ap_client::UserClient::connect( Box::new(identity), Box::new(session_store), - proxy_client, + relay_client, audit_log, psk_store, ) diff --git a/crates/ap-uniffi/tests/integration.rs b/crates/ap-uniffi/tests/integration.rs index f6e851c..3f63af3 100644 --- a/crates/ap-uniffi/tests/integration.rs +++ b/crates/ap-uniffi/tests/integration.rs @@ -1,13 +1,13 @@ //! Integration tests for ap-uniffi //! -//! Tests the UniFFI wrapper against a real proxy server, exercising the full -//! protocol stack: proxy connection, PSK pairing, credential exchange. +//! Tests the UniFFI wrapper against a real relay server, exercising the full +//! protocol stack: relay connection, PSK pairing, credential exchange. use std::net::SocketAddr; use std::sync::Mutex; use ap_client::{ - CredentialData, CredentialRequestReply, DefaultProxyClient, FingerprintVerificationReply, + CredentialData, CredentialRequestReply, DefaultRelayClient, FingerprintVerificationReply, MemoryConnectionStore, MemoryIdentityProvider, UserClient, UserClientHandle, UserClientRequest, }; use ap_uniffi::{ @@ -104,7 +104,7 @@ async fn start_test_server() -> SocketAddr { let addr = listener.local_addr().expect("should get local address"); drop(listener); - let server = ap_proxy::server::ProxyServer::new(addr); + let server = ap_relay::server::RelayServer::new(addr); tokio::spawn(async move { server.run().await.ok() }); tokio::time::sleep(Duration::from_millis(100)).await; @@ -139,7 +139,7 @@ fn test_ffi_credential() -> FfiCredentialData { /// Returns the PSK token string. async fn setup_user_client_psk(addr: SocketAddr) -> (String, Vec>) { let user_identity = MemoryIdentityProvider::new(); - let user_proxy = Box::new(DefaultProxyClient::from_url(format!("ws://{addr}"))); + let user_relay = Box::new(DefaultRelayClient::from_url(format!("ws://{addr}"))); let user_session_store = MemoryConnectionStore::new(); let UserClientHandle { @@ -149,7 +149,7 @@ async fn setup_user_client_psk(addr: SocketAddr) -> (String, Vec (String, Vec RemoteClient { +fn make_remote_client(relay_url: String) -> RemoteClient { RemoteClient::new( - proxy_url, + relay_url, Box::new(MemoryIdentityStorage::new()), Box::new(MemoryConnectionStorage::new()), None, @@ -215,7 +215,7 @@ fn make_remote_client(proxy_url: String) -> RemoteClient { // ============================================================================ #[tokio::test] -async fn connect_to_nonexistent_proxy_fails() { +async fn connect_to_nonexistent_relay_fails() { let client = make_remote_client("ws://127.0.0.1:1".to_string()); let result = client.connect().await; assert!(result.is_err()); @@ -227,8 +227,8 @@ async fn test_psk_pairing_and_credential_request() { let addr = start_test_server().await; let (psk_token, tasks) = setup_user_client_psk(addr).await; - let proxy_url = format!("ws://{addr}"); - let client = make_remote_client(proxy_url); + let relay_url = format!("ws://{addr}"); + let client = make_remote_client(relay_url); client.connect().await.expect("connect should succeed"); client @@ -262,7 +262,7 @@ async fn test_rendezvous_pairing_and_credential_request() { let addr = start_test_server().await; let user_identity = MemoryIdentityProvider::new(); - let user_proxy = Box::new(DefaultProxyClient::from_url(format!("ws://{addr}"))); + let user_relay = Box::new(DefaultRelayClient::from_url(format!("ws://{addr}"))); let user_session_store = MemoryConnectionStore::new(); let UserClientHandle { @@ -272,7 +272,7 @@ async fn test_rendezvous_pairing_and_credential_request() { } = UserClient::connect( Box::new(user_identity), Box::new(user_session_store), - user_proxy, + user_relay, None, None, ) @@ -313,8 +313,8 @@ async fn test_rendezvous_pairing_and_credential_request() { } }); - let proxy_url = format!("ws://{addr}"); - let client = make_remote_client(proxy_url); + let relay_url = format!("ws://{addr}"); + let client = make_remote_client(relay_url); client.connect().await.expect("connect should succeed"); let fp = client @@ -378,10 +378,10 @@ async fn test_user_access_client_with_credential_provider() { } let addr = start_test_server().await; - let proxy_url = format!("ws://{addr}"); + let relay_url = format!("ws://{addr}"); let user = UniffiUserClient::new( - proxy_url.clone(), + relay_url.clone(), Box::new(MemoryIdentityStorage::new()), Box::new(MemoryConnectionStorage::new()), Box::new(TestProvider), @@ -397,7 +397,7 @@ async fn test_user_access_client_with_credential_provider() { .await .expect("should get psk token"); - let client = make_remote_client(proxy_url); + let client = make_remote_client(relay_url); client.connect().await.expect("connect should succeed"); client diff --git a/examples/js-wasm/Cargo.lock b/examples/js-wasm/Cargo.lock index b524641..986ebea 100644 --- a/examples/js-wasm/Cargo.lock +++ b/examples/js-wasm/Cargo.lock @@ -14,12 +14,12 @@ dependencies = [ [[package]] name = "ap-client" -version = "0.10.0" +version = "0.12.0" dependencies = [ "ap-error", "ap-noise", - "ap-proxy-client", - "ap-proxy-protocol", + "ap-relay-client", + "ap-relay-protocol", "async-trait", "base64", "futures-util", @@ -33,18 +33,19 @@ dependencies = [ "tracing", "wasm-bindgen-futures", "web-time", + "zeroize", ] [[package]] name = "ap-error" -version = "0.10.0" +version = "0.12.0" dependencies = [ "ap-error-macro", ] [[package]] name = "ap-error-macro" -version = "0.10.0" +version = "0.12.0" dependencies = [ "darling", "proc-macro2", @@ -54,7 +55,7 @@ dependencies = [ [[package]] name = "ap-noise" -version = "0.10.0" +version = "0.12.0" dependencies = [ "ap-error", "base64", @@ -73,17 +74,17 @@ dependencies = [ ] [[package]] -name = "ap-proxy-client" -version = "0.10.0" +name = "ap-relay-client" +version = "0.12.0" dependencies = [ - "ap-proxy-protocol", + "ap-relay-protocol", "serde_json", "tokio", ] [[package]] -name = "ap-proxy-protocol" -version = "0.10.0" +name = "ap-relay-protocol" +version = "0.12.0" dependencies = [ "ap-error", "coset", @@ -165,8 +166,8 @@ version = "0.1.0" dependencies = [ "ap-client", "ap-noise", - "ap-proxy-client", - "ap-proxy-protocol", + "ap-relay-client", + "ap-relay-protocol", "async-trait", "base64", "getrandom 0.2.17", @@ -1299,6 +1300,7 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ + "serde", "zeroize_derive", ] diff --git a/examples/js-wasm/Cargo.toml b/examples/js-wasm/Cargo.toml index af1f90c..d1e01a4 100644 --- a/examples/js-wasm/Cargo.toml +++ b/examples/js-wasm/Cargo.toml @@ -14,8 +14,8 @@ path = "src/lib.rs" [dependencies] ap-client = { path = "../../crates/ap-client", default-features = false } -ap-proxy-client = { path = "../../crates/ap-proxy-client", default-features = false } -ap-proxy-protocol = { path = "../../crates/ap-proxy-protocol", default-features = false } +ap-relay-client = { path = "../../crates/ap-relay-client", default-features = false } +ap-relay-protocol = { path = "../../crates/ap-relay-protocol", default-features = false } ap-noise = { path = "../../crates/ap-noise" } async-trait = "0.1" diff --git a/examples/js-wasm/README.md b/examples/js-wasm/README.md index abc4387..13949e1 100644 --- a/examples/js-wasm/README.md +++ b/examples/js-wasm/README.md @@ -23,7 +23,7 @@ npm run dev Then in a separate terminal, start a listener: ```bash -cargo run --bin aac -- listen --proxy wss://ap.lesspassword.dev +cargo run --bin aac -- listen --relay wss://ap.lesspassword.dev ``` Open the URL shown by Vite (default http://localhost:5173), paste the rendezvous code, click **Connect**, enter a domain, and click **Request Credential**. @@ -57,7 +57,7 @@ client.disconnect(); ## API -### `await createClient(proxyUrl, identityName?)` +### `await createClient(relayUrl, identityName?)` Create a client. Identity and sessions are persisted in `localStorage`. diff --git a/examples/js-wasm/package-lock.json b/examples/js-wasm/package-lock.json new file mode 100644 index 0000000..c4a1b9e --- /dev/null +++ b/examples/js-wasm/package-lock.json @@ -0,0 +1,1179 @@ +{ + "name": "bw-remote-wasm-demo", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "bw-remote-wasm-demo", + "dependencies": { + "alpinejs": "^3.15.8" + }, + "devDependencies": { + "vite": "^6", + "vite-plugin-wasm": "^3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", + "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", + "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", + "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", + "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", + "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", + "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", + "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", + "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", + "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", + "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", + "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", + "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", + "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", + "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", + "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", + "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", + "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", + "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", + "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", + "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", + "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", + "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", + "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", + "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", + "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz", + "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.1.5" + } + }, + "node_modules/@vue/shared": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz", + "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==", + "license": "MIT" + }, + "node_modules/alpinejs": { + "version": "3.15.12", + "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.12.tgz", + "integrity": "sha512-nJvPAQVNPdZZ0NrExJ/kzQco3ijR8LwvCOadQecllESiqT4NyZ/57sN9V2XyvhlBGAbmlKYgeWZvYdKq99ij/Q==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "~3.1.1" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", + "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.3", + "@rollup/rollup-android-arm64": "4.60.3", + "@rollup/rollup-darwin-arm64": "4.60.3", + "@rollup/rollup-darwin-x64": "4.60.3", + "@rollup/rollup-freebsd-arm64": "4.60.3", + "@rollup/rollup-freebsd-x64": "4.60.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", + "@rollup/rollup-linux-arm-musleabihf": "4.60.3", + "@rollup/rollup-linux-arm64-gnu": "4.60.3", + "@rollup/rollup-linux-arm64-musl": "4.60.3", + "@rollup/rollup-linux-loong64-gnu": "4.60.3", + "@rollup/rollup-linux-loong64-musl": "4.60.3", + "@rollup/rollup-linux-ppc64-gnu": "4.60.3", + "@rollup/rollup-linux-ppc64-musl": "4.60.3", + "@rollup/rollup-linux-riscv64-gnu": "4.60.3", + "@rollup/rollup-linux-riscv64-musl": "4.60.3", + "@rollup/rollup-linux-s390x-gnu": "4.60.3", + "@rollup/rollup-linux-x64-gnu": "4.60.3", + "@rollup/rollup-linux-x64-musl": "4.60.3", + "@rollup/rollup-openbsd-x64": "4.60.3", + "@rollup/rollup-openharmony-arm64": "4.60.3", + "@rollup/rollup-win32-arm64-msvc": "4.60.3", + "@rollup/rollup-win32-ia32-msvc": "4.60.3", + "@rollup/rollup-win32-x64-gnu": "4.60.3", + "@rollup/rollup-win32-x64-msvc": "4.60.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-wasm": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.6.0.tgz", + "integrity": "sha512-mL/QPziiIA4RAA6DkaZZzOstdwbW5jO4Vz7Zenj0wieKWBlNvIvX5L5ljum9lcUX0ShNfBgCNLKTjNkRVVqcsw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + } + } +} diff --git a/examples/js-wasm/src/client.rs b/examples/js-wasm/src/client.rs index 77c9e75..d6ae10b 100644 --- a/examples/js-wasm/src/client.rs +++ b/examples/js-wasm/src/client.rs @@ -7,7 +7,7 @@ use ap_client::{IdentityFingerprint, PskToken, RemoteClient}; use wasm_bindgen::prelude::*; -use crate::proxy_client::WasmProxyClient; +use crate::relay_client::WasmRelayClient; use crate::storage::{LocalStorageConnectionStore, LocalStorageIdentityProvider}; use crate::types::{client_error_to_js, JsCredentialData}; @@ -25,7 +25,7 @@ use crate::types::{client_error_to_js, JsCredentialData}; #[wasm_bindgen] pub struct WasmRemoteClient { inner: Option, - proxy_url: String, + relay_url: String, identity_name: String, } @@ -33,18 +33,18 @@ pub struct WasmRemoteClient { impl WasmRemoteClient { /// Create a new client. /// - /// @param proxy_url - WebSocket URL of the proxy server + /// @param relay_url - WebSocket URL of the relay server /// @param identity_name - Name for the identity keypair stored in localStorage #[wasm_bindgen(constructor)] - pub fn new(proxy_url: &str, identity_name: &str) -> Self { + pub fn new(relay_url: &str, identity_name: &str) -> Self { Self { inner: None, - proxy_url: proxy_url.to_string(), + relay_url: relay_url.to_string(), identity_name: identity_name.to_string(), } } - /// Connect to the proxy server. + /// Connect to the relay server. /// /// Establishes the WebSocket connection and authenticates. After this, /// call one of the pairing methods to establish a secure channel. @@ -55,12 +55,12 @@ impl WasmRemoteClient { let connection_store = LocalStorageConnectionStore::load_or_create(&self.identity_name) .map_err(client_error_to_js)?; - let proxy_client = Box::new(WasmProxyClient::new(self.proxy_url.clone())); + let relay_client = Box::new(WasmRelayClient::new(self.relay_url.clone())); let handle = RemoteClient::connect( Box::new(identity), Box::new(connection_store), - proxy_client, + relay_client, ) .await .map_err(client_error_to_js)?; diff --git a/examples/js-wasm/src/lib.rs b/examples/js-wasm/src/lib.rs index 9d1e92a..5d45513 100644 --- a/examples/js-wasm/src/lib.rs +++ b/examples/js-wasm/src/lib.rs @@ -1,4 +1,4 @@ mod client; -mod proxy_client; +mod relay_client; mod storage; mod types; diff --git a/examples/js-wasm/src/proxy_client.rs b/examples/js-wasm/src/relay_client.rs similarity index 95% rename from examples/js-wasm/src/proxy_client.rs rename to examples/js-wasm/src/relay_client.rs index 860c635..30041ba 100644 --- a/examples/js-wasm/src/proxy_client.rs +++ b/examples/js-wasm/src/relay_client.rs @@ -1,13 +1,13 @@ -//! Browser WebSocket implementation of the `ProxyClient` trait. +//! Browser WebSocket implementation of the `RelayClient` trait. //! -//! Uses `web_sys::WebSocket` to communicate with the proxy server, +//! Uses `web_sys::WebSocket` to communicate with the relay server, //! bridging the browser's callback-driven API to the channel-based -//! `ProxyClient` trait. +//! `RelayClient` trait. use ap_client::error::ClientError; -use ap_client::ProxyClient; -use ap_proxy_client::IncomingMessage; -use ap_proxy_protocol::{IdentityFingerprint, IdentityKeyPair, Messages, RendezvousCode}; +use ap_client::RelayClient; +use ap_relay_client::IncomingMessage; +use ap_relay_protocol::{IdentityFingerprint, IdentityKeyPair, Messages, RendezvousCode}; use async_trait::async_trait; use std::sync::{Arc, Mutex}; use tokio::sync::{mpsc, oneshot}; @@ -15,16 +15,16 @@ use wasm_bindgen::closure::Closure; use wasm_bindgen::prelude::*; use web_sys::{CloseEvent, ErrorEvent, MessageEvent, WebSocket}; -/// A `ProxyClient` implementation using the browser's WebSocket API. +/// A `RelayClient` implementation using the browser's WebSocket API. /// /// Closures are registered with `.forget()` (standard WASM practice for event /// handlers). The struct only holds `Send + Sync` types. -pub struct WasmProxyClient { +pub struct WasmRelayClient { url: String, ws: Option, } -impl WasmProxyClient { +impl WasmRelayClient { pub fn new(url: String) -> Self { Self { url, ws: None } } @@ -43,7 +43,7 @@ impl WasmProxyClient { } #[async_trait] -impl ProxyClient for WasmProxyClient { +impl RelayClient for WasmRelayClient { async fn connect( &mut self, identity: IdentityKeyPair, @@ -117,7 +117,7 @@ impl ProxyClient for WasmProxyClient { let parsed: Messages = match serde_json::from_str(&text) { Ok(m) => m, Err(e) => { - tracing::warn!("Failed to parse proxy message: {e}"); + tracing::warn!("Failed to parse relay message: {e}"); return; } }; diff --git a/examples/js-wasm/src/storage.rs b/examples/js-wasm/src/storage.rs index 11fb745..944de15 100644 --- a/examples/js-wasm/src/storage.rs +++ b/examples/js-wasm/src/storage.rs @@ -7,7 +7,7 @@ use ap_client::error::ClientError; use ap_client::{ConnectionInfo, ConnectionStore, ConnectionUpdate, IdentityProvider}; use ap_noise::{MultiDeviceTransport, PersistentTransportState}; -use ap_proxy_protocol::{IdentityFingerprint, IdentityKeyPair}; +use ap_relay_protocol::{IdentityFingerprint, IdentityKeyPair}; use async_trait::async_trait; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; diff --git a/examples/js-wasm/www/agent-access.js b/examples/js-wasm/www/agent-access.js index dbdae17..4ec484a 100644 --- a/examples/js-wasm/www/agent-access.js +++ b/examples/js-wasm/www/agent-access.js @@ -50,7 +50,7 @@ function looksLikePskToken(s) { function cleanError(e) { if (e instanceof Error) return e.message; const s = String(e); - // Strip "Failed to connect to proxy: Failed to create WebSocket: JsValue(...)" noise + // Strip "Failed to connect to relay: Failed to create WebSocket: JsValue(...)" noise const jsVal = s.match(/JsValue\((?:\w+:\s*)?(.+?)(?:\s+\S+@http).*/s); if (jsVal) return jsVal[1].trim(); // Strip nested "Foo: Bar: Baz" to just the innermost message @@ -61,25 +61,25 @@ function cleanError(e) { /** * Create an Agent Access Remote Client. * - * @param {string} proxyUrl - WebSocket URL of the proxy server + * @param {string} relayUrl - WebSocket URL of the relay server * @param {string} [identityName="js-wasm-remote"] - Name for the localStorage identity * @returns {Promise} */ -export async function createClient(proxyUrl, identityName = "js-wasm-remote") { +export async function createClient(relayUrl, identityName = "js-wasm-remote") { await ensureWasm(); - return new AgentAccessClient(proxyUrl, identityName); + return new AgentAccessClient(relayUrl, identityName); } class AgentAccessClient { #inner = null; - #proxyUrl; + #relayUrl; #identityName; - constructor(proxyUrl, identityName) { - if (!proxyUrl || !/^wss?:\/\/.+/.test(proxyUrl)) { - throw new Error(`Invalid proxy URL: "${proxyUrl}" — expected ws:// or wss://`); + constructor(relayUrl, identityName) { + if (!relayUrl || !/^wss?:\/\/.+/.test(relayUrl)) { + throw new Error(`Invalid relay URL: "${relayUrl}" — expected ws:// or wss://`); } - this.#proxyUrl = proxyUrl; + this.#relayUrl = relayUrl; this.#identityName = identityName; } @@ -108,7 +108,7 @@ class AgentAccessClient { async #withFreshConnection(action) { this.disconnect(); - this.#inner = new WasmRemoteClient(this.#proxyUrl, this.#identityName); + this.#inner = new WasmRemoteClient(this.#relayUrl, this.#identityName); try { await this.#inner.connect(); return await action(this.#inner); diff --git a/examples/js-wasm/www/app.js b/examples/js-wasm/www/app.js index 313105f..13b4c1b 100644 --- a/examples/js-wasm/www/app.js +++ b/examples/js-wasm/www/app.js @@ -7,8 +7,8 @@ let client = null; window.app = function () { return { - proxyUrl: "wss://ap.lesspassword.dev", - editingProxy: false, + relayUrl: "wss://ap.lesspassword.dev", + editingRelay: false, connections: [], loading: true, connected: false, @@ -49,7 +49,7 @@ window.app = function () { async refreshConnections() { this.loading = true; try { - const tmp = await createClient(this.proxyUrl); + const tmp = await createClient(this.relayUrl); this.connections = await tmp.listConnections(); this.addLog( this.connections.length @@ -82,7 +82,7 @@ window.app = function () { this.pairing = true; this.addLog(`Pairing with ${this.token.substring(0, 11)}...`, "pending"); try { - client = await createClient(this.proxyUrl); + client = await createClient(this.relayUrl); const fp = await client.pair(this.token.trim()); this.addLog(fp ? `Paired. Fingerprint: ${fp.substring(0, 12)}...` : "Paired via PSK", "success"); this.connected = true; @@ -102,7 +102,7 @@ window.app = function () { async reconnect(fingerprint) { this.addLog(`Reconnecting to ${fingerprint.substring(0, 12)}...`, "pending"); try { - client = await createClient(this.proxyUrl); + client = await createClient(this.relayUrl); await client.reconnect(fingerprint); this.addLog("Reconnected", "success"); this.connected = true; @@ -140,14 +140,14 @@ window.app = function () { }, async clearConnections() { - const tmp = await createClient(this.proxyUrl); + const tmp = await createClient(this.relayUrl); tmp.clearConnections(); this.addLog("Cleared all cached connections"); await this.refreshConnections(); }, - saveProxy() { - this.editingProxy = false; + saveRelay() { + this.editingRelay = false; }, }; }; diff --git a/examples/js-wasm/www/index.html b/examples/js-wasm/www/index.html index f778ed4..f566b40 100644 --- a/examples/js-wasm/www/index.html +++ b/examples/js-wasm/www/index.html @@ -12,22 +12,22 @@

Bitwarden Agent Access WASM

- +
- Proxy -