Implemented connection handshake protocol for federation building on the Instance Identity Model from issue #84. **Services:** - SignatureService: Message signing/verification with RSA-SHA256 - ConnectionService: Federation connection management **API Endpoints:** - POST /api/v1/federation/connections/initiate - POST /api/v1/federation/connections/:id/accept - POST /api/v1/federation/connections/:id/reject - POST /api/v1/federation/connections/:id/disconnect - GET /api/v1/federation/connections - GET /api/v1/federation/connections/:id - POST /api/v1/federation/incoming/connect **Tests:** 70 tests pass (18 Signature + 20 Connection + 13 Controller + 19 existing) **Coverage:** 100% on new code **TDD Approach:** Tests written before implementation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
8.3 KiB
Issue #85: [FED-002] CONNECT/DISCONNECT Protocol
Objective
Implement the connection handshake protocol for federation, building on the Instance Identity Model from issue #84. This includes:
- Connection request/accept/reject handshake
- Message signing and verification using instance keypairs
- Connection state management (PENDING → ACTIVE, DISCONNECTED)
- API endpoints for initiating and managing connections
- Proper error handling and validation
Context
Issue #84 provides the foundation:
Instancemodel with keypair for signingFederationConnectionmodel with status enum (PENDING, ACTIVE, SUSPENDED, DISCONNECTED)FederationServicewith identity managementCryptoServicefor encryption/decryption- Database schema is already in place
Approach
1. Create Types for Connection Protocol
Define TypeScript interfaces in /apps/api/src/federation/types/connection.types.ts:
// Connection request payload
interface ConnectionRequest {
instanceId: string;
instanceUrl: string;
publicKey: string;
capabilities: FederationCapabilities;
timestamp: number;
signature: string; // Sign entire payload with private key
}
// Connection response
interface ConnectionResponse {
accepted: boolean;
instanceId: string;
publicKey: string;
capabilities: FederationCapabilities;
reason?: string; // If rejected
timestamp: number;
signature: string;
}
// Disconnect request
interface DisconnectRequest {
instanceId: string;
reason?: string;
timestamp: number;
signature: string;
}
2. Add Signature Service
Create /apps/api/src/federation/signature.service.ts for message signing:
sign(message: object, privateKey: string): string- Sign a messageverify(message: object, signature: string, publicKey: string): boolean- Verify signaturesignConnectionRequest(...)- Sign connection requestverifyConnectionRequest(...)- Verify connection request
3. Create Connection Service
Create /apps/api/src/federation/connection.service.ts:
initiateConnection(workspaceId, remoteUrl)- Start connection handshakeacceptConnection(workspaceId, connectionId)- Accept pending connectionrejectConnection(workspaceId, connectionId, reason)- Reject connectiondisconnect(workspaceId, connectionId, reason)- Disconnect active connectiongetConnections(workspaceId, status?)- List connectionsgetConnection(workspaceId, connectionId)- Get single connection
4. Add API Endpoints
Extend FederationController with:
POST /api/v1/federation/connections/initiate- Initiate connection to remote instancePOST /api/v1/federation/connections/:id/accept- Accept incoming connectionPOST /api/v1/federation/connections/:id/reject- Reject incoming connectionPOST /api/v1/federation/connections/:id/disconnect- Disconnect active connectionGET /api/v1/federation/connections- List workspace connectionsGET /api/v1/federation/connections/:id- Get connection detailsPOST /api/v1/federation/incoming/connect- Public endpoint for receiving connection requests
5. Connection Handshake Flow
Initiator (Instance A) → Target (Instance B)
- Instance A calls
POST /api/v1/federation/connections/initiatewithremoteUrl - Service creates connection record with status=PENDING
- Service fetches remote instance identity from
GET {remoteUrl}/api/v1/federation/instance - Service creates signed ConnectionRequest
- Service sends request to
POST {remoteUrl}/api/v1/federation/incoming/connect - Instance B receives request, validates signature
- Instance B creates connection record with status=PENDING
- Instance B can accept (status=ACTIVE) or reject (status=DISCONNECTED)
- Instance B sends signed ConnectionResponse back to Instance A
- Instance A updates connection status based on response
6. Security Considerations
- All connection requests must be signed with instance private key
- All responses must be verified using remote instance public key
- Timestamps must be within 5 minutes to prevent replay attacks
- Connection requests must come from authenticated workspace members
- Public key must match the one fetched from remote instance identity endpoint
7. Testing Strategy
Unit Tests (TDD approach):
- SignatureService.sign() creates valid signatures
- SignatureService.verify() validates signatures correctly
- SignatureService.verify() rejects invalid signatures
- ConnectionService.initiateConnection() creates PENDING connection
- ConnectionService.acceptConnection() updates to ACTIVE
- ConnectionService.rejectConnection() marks as DISCONNECTED
- ConnectionService.disconnect() updates active connection to DISCONNECTED
- Timestamp validation rejects old requests (>5 min)
Integration Tests:
- POST /connections/initiate creates connection and calls remote
- POST /incoming/connect validates signature and creates connection
- POST /connections/:id/accept updates status correctly
- POST /connections/:id/reject marks connection as rejected
- POST /connections/:id/disconnect disconnects active connection
- GET /connections returns workspace connections
- Workspace isolation (can't access other workspace connections)
Progress
- Create scratchpad
- Create connection.types.ts with protocol types
- Write tests for SignatureService
- Implement SignatureService (sign, verify)
- Write tests for ConnectionService
- Implement ConnectionService
- Write tests for connection API endpoints
- Implement connection API endpoints
- Update FederationModule with new providers
- Verify all tests pass
- Verify type checking passes
- Verify test coverage ≥85%
- Commit changes
Testing Plan
Unit Tests
-
SignatureService:
- Should create RSA SHA-256 signatures
- Should verify valid signatures
- Should reject invalid signatures
- Should reject tampered messages
- Should reject expired timestamps
-
ConnectionService:
- Should initiate connection with PENDING status
- Should fetch remote instance identity before connecting
- Should create signed connection request
- Should accept connection and update to ACTIVE
- Should reject connection with reason
- Should disconnect active connection
- Should list connections for workspace
- Should enforce workspace isolation
Integration Tests
-
POST /api/v1/federation/connections/initiate:
- Should require authentication
- Should create connection record
- Should fetch remote instance identity
- Should return connection details
-
POST /api/v1/federation/incoming/connect:
- Should validate connection request signature
- Should reject requests with invalid signatures
- Should reject requests with old timestamps
- Should create pending connection
-
POST /api/v1/federation/connections/:id/accept:
- Should require authentication
- Should update connection to ACTIVE
- Should set connectedAt timestamp
- Should enforce workspace ownership
-
POST /api/v1/federation/connections/:id/reject:
- Should require authentication
- Should update connection to DISCONNECTED
- Should store rejection reason
-
POST /api/v1/federation/connections/:id/disconnect:
- Should require authentication
- Should disconnect active connection
- Should set disconnectedAt timestamp
-
GET /api/v1/federation/connections:
- Should list workspace connections
- Should filter by status if provided
- Should enforce workspace isolation
Design Decisions
- RSA Signatures: Use RSA SHA-256 for signing (matches existing keypair format)
- Timestamp Validation: 5-minute window to prevent replay attacks
- Workspace Scoping: All connections belong to a workspace for RLS
- Stateless Protocol: Each request is independently signed and verified
- Public Connection Endpoint:
/incoming/connectis public (no auth) but requires valid signature - State Transitions: PENDING → ACTIVE, PENDING → DISCONNECTED, ACTIVE → DISCONNECTED
Notes
- Connection requests are workspace-scoped (authenticated users only)
- Incoming connection endpoint is public but cryptographically verified
- Need to handle network errors gracefully when calling remote instances
- Should validate remote instance URL format before attempting connection
- Consider rate limiting for incoming connection requests (future enhancement)