diff --git a/services/identity/src/constants.rs b/services/identity/src/constants.rs --- a/services/identity/src/constants.rs +++ b/services/identity/src/constants.rs @@ -124,6 +124,10 @@ pub const NONCE_LENGTH: usize = 17; pub const NONCE_TTL_DURATION: i64 = 30; +// Identity + +pub const DEFAULT_IDENTITY_ENDPOINT: &str = "http://localhost:50054"; + // LocalStack pub const LOCALSTACK_ENDPOINT: &str = "LOCALSTACK_ENDPOINT"; diff --git a/services/identity/src/websockets/auth.rs b/services/identity/src/websockets/auth.rs new file mode 100644 --- /dev/null +++ b/services/identity/src/websockets/auth.rs @@ -0,0 +1,70 @@ +use client_proto::VerifyUserAccessTokenRequest; +use grpc_clients::identity; +use grpc_clients::tonic::Request; +use identity::get_unauthenticated_client; +use identity::protos::unauthenticated as client_proto; +use serde::{Deserialize, Serialize}; +use tracing::{debug, error}; + +use crate::constants::DEFAULT_IDENTITY_ENDPOINT; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct AuthMessage { + #[serde(rename = "userID")] + pub user_id: String, + #[serde(rename = "deviceID")] + pub device_id: String, + pub access_token: String, +} + +const PLACEHOLDER_CODE_VERSION: u64 = 0; +const DEVICE_TYPE: &str = "service"; + +async fn verify_user_access_token( + user_id: &str, + device_id: &str, + access_token: &str, +) -> Result { + let mut grpc_client = get_unauthenticated_client( + DEFAULT_IDENTITY_ENDPOINT, + PLACEHOLDER_CODE_VERSION, + DEVICE_TYPE.to_string(), + ) + .await + .map_err(|e| format!("failed to get unauthenticated client: {}", e))?; + let message = VerifyUserAccessTokenRequest { + user_id: user_id.to_string(), + signing_public_key: device_id.to_string(), + access_token: access_token.to_string(), + }; + + let request = Request::new(message); + let response = grpc_client + .verify_user_access_token(request) + .await + .map_err(|e| format!("failed to verify user access token: {}", e))?; + Ok(response.into_inner().token_valid) +} + +pub async fn handle_auth_message(message: &str) -> Result<(), String> { + error!("Handling auth message: {}", message); + let auth_message: AuthMessage = serde_json::from_str(message.trim()) + .map_err(|e| format!("failed to parse auth message: {}", e))?; + + let user_id = auth_message.user_id; + let device_id = auth_message.device_id; + let access_token = auth_message.access_token; + + let is_valid_token = + verify_user_access_token(&user_id, &device_id, &access_token).await?; + + if is_valid_token { + debug!("User {} authenticated", user_id); + } else { + debug!("User {} not authenticated", user_id); + return Err("invalid authentication token".into()); + } + + Ok(()) +} diff --git a/services/identity/src/websockets/mod.rs b/services/identity/src/websockets/mod.rs --- a/services/identity/src/websockets/mod.rs +++ b/services/identity/src/websockets/mod.rs @@ -11,6 +11,8 @@ use tokio::net::TcpListener; use tracing::{debug, error, info}; +mod auth; + use crate::config::CONFIG; use crate::constants::IDENTITY_SERVICE_WEBSOCKET_ADDR; @@ -151,6 +153,35 @@ let opensearch_url = format!("https://{}/users/_search/", &CONFIG.opensearch_endpoint); + if let Some(Ok(auth_message)) = incoming.next().await { + match auth_message { + Message::Text(text) => { + if let Err(auth_error) = auth::handle_auth_message(&text).await { + let error_msg = serde_json::json!({ + "action": "errorMessage", + "error": auth_error.to_string() + }); + + if let Err(send_error) = outgoing + .send(Message::Text(format!("{}", error_msg.to_string()))) + .await + { + error!("Error sending auth error response: {}", send_error); + } + + return; + } + } + _ => { + error!("Invalid authentication message from {}", addr); + return; + } + } + } else { + error!("No authentication message from {}", addr); + return; + } + while let Some(message) = incoming.next().await { match message { Ok(Message::Close(_)) => {