db/websocket/architecture.md
2025-06-03 21:51:21 +03:00

327 lines
9.9 KiB
Markdown

# WebSocket Signing Server Architecture Plan
Based on my analysis of the existing Actix application structure, I've designed a comprehensive architecture for implementing a WebSocket server that handles signing operations. This server will integrate seamlessly with the existing hostbasket application.
## 1. Overview
The WebSocket Signing Server will:
- Accept WebSocket connections from clients
- Allow clients to identify themselves with a public key
- Provide a `send_to_sign()` function that takes a public key and a message
- Forward the message to the appropriate client for signing
- Wait for a signed response (with a 1-minute timeout)
- Verify the signature using the client's public key
- Return the response message and signature
## 2. Component Architecture
```mermaid
graph TD
A[Actix Web Server] --> B[WebSocket Manager]
B --> C[Connection Registry]
B --> D[Message Handler]
D --> E[Signature Verifier]
F[Client] <--> B
G[Controllers] --> H[SigningService]
H --> B
```
### Key Components:
1. **WebSocket Manager**
- Handles WebSocket connections
- Manages connection lifecycle
- Routes messages to appropriate handlers
2. **Connection Registry**
- Maps public keys to active WebSocket connections
- Handles connection tracking and cleanup
- Provides lookup functionality
3. **Message Handler**
- Processes incoming messages
- Implements the message protocol
- Manages timeouts for responses
4. **Signature Verifier**
- Verifies signatures using public keys
- Implements cryptographic operations
- Ensures security of the signing process
5. **Signing Service**
- Provides a clean API for controllers to use
- Abstracts WebSocket complexity from business logic
- Handles error cases and timeouts
## 3. Directory Structure
```
src/
├── websocket/
│ ├── mod.rs # Module exports
│ ├── manager.rs # WebSocket connection manager
│ ├── registry.rs # Connection registry
│ ├── handler.rs # Message handling logic
│ ├── protocol.rs # Message protocol definitions
│ ├── crypto.rs # Cryptographic operations
│ └── service.rs # Service API for controllers
├── controllers/
│ └── [existing controllers]
│ └── websocket.rs # WebSocket controller (if needed)
└── routes/
└── mod.rs # Updated to include WebSocket routes
```
## 4. Data Flow
```mermaid
sequenceDiagram
participant Client
participant WebSocketManager
participant Registry
participant Controller
participant SigningService
Client->>WebSocketManager: Connect
Client->>WebSocketManager: Introduce(public_key)
WebSocketManager->>Registry: Register(connection, public_key)
Controller->>SigningService: send_to_sign(public_key, message)
SigningService->>Registry: Lookup(public_key)
Registry-->>SigningService: connection
SigningService->>WebSocketManager: Send message to connection
WebSocketManager->>Client: Message to sign
Client->>WebSocketManager: Signed response
WebSocketManager->>SigningService: Forward response
SigningService->>SigningService: Verify signature
SigningService-->>Controller: Return verified response
```
## 5. Message Protocol
We'll define a simple JSON-based protocol for communication:
```json
// Client introduction
{
"type": "introduction",
"public_key": "base64_encoded_public_key"
}
// Sign request
{
"type": "sign_request",
"message": "base64_encoded_message",
"request_id": "unique_request_id"
}
// Sign response
{
"type": "sign_response",
"request_id": "unique_request_id",
"message": "base64_encoded_message",
"signature": "base64_encoded_signature"
}
```
## 6. Required Dependencies
We'll need to add the following dependencies to the project:
```toml
# WebSocket support
actix-web-actors = "4.2.0"
# Cryptography
ed25519-dalek = "2.0.0" # For Ed25519 signatures
base64 = "0.21.0" # For encoding/decoding
rand = "0.8.5" # For generating random data
```
## 7. Implementation Details
### 7.1 WebSocket Manager
The WebSocket Manager will handle the lifecycle of WebSocket connections:
```rust
pub struct WebSocketManager {
registry: Arc<RwLock<ConnectionRegistry>>,
}
impl Actor for WebSocketManager {
type Context = ws::WebsocketContext<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
// Handle connection start
}
fn stopped(&mut self, ctx: &mut Self::Context) {
// Handle connection close and cleanup
}
}
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WebSocketManager {
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
// Handle different types of WebSocket messages
}
}
```
### 7.2 Connection Registry
The Connection Registry will maintain a mapping of public keys to active connections:
```rust
pub struct ConnectionRegistry {
connections: HashMap<String, Addr<WebSocketManager>>,
}
impl ConnectionRegistry {
pub fn register(&mut self, public_key: String, addr: Addr<WebSocketManager>) {
self.connections.insert(public_key, addr);
}
pub fn unregister(&mut self, public_key: &str) {
self.connections.remove(public_key);
}
pub fn get(&self, public_key: &str) -> Option<&Addr<WebSocketManager>> {
self.connections.get(public_key)
}
}
```
### 7.3 Signing Service
The Signing Service will provide a clean API for controllers:
```rust
pub struct SigningService {
registry: Arc<RwLock<ConnectionRegistry>>,
}
impl SigningService {
pub async fn send_to_sign(&self, public_key: &str, message: &[u8])
-> Result<(Vec<u8>, Vec<u8>), SigningError> {
// 1. Find the connection for the public key
let connection = self.registry.read().await.get(public_key).cloned();
if let Some(conn) = connection {
// 2. Generate a unique request ID
let request_id = Uuid::new_v4().to_string();
// 3. Create a response channel
let (tx, rx) = oneshot::channel();
// 4. Register the response channel
self.pending_requests.write().await.insert(request_id.clone(), tx);
// 5. Send the message to the client
let sign_request = SignRequest {
request_id: request_id.clone(),
message: message.to_vec(),
};
conn.do_send(sign_request);
// 6. Wait for the response with a timeout
match tokio::time::timeout(Duration::from_secs(60), rx).await {
Ok(Ok(response)) => {
// 7. Verify the signature
if self.verify_signature(&response.signature, message, public_key) {
Ok((response.message, response.signature))
} else {
Err(SigningError::InvalidSignature)
}
},
Ok(Err(_)) => Err(SigningError::ChannelClosed),
Err(_) => Err(SigningError::Timeout),
}
} else {
Err(SigningError::ConnectionNotFound)
}
}
}
```
## 8. Error Handling
We'll define a comprehensive error type for the signing service:
```rust
#[derive(Debug, thiserror::Error)]
pub enum SigningError {
#[error("Connection not found for the provided public key")]
ConnectionNotFound,
#[error("Timeout waiting for signature")]
Timeout,
#[error("Invalid signature")]
InvalidSignature,
#[error("Channel closed unexpectedly")]
ChannelClosed,
#[error("WebSocket error: {0}")]
WebSocketError(#[from] ws::ProtocolError),
#[error("Serialization error: {0}")]
SerializationError(#[from] serde_json::Error),
}
```
## 9. Security Considerations
1. **Public Key Validation**: Validate public keys upon connection to ensure they are properly formatted
2. **Message Authentication**: Consider adding a nonce or timestamp to prevent replay attacks
3. **Rate Limiting**: Implement rate limiting to prevent DoS attacks
4. **Connection Timeouts**: Automatically close inactive connections
5. **Error Logging**: Log errors but avoid exposing sensitive information
6. **Input Validation**: Validate all inputs to prevent injection attacks
## 10. Testing Strategy
1. **Unit Tests**: Test individual components in isolation
2. **Integration Tests**: Test the interaction between components
3. **End-to-End Tests**: Test the complete flow from client connection to signature verification
4. **Load Tests**: Test the system under high load to ensure stability
5. **Security Tests**: Test for common security vulnerabilities
## 11. Integration with Existing Controllers
Controllers can use the SigningService through dependency injection:
```rust
pub struct SomeController {
signing_service: Arc<SigningService>,
}
impl SomeController {
pub async fn some_action(&self, public_key: String, message: Vec<u8>) -> HttpResponse {
match self.signing_service.send_to_sign(&public_key, &message).await {
Ok((response, signature)) => {
HttpResponse::Ok().json(json!({
"response": base64::encode(response),
"signature": base64::encode(signature),
}))
},
Err(e) => {
HttpResponse::InternalServerError().json(json!({
"error": e.to_string(),
}))
}
}
}
}
```
## 12. Deployment Considerations
1. **Scalability**: The WebSocket server should be designed to scale horizontally
2. **Monitoring**: Implement metrics for connection count, message throughput, and error rates
3. **Logging**: Log important events for debugging and auditing
4. **Documentation**: Document the API and protocol for client implementers