diff --git a/lib/types/tunnelbroker/messages.js b/lib/types/tunnelbroker/messages.js index 1bcfcc340..f76208d40 100644 --- a/lib/types/tunnelbroker/messages.js +++ b/lib/types/tunnelbroker/messages.js @@ -1,71 +1,72 @@ // @flow import type { TUnion } from 'tcomb'; import t from 'tcomb'; import { type ConnectionInitializationResponse, connectionInitializationResponseValidator, } from './connection-initialization-response-types.js'; import { type Heartbeat, heartbeatValidator } from './heartbeat-types.js'; import { type MessageReceiveConfirmation, messageReceiveConfirmationValidator, } from './message-receive-confirmation-types.js'; import { type MessageToDeviceRequestStatus, messageToDeviceRequestStatusValidator, } from './message-to-device-request-status-types.js'; import { type MessageToDeviceRequest, messageToDeviceRequestValidator, } from './message-to-device-request-types.js'; import { type MessageToDevice, messageToDeviceValidator, } from './message-to-device-types.js'; import { type ConnectionInitializationMessage, connectionInitializationMessageValidator, } from './session-types.js'; /* * This file defines types and validation for messages exchanged * with the Tunnelbroker. The definitions in this file should remain in sync * with the structures defined in the corresponding * Rust file at `shared/tunnelbroker_messages/src/messages/mod.rs`. * * If you edit the definitions in one file, * please make sure to update the corresponding definitions in the other. * */ export const tunnelbrokerMessageTypes = Object.freeze({ CONNECTION_INITIALIZATION_MESSAGE: 'ConnectionInitializationMessage', CONNECTION_INITIALIZATION_RESPONSE: 'ConnectionInitializationResponse', + ANONYMOUS_INITIALIZATION_MESSAGE: 'AnonymousInitializationMessage', MESSAGE_TO_DEVICE_REQUEST_STATUS: 'MessageToDeviceRequestStatus', MESSAGE_TO_DEVICE_REQUEST: 'MessageToDeviceRequest', MESSAGE_TO_DEVICE: 'MessageToDevice', MESSAGE_RECEIVE_CONFIRMATION: 'MessageReceiveConfirmation', HEARTBEAT: 'Heartbeat', }); export const tunnelbrokerMessageValidator: TUnion = t.union([ connectionInitializationMessageValidator, connectionInitializationResponseValidator, messageToDeviceRequestStatusValidator, messageToDeviceRequestValidator, messageToDeviceValidator, messageReceiveConfirmationValidator, heartbeatValidator, ]); export type TunnelbrokerMessage = | ConnectionInitializationMessage | ConnectionInitializationResponse | MessageToDeviceRequestStatus | MessageToDeviceRequest | MessageToDevice | MessageReceiveConfirmation | Heartbeat; diff --git a/shared/tunnelbroker_messages/src/messages/message_to_device_request_status.rs b/shared/tunnelbroker_messages/src/messages/message_to_device_request_status.rs index bd77e3f40..564bd0c6f 100644 --- a/shared/tunnelbroker_messages/src/messages/message_to_device_request_status.rs +++ b/shared/tunnelbroker_messages/src/messages/message_to_device_request_status.rs @@ -1,86 +1,88 @@ //! Message sent from Tunnelbroker to WebSocket clients to inform that message //! was processed, saved in DDB, and will be delivered. use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, PartialEq, Debug)] pub struct Failure { pub id: String, pub error: String, } #[derive(Serialize, Deserialize, PartialEq, Debug)] #[serde(tag = "type", content = "data")] pub enum MessageSentStatus { /// The message with the provided ID (String) has been processed /// by the Tunnelbroker and is queued for delivery. Success(String), /// 'Failure' contains information about the message ID /// along with the specific error message. Error(Failure), /// The request was invalid (e.g., Bytes instead of Text). /// In this case, the ID cannot be retrieved. InvalidRequest, + /// Unauthenticated client tried to send a message. + Unauthenticated, /// The JSON could not be serialized, which is why the entire message is /// returned back. /// It becomes impossible to retrieve the message ID in such circumstances. SerializationError(String), } #[derive(Serialize, Deserialize, PartialEq, Debug)] #[serde(tag = "type", rename_all = "camelCase")] pub struct MessageToDeviceRequestStatus { #[serde(rename = "clientMessageIDs")] pub client_message_ids: Vec, } #[cfg(test)] mod send_confirmation_tests { use super::*; #[test] fn test_send_confirmation_deserialization() { let example_payload = r#"{ "type": "MessageToDeviceRequestStatus", "clientMessageIDs": [ {"type": "Success", "data": "id123"}, {"type": "Success", "data": "id456"}, {"type": "Error", "data": {"id": "id789", "error": "Something went wrong"}}, {"type": "SerializationError", "data": "message"}, {"type": "InvalidRequest"} ] }"#; let request = serde_json::from_str::(example_payload) .unwrap(); let expected_client_message_ids = vec![ MessageSentStatus::Success("id123".to_string()), MessageSentStatus::Success("id456".to_string()), MessageSentStatus::Error(Failure { id: String::from("id789"), error: String::from("Something went wrong"), }), MessageSentStatus::SerializationError("message".to_string()), MessageSentStatus::InvalidRequest, ]; assert_eq!(request.client_message_ids, expected_client_message_ids); } #[test] fn test_send_confirmation_deserialization_empty_vec() { let example_payload = r#"{ "type": "MessageToDeviceRequestStatus", "clientMessageIDs": [] }"#; let request = serde_json::from_str::(example_payload) .unwrap(); let expected_client_message_ids: Vec = Vec::new(); assert_eq!(request.client_message_ids, expected_client_message_ids); } } diff --git a/shared/tunnelbroker_messages/src/messages/mod.rs b/shared/tunnelbroker_messages/src/messages/mod.rs index c85c073c2..406e8e443 100644 --- a/shared/tunnelbroker_messages/src/messages/mod.rs +++ b/shared/tunnelbroker_messages/src/messages/mod.rs @@ -1,50 +1,51 @@ //! Messages sent between Tunnelbroker and a device. pub mod connection_initialization_response; pub mod heartbeat; pub mod keys; pub mod message_receive_confirmation; pub mod message_to_device; pub mod message_to_device_request; pub mod message_to_device_request_status; pub mod session; pub use connection_initialization_response::*; pub use heartbeat::*; pub use keys::*; pub use message_receive_confirmation::*; pub use message_to_device::*; pub use message_to_device_request::*; pub use message_to_device_request_status::*; pub use session::*; use serde::{Deserialize, Serialize}; // This file defines types and validation for messages exchanged // with the Tunnelbroker. The definitions in this file should remain in sync // with the structures defined in the corresponding // JavaScript file at `lib/types/tunnelbroker/messages.js`. // If you edit the definitions in one file, // please make sure to update the corresponding definitions in the other. #[derive(Serialize, Deserialize, Debug)] #[serde(untagged)] pub enum Messages { ConnectionInitializationMessage(ConnectionInitializationMessage), ConnectionInitializationResponse(ConnectionInitializationResponse), + AnonymousInitializationMessage(AnonymousInitializationMessage), // MessageToDeviceRequestStatus must be placed before MessageToDeviceRequest. // This is due to serde's pattern matching behavior where it prioritizes // the first matching pattern it encounters. MessageToDeviceRequestStatus(MessageToDeviceRequestStatus), MessageToDeviceRequest(MessageToDeviceRequest), MessageToDevice(MessageToDevice), MessageReceiveConfirmation(MessageReceiveConfirmation), Heartbeat(Heartbeat), } #[derive(Serialize, Deserialize, Debug)] #[serde(untagged)] pub enum PeerToPeerMessages { RefreshKeysRequest(RefreshKeyRequest), } diff --git a/shared/tunnelbroker_messages/src/messages/session.rs b/shared/tunnelbroker_messages/src/messages/session.rs index 4f98961ce..4d8218442 100644 --- a/shared/tunnelbroker_messages/src/messages/session.rs +++ b/shared/tunnelbroker_messages/src/messages/session.rs @@ -1,68 +1,82 @@ //! The first message sent from WebSocket client to Tunnelbroker. use serde::{Deserialize, Serialize}; /// The workflow when establishing a Tunnelbroker connection: /// - Client sends ConnectionInitializationMessage /// - Tunnelbroker validates access_token with identity service /// - Tunnelbroker emits an AMQP message declaring that it has opened a new /// connection with a given device, so that the respective Tunnelbroker /// instance can close the existing connection. /// - Tunnelbroker returns a session_id representing that the connection was /// accepted /// - Tunnelbroker will flush all messages related to device from RabbitMQ. /// This must be done first before flushing DynamoDB to prevent duplicated /// messages. /// - Tunnelbroker flushes all messages in DynamoDB /// - Tunnelbroker orders messages by creation date (oldest first), and sends /// messages to device /// - Tunnelbroker then polls for incoming messages from device #[derive(Serialize, Deserialize, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub enum DeviceTypes { Mobile, Web, Keyserver, } /// Message sent by a client to Tunnelbroker to initiate a websocket /// session. Tunnelbroker will then validate the access token with identity /// service before continuing with the request. #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "type", rename_all = "camelCase")] pub struct ConnectionInitializationMessage { #[serde(rename = "deviceID")] pub device_id: String, pub access_token: String, #[serde(rename = "userID")] pub user_id: String, pub notify_token: Option, pub device_type: DeviceTypes, pub device_app_version: Option, pub device_os: Option, } +/// Message sent by a client to Tunnelbroker to initiate unauthenticated +/// websocket session. In contrast to [`ConnectionInitializationMessage`], +/// Tunnelbroker won't validate access token, but session capabilities +/// will be limited. +#[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct AnonymousInitializationMessage { + #[serde(rename = "deviceID")] + pub device_id: String, + pub device_type: DeviceTypes, + pub device_app_version: Option, + pub device_os: Option, +} + #[cfg(test)] mod session_tests { use super::*; #[test] fn test_session_deserialization() { let example_payload = r#"{ "type": "sessionRequest", "accessToken": "xkdeifjsld", "deviceID": "foo", "userID": "alice", "deviceType": "keyserver" }"#; let request = serde_json::from_str::(example_payload) .unwrap(); assert_eq!(request.device_id, "foo"); assert_eq!(request.access_token, "xkdeifjsld"); assert_eq!(request.device_os, None); assert_eq!(request.device_type, DeviceTypes::Keyserver); } }