diff --git a/keyserver/addons/rust-node-addon/src/identity_client/delete_user.rs b/keyserver/addons/rust-node-addon/src/identity_client/delete_user.rs index 8bdd331e3..35bd67fdb 100644 --- a/keyserver/addons/rust-node-addon/src/identity_client/delete_user.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/delete_user.rs @@ -1,33 +1,25 @@ use super::*; #[napi] #[instrument(skip_all)] pub async fn delete_user(user_id: String) -> Result<()> { - let channel = Channel::from_static(&IDENTITY_SERVICE_SOCKET_ADDR) - .connect() - .await - .map_err(|_| { - Error::new( - Status::GenericFailure, - "Unable to connect to identity service".to_string(), - ) - })?; + let channel = get_identity_service_channel().await?; let token: MetadataValue<_> = AUTH_TOKEN .parse() .map_err(|_| Error::from_status(Status::GenericFailure))?; let mut identity_client = IdentityServiceClient::with_interceptor(channel, |mut req: Request<()>| { req.metadata_mut().insert("authorization", token.clone()); Ok(req) }); let request = Request::new(DeleteUserRequest { user_id: user_id.clone(), }); identity_client .delete_user(request) .await .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?; Ok(()) } diff --git a/keyserver/addons/rust-node-addon/src/identity_client/login_user.rs b/keyserver/addons/rust-node-addon/src/identity_client/login_user.rs index 4c597d21d..eaaeaf6f4 100644 --- a/keyserver/addons/rust-node-addon/src/identity_client/login_user.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/login_user.rs @@ -1,137 +1,201 @@ use super::*; +#[napi] +#[instrument(skip_all)] +async fn login_user_wallet( + user_id: String, + signing_public_key: String, + siwe_message: String, + siwe_signature: String, + mut session_initialization_info: HashMap, + social_proof: String, +) -> Result { + let channel = get_identity_service_channel().await?; + let token: MetadataValue<_> = AUTH_TOKEN + .parse() + .map_err(|_| Error::from_status(Status::GenericFailure))?; + let mut identity_client = + IdentityServiceClient::with_interceptor(channel, |mut req: Request<()>| { + req.metadata_mut().insert("authorization", token.clone()); + Ok(req) + }); + + // Create a LoginRequest channel and use ReceiverStream to turn the + // MPSC receiver into a Stream for outbound messages + let (tx, rx) = mpsc::channel(1); + let stream = ReceiverStream::new(rx); + let request = Request::new(stream); + + let mut response_stream = identity_client + .login_user(request) + .await + .map_err(|_| Error::from_status(Status::GenericFailure))? + .into_inner(); + + // Start wallet login on client and send initial login request to Identity + // service + session_initialization_info.insert("socialProof".to_string(), social_proof); + let login_request = LoginRequest { + data: Some(WalletLoginRequest(WalletLoginRequestStruct { + user_id, + signing_public_key, + siwe_message, + siwe_signature, + session_initialization_info: Some(SessionInitializationInfo { + info: session_initialization_info, + }), + })), + }; + if let Err(e) = tx.send(login_request).await { + error!("Response was dropped: {}", e); + return Err(Error::from_status(Status::GenericFailure)); + } + + // Return access token + let message = response_stream.message().await.map_err(|e| { + error!("Received an error from inbound message stream: {}", e); + Error::from_status(Status::GenericFailure) + })?; + get_wallet_access_token(message) +} + #[napi] #[instrument(skip_all)] async fn login_user_pake( user_id: String, signing_public_key: String, password: String, session_initialization_info: HashMap, ) -> Result { - let channel = Channel::from_static(&IDENTITY_SERVICE_SOCKET_ADDR) - .connect() - .await - .map_err(|_| { - Error::new( - Status::GenericFailure, - "Unable to connect to identity service".to_string(), - ) - })?; + let channel = get_identity_service_channel().await?; let token: MetadataValue<_> = AUTH_TOKEN .parse() .map_err(|_| Error::from_status(Status::GenericFailure))?; let mut identity_client = IdentityServiceClient::with_interceptor(channel, |mut req: Request<()>| { req.metadata_mut().insert("authorization", token.clone()); Ok(req) }); // Create a LoginRequest channel and use ReceiverStream to turn the // MPSC receiver into a Stream for outbound messages let (tx, rx) = mpsc::channel(1); let stream = ReceiverStream::new(rx); let request = Request::new(stream); // `response` is the Stream for inbound messages let mut response = identity_client .login_user(request) .await .map_err(|_| Error::from_status(Status::GenericFailure))? .into_inner(); // Start PAKE login on client and send initial login request to Identity // service let mut client_rng = OsRng; let client_login_start_result = pake_login_start(&mut client_rng, &password)?; let pake_credential_request = client_login_start_result.message.serialize().map_err(|e| { error!("Could not serialize credential request: {}", e); Error::new(Status::GenericFailure, e.to_string()) })?; let login_request = LoginRequest { data: Some(PakeLoginRequest(PakeLoginRequestStruct { data: Some(PakeCredentialRequestAndUserId( PakeCredentialRequestAndUserIdStruct { user_id, signing_public_key, pake_credential_request, session_initialization_info: Some(SessionInitializationInfo { info: session_initialization_info, }), }, )), })), }; send_to_mpsc(tx.clone(), login_request).await?; // Handle responses from Identity service sequentially, making sure we get // messages in the correct order // Finish PAKE login; send final login request to Identity service let message = response.message().await.map_err(|e| { error!("Received an error from inbound message stream: {}", e); Error::from_status(Status::GenericFailure) })?; handle_login_credential_response( message, client_login_start_result.state, tx, ) .await?; // Return access token let message = response.message().await.map_err(|e| { error!("Received an error from inbound message stream: {}", e); Error::from_status(Status::GenericFailure) })?; handle_login_token_response(message) } async fn handle_login_credential_response( message: Option, client_login: ClientLogin, tx: mpsc::Sender, ) -> Result<(), Status> { if let Some(LoginResponse { data: Some(LoginPakeLoginResponse(PakeLoginResponseStruct { data: Some(PakeCredentialResponse(credential_response_bytes)), })), }) = message { let credential_finalization_bytes = pake_login_finish(&credential_response_bytes, client_login)? .serialize() .map_err(|e| { error!("Could not serialize credential request: {}", e); Error::from_status(Status::GenericFailure) })?; let login_request = LoginRequest { data: Some(PakeLoginRequest(PakeLoginRequestStruct { data: Some(LoginPakeCredentialFinalization( credential_finalization_bytes, )), })), }; send_to_mpsc(tx, login_request).await } else { Err(handle_unexpected_response(message)) } } fn handle_login_token_response( message: Option, ) -> Result { if let Some(LoginResponse { data: Some(LoginPakeLoginResponse(PakeLoginResponseStruct { data: Some(AccessToken(access_token)), })), }) = message { Ok(access_token) } else { Err(handle_unexpected_response(message)) } } + +fn get_wallet_access_token( + message: Option, +) -> Result { + if let Some(LoginResponse { + data: Some(WalletLoginResponse(WalletLoginResponseStruct { access_token })), + }) = message + { + Ok(access_token) + } else { + Err(handle_unexpected_response(message)) + } +} diff --git a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs index d7a761d39..e1abab831 100644 --- a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs @@ -1,102 +1,117 @@ pub mod delete_user; pub mod login_user; pub mod register_user; pub mod identity { tonic::include_proto!("identity"); } use comm_opaque::Cipher; use identity::identity_service_client::IdentityServiceClient; use identity::{ login_request::Data::PakeLoginRequest, + login_request::Data::WalletLoginRequest, login_response::Data::PakeLoginResponse as LoginPakeLoginResponse, + login_response::Data::WalletLoginResponse, pake_login_request::Data::PakeCredentialFinalization as LoginPakeCredentialFinalization, pake_login_request::Data::PakeCredentialRequestAndUserId, pake_login_response::Data::AccessToken, pake_login_response::Data::PakeCredentialResponse, registration_request::Data::PakeCredentialFinalization as RegistrationPakeCredentialFinalization, registration_request::Data::PakeRegistrationRequestAndUserId, registration_request::Data::PakeRegistrationUploadAndCredentialRequest, registration_response::Data::PakeLoginResponse as RegistrationPakeLoginResponse, registration_response::Data::PakeRegistrationResponse, DeleteUserRequest, LoginRequest, LoginResponse, PakeCredentialRequestAndUserId as PakeCredentialRequestAndUserIdStruct, PakeLoginRequest as PakeLoginRequestStruct, PakeLoginResponse as PakeLoginResponseStruct, PakeRegistrationRequestAndUserId as PakeRegistrationRequestAndUserIdStruct, PakeRegistrationUploadAndCredentialRequest as PakeRegistrationUploadAndCredentialRequestStruct, RegistrationRequest, RegistrationResponse as RegistrationResponseMessage, - SessionInitializationInfo, + SessionInitializationInfo, WalletLoginRequest as WalletLoginRequestStruct, + WalletLoginResponse as WalletLoginResponseStruct, }; use lazy_static::lazy_static; use napi::bindgen_prelude::*; use opaque_ke::{ ClientLogin, ClientLoginFinishParameters, ClientLoginStartParameters, ClientLoginStartResult, ClientRegistration, ClientRegistrationFinishParameters, CredentialFinalization, CredentialResponse, RegistrationResponse, RegistrationUpload, }; use rand::{rngs::OsRng, CryptoRng, Rng}; use std::collections::HashMap; use std::env::var; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; use tonic::{metadata::MetadataValue, transport::Channel, Request}; use tracing::{error, instrument}; lazy_static! { pub static ref IDENTITY_SERVICE_SOCKET_ADDR: String = var("COMM_IDENTITY_SERVICE_SOCKET_ADDR") .unwrap_or_else(|_| "https://[::1]:50051".to_string()); pub static ref AUTH_TOKEN: String = var("COMM_IDENTITY_SERVICE_AUTH_TOKEN") .unwrap_or_else(|_| "test".to_string()); } fn handle_unexpected_response(message: Option) -> Error { error!("Received an unexpected message: {:?}", message); Error::from_status(Status::GenericFailure) } async fn send_to_mpsc(tx: mpsc::Sender, request: T) -> Result<()> { if let Err(e) = tx.send(request).await { error!("Response was dropped: {}", e); return Err(Error::from_status(Status::GenericFailure)); } Ok(()) } fn pake_login_start( rng: &mut (impl Rng + CryptoRng), password: &str, ) -> Result> { ClientLogin::::start( rng, password.as_bytes(), ClientLoginStartParameters::default(), ) .map_err(|e| { error!("Failed to start PAKE login: {}", e); Error::from_status(Status::GenericFailure) }) } fn pake_login_finish( credential_response_bytes: &[u8], client_login: ClientLogin, ) -> Result> { client_login .finish( CredentialResponse::deserialize(credential_response_bytes).map_err( |e| { error!("Could not deserialize credential response bytes: {}", e); Error::from_status(Status::GenericFailure) }, )?, ClientLoginFinishParameters::default(), ) .map_err(|e| { error!("Failed to finish PAKE login: {}", e); Error::from_status(Status::GenericFailure) }) .map(|res| res.message) } + +async fn get_identity_service_channel() -> Result { + Channel::from_static(&IDENTITY_SERVICE_SOCKET_ADDR) + .connect() + .await + .map_err(|_| { + Error::new( + Status::GenericFailure, + "Unable to connect to identity service".to_string(), + ) + }) +} diff --git a/keyserver/addons/rust-node-addon/src/identity_client/register_user.rs b/keyserver/addons/rust-node-addon/src/identity_client/register_user.rs index edba3f1a8..1e3febcb8 100644 --- a/keyserver/addons/rust-node-addon/src/identity_client/register_user.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/register_user.rs @@ -1,235 +1,227 @@ use super::*; #[napi] #[instrument(skip_all)] pub async fn register_user( user_id: String, signing_public_key: String, username: String, password: String, session_initialization_info: HashMap, ) -> Result { - let channel = Channel::from_static(&IDENTITY_SERVICE_SOCKET_ADDR) - .connect() - .await - .map_err(|_| { - Error::new( - Status::GenericFailure, - "Unable to connect to identity service".to_string(), - ) - })?; + let channel = get_identity_service_channel().await?; let token: MetadataValue<_> = AUTH_TOKEN .parse() .map_err(|_| Error::from_status(Status::GenericFailure))?; let mut identity_client = IdentityServiceClient::with_interceptor(channel, |mut req: Request<()>| { req.metadata_mut().insert("authorization", token.clone()); Ok(req) }); // Create a RegistrationRequest channel and use ReceiverStream to turn the // MPSC receiver into a Stream for outbound messages let (tx, rx) = mpsc::channel(1); let stream = ReceiverStream::new(rx); let request = Request::new(stream); // `response` is the Stream for inbound messages let mut response = identity_client .register_user(request) .await .map_err(|_| Error::from_status(Status::GenericFailure))? .into_inner(); // Start PAKE registration on client and send initial registration request // to Identity service let mut client_rng = OsRng; let (registration_request, client_registration) = pake_registration_start( &mut client_rng, user_id, signing_public_key, &password, username, SessionInitializationInfo { info: session_initialization_info, }, )?; send_to_mpsc(tx.clone(), registration_request).await?; // Handle responses from Identity service sequentially, making sure we get // messages in the correct order // Finish PAKE registration and begin PAKE login; send the final // registration request and initial login request together to reduce the // number of trips let message = response .message() .await .map_err(|_| Error::from_status(Status::GenericFailure))?; let client_login = handle_registration_response( message, &mut client_rng, client_registration, &password, tx.clone(), ) .await?; // Finish PAKE login; send final login request to Identity service let message = response .message() .await .map_err(|_| Error::from_status(Status::GenericFailure))?; handle_registration_credential_response(message, client_login, tx) .await .map_err(|_| Error::from_status(Status::GenericFailure))?; // Return access token let message = response .message() .await .map_err(|_| Error::from_status(Status::GenericFailure))?; handle_registration_token_response(message) } async fn handle_registration_response( message: Option, client_rng: &mut (impl Rng + CryptoRng), client_registration: ClientRegistration, password: &str, tx: mpsc::Sender, ) -> Result> { if let Some(RegistrationResponseMessage { data: Some(PakeRegistrationResponse(registration_response_bytes)), .. }) = message { let pake_registration_upload = pake_registration_finish( client_rng, ®istration_response_bytes, client_registration, )? .serialize(); let client_login_start_result = pake_login_start(client_rng, password)?; // `registration_request` is a gRPC message containing serialized bytes to // complete PAKE registration and begin PAKE login let registration_request = RegistrationRequest { data: Some(PakeRegistrationUploadAndCredentialRequest( PakeRegistrationUploadAndCredentialRequestStruct { pake_registration_upload, pake_credential_request: client_login_start_result .message .serialize() .map_err(|e| { error!("Could not serialize credential request: {}", e); Error::from_status(Status::GenericFailure) })?, }, )), }; send_to_mpsc(tx, registration_request).await?; Ok(client_login_start_result.state) } else { Err(handle_unexpected_response(message)) } } async fn handle_registration_credential_response( message: Option, client_login: ClientLogin, tx: mpsc::Sender, ) -> Result<()> { if let Some(RegistrationResponseMessage { data: Some(RegistrationPakeLoginResponse(PakeLoginResponseStruct { data: Some(PakeCredentialResponse(credential_response_bytes)), })), }) = message { let registration_request = RegistrationRequest { data: Some(RegistrationPakeCredentialFinalization( pake_login_finish(&credential_response_bytes, client_login)? .serialize() .map_err(|e| { error!("Could not serialize credential request: {}", e); Error::from_status(Status::GenericFailure) })?, )), }; send_to_mpsc(tx, registration_request).await } else { Err(handle_unexpected_response(message)) } } fn handle_registration_token_response( message: Option, ) -> Result { if let Some(RegistrationResponseMessage { data: Some(RegistrationPakeLoginResponse(PakeLoginResponseStruct { data: Some(AccessToken(access_token)), })), }) = message { Ok(access_token) } else { Err(handle_unexpected_response(message)) } } fn pake_registration_start( rng: &mut (impl Rng + CryptoRng), user_id: String, signing_public_key: String, password: &str, username: String, session_initialization_info: SessionInitializationInfo, ) -> Result<(RegistrationRequest, ClientRegistration)> { let client_registration_start_result = ClientRegistration::::start(rng, password.as_bytes()).map_err( |e| { error!("Failed to start PAKE registration: {}", e); Error::from_status(Status::GenericFailure) }, )?; let pake_registration_request = client_registration_start_result.message.serialize(); Ok(( RegistrationRequest { data: Some(PakeRegistrationRequestAndUserId( PakeRegistrationRequestAndUserIdStruct { user_id, pake_registration_request, username, signing_public_key, session_initialization_info: Some(session_initialization_info), }, )), }, client_registration_start_result.state, )) } fn pake_registration_finish( rng: &mut (impl Rng + CryptoRng), registration_response_bytes: &[u8], client_registration: ClientRegistration, ) -> Result> { client_registration .finish( rng, RegistrationResponse::deserialize(registration_response_bytes).map_err( |e| { error!("Could not deserialize registration response bytes: {}", e); Error::from_status(Status::GenericFailure) }, )?, ClientRegistrationFinishParameters::default(), ) .map_err(|e| { error!("Failed to finish PAKE registration: {}", e); Error::from_status(Status::GenericFailure) }) .map(|res| res.message) } diff --git a/lib/types/rust-binding-types.js b/lib/types/rust-binding-types.js index ae8c70f0c..e50b54454 100644 --- a/lib/types/rust-binding-types.js +++ b/lib/types/rust-binding-types.js @@ -1,36 +1,44 @@ // @flow import type { SignedIdentityKeysBlob } from './crypto-types.js'; type tunnelbrokerOnReceiveCallback = ( err: Error | null, payload: string, ) => mixed; declare class TunnelbrokerClientClass { constructor( deviceId: string, onReceiveCallback: tunnelbrokerOnReceiveCallback, ): TunnelbrokerClientClass; publish(toDeviceId: string, payload: string): Promise; } type RustNativeBindingAPI = { +registerUser: ( userId: string, signingPublicKey: string, username: string, password: string, sessionInitializationInfo: SignedIdentityKeysBlob, ) => Promise, +loginUserPake: ( userId: string, signingPublicKey: string, password: string, sessionInitializationInfo: SignedIdentityKeysBlob, ) => Promise, + +loginUserWallet: ( + userId: string, + signingPublicKey: string, + siweMessage: string, + siweSignature: string, + sessionInitializationInfo: SignedIdentityKeysBlob, + socialProof: string, + ) => Promise, +deleteUser: (userId: string) => Promise, +TunnelbrokerClient: Class, }; export type { RustNativeBindingAPI };