Page MenuHomePhabricator

No OneTemporary

diff --git a/keyserver/addons/rust-node-addon/src/identity_client/login.rs b/keyserver/addons/rust-node-addon/src/identity_client/login.rs
index f36b53f3b..9b49fa10c 100644
--- a/keyserver/addons/rust-node-addon/src/identity_client/login.rs
+++ b/keyserver/addons/rust-node-addon/src/identity_client/login.rs
@@ -1,86 +1,103 @@
use super::*;
use comm_opaque2::client::Login;
use grpc_clients::identity::protos::unauthenticated::{
OpaqueLoginFinishRequest, OpaqueLoginStartRequest,
};
use tracing::debug;
#[napi]
#[instrument(skip_all)]
pub async fn login_user(
username: String,
password: String,
signed_identity_keys_blob: SignedIdentityKeysBlob,
content_prekey: String,
content_prekey_signature: String,
notif_prekey: String,
notif_prekey_signature: String,
content_one_time_keys: Vec<String>,
notif_one_time_keys: Vec<String>,
) -> Result<UserLoginInfo> {
debug!("Attempting to login user: {}", username);
// Set up the gRPC client that will be used to talk to the Identity service
let mut identity_client = get_identity_client().await?;
// Start OPAQUE registration and send initial registration request
let mut client_login = Login::new();
let opaque_login_request = client_login
.start(&password)
.map_err(|_| Error::from_reason("Failed to create opaque login request"))?;
let login_start_request = OpaqueLoginStartRequest {
opaque_login_request,
username,
device_key_upload: Some(DeviceKeyUpload {
device_key_info: Some(IdentityKeyInfo {
payload: signed_identity_keys_blob.payload,
payload_signature: signed_identity_keys_blob.signature,
social_proof: None,
}),
content_upload: Some(PreKey {
pre_key: content_prekey,
pre_key_signature: content_prekey_signature,
}),
notif_upload: Some(PreKey {
pre_key: notif_prekey,
pre_key_signature: notif_prekey_signature,
}),
one_time_content_prekeys: content_one_time_keys,
one_time_notif_prekeys: notif_one_time_keys,
device_type: DeviceType::Keyserver.into(),
}),
};
debug!("Starting login to identity service");
- let login_start_response = identity_client
+ let response = identity_client
.login_password_user_start(login_start_request)
.await
- .map_err(handle_grpc_error)?
- .into_inner();
-
+ .map_err(handle_grpc_error)?;
debug!("Received login response from identity service");
+
+ // We need to get the load balancer cookie from from the response and send it
+ // in the subsequent request to ensure it is routed to the same identity
+ // service instance as the first request
+ let cookie = response
+ .metadata()
+ .get(RESPONSE_METADATA_COOKIE_KEY)
+ .cloned();
+
+ let login_start_response = response.into_inner();
+
let opaque_login_upload = client_login
.finish(&login_start_response.opaque_login_response)
.map_err(|_| Error::from_reason("Failed to finish opaque login request"))?;
- let login_finish_request = OpaqueLoginFinishRequest {
+
+ let mut login_finish_request = Request::new(OpaqueLoginFinishRequest {
session_id: login_start_response.session_id,
opaque_login_upload,
- };
+ });
+
+ // Cookie won't be available in local dev environments
+ if let Some(cookie_metadata) = cookie {
+ login_finish_request
+ .metadata_mut()
+ .insert(REQUEST_METADATA_COOKIE_KEY, cookie_metadata);
+ }
debug!("Attempting to finalize opaque login exchange with identity service");
let login_finish_response = identity_client
.login_password_user_finish(login_finish_request)
.await
.map_err(handle_grpc_error)?
.into_inner();
debug!("Finished login with identity service");
let user_info = UserLoginInfo {
user_id: login_finish_response.user_id,
access_token: login_finish_response.access_token,
};
Ok(user_info)
}
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 cd0740526..139f87aac 100644
--- a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs
+++ b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs
@@ -1,194 +1,197 @@
pub mod add_reserved_usernames;
pub mod get_inbound_keys_for_user;
pub mod login;
pub mod prekey;
pub mod register_user;
pub mod remove_reserved_usernames;
pub mod upload_one_time_keys;
use client_proto::identity_client_service_client::IdentityClientServiceClient;
use client_proto::{
AddReservedUsernamesRequest, DeviceKeyUpload, DeviceType, IdentityKeyInfo,
InboundKeyInfo, PreKey, RegistrationFinishRequest, RegistrationStartRequest,
RemoveReservedUsernameRequest,
};
use grpc_clients::identity::authenticated::ChainedInterceptedAuthClient;
use grpc_clients::identity::protos::authenticated::UploadOneTimeKeysRequest;
use grpc_clients::identity::protos::unauthenticated as client_proto;
use grpc_clients::identity::shared::CodeVersionLayer;
+use grpc_clients::identity::{
+ REQUEST_METADATA_COOKIE_KEY, RESPONSE_METADATA_COOKIE_KEY,
+};
use lazy_static::lazy_static;
use napi::bindgen_prelude::*;
use serde::{Deserialize, Serialize};
use std::env::var;
use tonic::codegen::InterceptedService;
use tonic::{transport::Channel, Request};
use tracing::{self, info, instrument, warn, Level};
use tracing_subscriber::EnvFilter;
mod generated {
// We get the CODE_VERSION from this generated file
include!(concat!(env!("OUT_DIR"), "/version.rs"));
}
pub use generated::CODE_VERSION;
pub const DEVICE_TYPE: &str = "keyserver";
lazy_static! {
static ref IDENTITY_SERVICE_CONFIG: IdentityServiceConfig = {
let filter = EnvFilter::builder()
.with_default_directive(Level::INFO.into())
.with_env_var(EnvFilter::DEFAULT_ENV)
.from_env_lossy();
let subscriber = tracing_subscriber::fmt().with_env_filter(filter).finish();
tracing::subscriber::set_global_default(subscriber)
.expect("Unable to configure tracing");
let config_json_string =
var("COMM_JSONCONFIG_secrets_identity_service_config");
match config_json_string {
Ok(json) => serde_json::from_str(&json).unwrap(),
Err(_) => IdentityServiceConfig::default(),
}
};
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct IdentityServiceConfig {
identity_socket_addr: String,
}
impl Default for IdentityServiceConfig {
fn default() -> Self {
info!("Using default identity configuration");
Self {
identity_socket_addr: "http://[::1]:50054".to_string(),
}
}
}
async fn get_identity_client() -> Result<
IdentityClientServiceClient<InterceptedService<Channel, CodeVersionLayer>>,
> {
info!("Connecting to identity service");
grpc_clients::identity::get_unauthenticated_client(
&IDENTITY_SERVICE_CONFIG.identity_socket_addr,
CODE_VERSION,
DEVICE_TYPE.to_string(),
)
.await
.map_err(|_| {
Error::new(
Status::GenericFailure,
"Unable to connect to identity service".to_string(),
)
})
}
async fn get_authenticated_identity_client(
user_id: String,
device_id: String,
access_token: String,
) -> Result<ChainedInterceptedAuthClient> {
info!("Connecting to identity service");
grpc_clients::identity::get_auth_client(
&IDENTITY_SERVICE_CONFIG.identity_socket_addr,
user_id,
device_id,
access_token,
CODE_VERSION,
DEVICE_TYPE.to_string(),
)
.await
.map_err(|_| {
Error::new(
Status::GenericFailure,
"Unable to connect to identity service".to_string(),
)
})
}
#[napi(object)]
pub struct SignedIdentityKeysBlob {
pub payload: String,
pub signature: String,
}
#[napi(object)]
pub struct UserLoginInfo {
pub user_id: String,
pub access_token: String,
}
#[napi(object)]
pub struct InboundKeyInfoResponse {
pub payload: String,
pub payload_signature: String,
pub social_proof: Option<String>,
pub content_prekey: String,
pub content_prekey_signature: String,
pub notif_prekey: String,
pub notif_prekey_signature: String,
}
impl TryFrom<InboundKeyInfo> for InboundKeyInfoResponse {
type Error = Error;
fn try_from(key_info: InboundKeyInfo) -> Result<Self> {
let identity_info = key_info
.identity_info
.ok_or(Error::from_status(Status::GenericFailure))?;
let IdentityKeyInfo {
payload,
payload_signature,
social_proof,
} = identity_info;
let content_prekey = key_info
.content_prekey
.ok_or(Error::from_status(Status::GenericFailure))?;
let PreKey {
pre_key: content_prekey_value,
pre_key_signature: content_prekey_signature,
} = content_prekey;
let notif_prekey = key_info
.notif_prekey
.ok_or(Error::from_status(Status::GenericFailure))?;
let PreKey {
pre_key: notif_prekey_value,
pre_key_signature: notif_prekey_signature,
} = notif_prekey;
Ok(Self {
payload,
payload_signature,
social_proof,
content_prekey: content_prekey_value,
content_prekey_signature,
notif_prekey: notif_prekey_value,
notif_prekey_signature,
})
}
}
pub fn handle_grpc_error(error: tonic::Status) -> napi::Error {
warn!("Received error: {}", error.message());
Error::new(Status::GenericFailure, error.message())
}
#[cfg(test)]
mod tests {
use super::CODE_VERSION;
#[test]
fn test_code_version_exists() {
assert!(CODE_VERSION > 0);
}
}
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 638c9f1be..ed7f3d70c 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,84 +1,101 @@
use super::*;
use tracing::{debug, warn};
#[napi]
#[instrument(skip_all)]
pub async fn register_user(
username: String,
password: String,
signed_identity_keys_blob: SignedIdentityKeysBlob,
content_prekey: String,
content_prekey_signature: String,
notif_prekey: String,
notif_prekey_signature: String,
content_one_time_keys: Vec<String>,
notif_one_time_keys: Vec<String>,
) -> Result<UserLoginInfo> {
debug!("Attempting to register user: {}", username);
// Set up the gRPC client that will be used to talk to the Identity service
let mut identity_client = get_identity_client().await?;
// Start OPAQUE registration and send initial registration request
let mut opaque_registration = comm_opaque2::client::Registration::new();
let opaque_registration_request = opaque_registration
.start(&password)
.map_err(|_| Error::from_status(Status::GenericFailure))?;
let device_key_upload = DeviceKeyUpload {
device_key_info: Some(IdentityKeyInfo {
payload: signed_identity_keys_blob.payload,
payload_signature: signed_identity_keys_blob.signature,
social_proof: None,
}),
content_upload: Some(PreKey {
pre_key: content_prekey,
pre_key_signature: content_prekey_signature,
}),
notif_upload: Some(PreKey {
pre_key: notif_prekey,
pre_key_signature: notif_prekey_signature,
}),
one_time_content_prekeys: content_one_time_keys,
one_time_notif_prekeys: notif_one_time_keys,
device_type: DeviceType::Keyserver.into(),
};
let registration_start_request = Request::new(RegistrationStartRequest {
opaque_registration_request,
username,
device_key_upload: Some(device_key_upload),
});
// Finish OPAQUE registration and send final registration request
- let registration_start_response = identity_client
+ let response = identity_client
.register_password_user_start(registration_start_request)
.await
- .map_err(handle_grpc_error)?
- .into_inner();
+ .map_err(handle_grpc_error)?;
debug!("Received registration start response");
+ // We need to get the load balancer cookie from from the response and send it
+ // in the subsequent request to ensure it is routed to the same identity
+ // service instance as the first request
+ let cookie = response
+ .metadata()
+ .get(RESPONSE_METADATA_COOKIE_KEY)
+ .cloned();
+
+ let registration_start_response = response.into_inner();
+
let opaque_registration_upload = opaque_registration
.finish(
&password,
&registration_start_response.opaque_registration_response,
)
.map_err(|_| Error::from_status(Status::GenericFailure))?;
- let registration_finish_request = Request::new(RegistrationFinishRequest {
- session_id: registration_start_response.session_id,
- opaque_registration_upload,
- });
+ let mut registration_finish_request =
+ Request::new(RegistrationFinishRequest {
+ session_id: registration_start_response.session_id,
+ opaque_registration_upload,
+ });
+
+ // Cookie won't be available in local dev environments
+ if let Some(cookie_metadata) = cookie {
+ registration_finish_request
+ .metadata_mut()
+ .insert(REQUEST_METADATA_COOKIE_KEY, cookie_metadata);
+ }
let registration_response = identity_client
.register_password_user_finish(registration_finish_request)
.await
.map_err(handle_grpc_error)?
.into_inner();
let user_info = UserLoginInfo {
user_id: registration_response.user_id,
access_token: registration_response.access_token,
};
Ok(user_info)
}
diff --git a/native/native_rust_library/src/lib.rs b/native/native_rust_library/src/lib.rs
index 3ce8d0f80..d5911ef09 100644
--- a/native/native_rust_library/src/lib.rs
+++ b/native/native_rust_library/src/lib.rs
@@ -1,839 +1,899 @@
use crate::ffi::{bool_callback, string_callback, void_callback};
use comm_opaque2::client::{Login, Registration};
use comm_opaque2::grpc::opaque_error_to_grpc_status as handle_error;
use grpc_clients::identity::protos::authenticated::{
UpdateUserPasswordFinishRequest, UpdateUserPasswordStartRequest,
};
use grpc_clients::identity::protos::client::{
outbound_keys_for_user_request::Identifier, DeviceKeyUpload, DeviceType,
Empty, IdentityKeyInfo, OpaqueLoginFinishRequest, OpaqueLoginStartRequest,
OutboundKeyInfo, OutboundKeysForUserRequest, PreKey,
RegistrationFinishRequest, RegistrationStartRequest, WalletLoginRequest,
};
-use grpc_clients::identity::{get_auth_client, get_unauthenticated_client};
+use grpc_clients::identity::{
+ get_auth_client, get_unauthenticated_client, REQUEST_METADATA_COOKIE_KEY,
+ RESPONSE_METADATA_COOKIE_KEY,
+};
use lazy_static::lazy_static;
use serde::Serialize;
use std::sync::Arc;
use tokio::runtime::{Builder, Runtime};
-use tonic::Status;
+use tonic::{Request, Status};
use tracing::instrument;
mod argon2_tools;
mod backup;
mod constants;
use argon2_tools::compute_backup_key_str;
mod generated {
// We get the CODE_VERSION from this generated file
include!(concat!(env!("OUT_DIR"), "/version.rs"));
}
pub use generated::CODE_VERSION;
#[cfg(not(feature = "android"))]
pub const DEVICE_TYPE: DeviceType = DeviceType::Ios;
#[cfg(feature = "android")]
pub const DEVICE_TYPE: DeviceType = DeviceType::Android;
lazy_static! {
pub static ref RUNTIME: Arc<Runtime> =
Arc::new(Builder::new_multi_thread().enable_all().build().unwrap());
}
use backup::ffi::*;
#[cxx::bridge]
mod ffi {
extern "Rust" {
#[cxx_name = "identityRegisterUser"]
fn register_user(
username: String,
password: String,
key_payload: String,
key_payload_signature: String,
content_prekey: String,
content_prekey_signature: String,
notif_prekey: String,
notif_prekey_signature: String,
content_one_time_keys: Vec<String>,
notif_one_time_keys: Vec<String>,
promise_id: u32,
);
#[cxx_name = "identityLoginPasswordUser"]
fn login_password_user(
username: String,
password: String,
key_payload: String,
key_payload_signature: String,
content_prekey: String,
content_prekey_signature: String,
notif_prekey: String,
notif_prekey_signature: String,
content_one_time_keys: Vec<String>,
notif_one_time_keys: Vec<String>,
promise_id: u32,
);
#[cxx_name = "identityLoginWalletUser"]
fn login_wallet_user(
siwe_message: String,
siwe_signature: String,
key_payload: String,
key_payload_signature: String,
content_prekey: String,
content_prekey_signature: String,
notif_prekey: String,
notif_prekey_signature: String,
content_one_time_keys: Vec<String>,
notif_one_time_keys: Vec<String>,
social_proof: String,
promise_id: u32,
);
#[cxx_name = "identityUpdateUserPassword"]
fn update_user_password(
user_id: String,
device_id: String,
access_token: String,
password: String,
promise_id: u32,
);
#[cxx_name = "identityDeleteUser"]
fn delete_user(
user_id: String,
device_id: String,
access_token: String,
promise_id: u32,
);
#[cxx_name = "identityGetOutboundKeysForUserDevice"]
fn get_outbound_keys_for_user_device(
identifier_type: String,
identifier_value: String,
device_id: String,
promise_id: u32,
);
#[cxx_name = "identityGenerateNonce"]
fn generate_nonce(promise_id: u32);
#[cxx_name = "identityVersionSupported"]
fn version_supported(promise_id: u32);
// Argon2
#[cxx_name = "compute_backup_key"]
fn compute_backup_key_str(
password: &str,
backup_id: &str,
) -> Result<[u8; 32]>;
}
unsafe extern "C++" {
include!("RustCallback.h");
#[namespace = "comm"]
#[cxx_name = "stringCallback"]
fn string_callback(error: String, promise_id: u32, ret: String);
#[namespace = "comm"]
#[cxx_name = "voidCallback"]
fn void_callback(error: String, promise_id: u32);
#[namespace = "comm"]
#[cxx_name = "boolCallback"]
fn bool_callback(error: String, promise_id: u32, ret: bool);
}
// AES cryptography
#[namespace = "comm"]
unsafe extern "C++" {
include!("RustAESCrypto.h");
#[allow(unused)]
#[cxx_name = "aesGenerateKey"]
fn generate_key(buffer: &mut [u8]) -> Result<()>;
/// The first two argument aren't mutated but creation of Java ByteBuffer
/// requires the underlying bytes to be mutable.
#[allow(unused)]
#[cxx_name = "aesEncrypt"]
fn encrypt(
key: &mut [u8],
plaintext: &mut [u8],
sealed_data: &mut [u8],
) -> Result<()>;
/// The first two argument aren't mutated but creation of Java ByteBuffer
/// requires the underlying bytes to be mutable.
#[allow(unused)]
#[cxx_name = "aesDecrypt"]
fn decrypt(
key: &mut [u8],
sealed_data: &mut [u8],
plaintext: &mut [u8],
) -> Result<()>;
}
// Backup
extern "Rust" {
#[cxx_name = "createBackup"]
fn create_backup_sync(
backup_id: String,
backup_secret: String,
pickle_key: String,
pickled_account: String,
user_data: String,
promise_id: u32,
);
#[cxx_name = "restoreBackup"]
fn restore_backup_sync(
backup_id: String,
backup_secret: String,
encrypted_user_keys: String,
encrypted_user_data: String,
promise_id: u32,
);
}
}
fn handle_string_result_as_callback<E>(
result: Result<String, E>,
promise_id: u32,
) where
E: std::fmt::Display,
{
match result {
Err(e) => string_callback(e.to_string(), promise_id, "".to_string()),
Ok(r) => string_callback("".to_string(), promise_id, r),
}
}
fn handle_void_result_as_callback<E>(result: Result<(), E>, promise_id: u32)
where
E: std::fmt::Display,
{
match result {
Err(e) => void_callback(e.to_string(), promise_id),
Ok(_) => void_callback("".to_string(), promise_id),
}
}
fn handle_bool_result_as_callback<E>(result: Result<bool, E>, promise_id: u32)
where
E: std::fmt::Display,
{
match result {
Err(e) => bool_callback(e.to_string(), promise_id, false),
Ok(r) => bool_callback("".to_string(), promise_id, r),
}
}
fn generate_nonce(promise_id: u32) {
RUNTIME.spawn(async move {
let result = fetch_nonce().await;
handle_string_result_as_callback(result, promise_id);
});
}
async fn fetch_nonce() -> Result<String, Error> {
let mut identity_client = get_unauthenticated_client(
"http://127.0.0.1:50054",
CODE_VERSION,
DEVICE_TYPE.as_str_name().to_lowercase(),
)
.await?;
let nonce = identity_client
.generate_nonce(Empty {})
.await?
.into_inner()
.nonce;
Ok(nonce)
}
fn version_supported(promise_id: u32) {
RUNTIME.spawn(async move {
let result = version_supported_helper().await;
handle_bool_result_as_callback(result, promise_id);
});
}
async fn version_supported_helper() -> Result<bool, Error> {
let mut identity_client = get_unauthenticated_client(
"http://127.0.0.1:50054",
CODE_VERSION,
DEVICE_TYPE.as_str_name().to_lowercase(),
)
.await?;
let response = identity_client.ping(Empty {}).await;
match response {
Ok(_) => Ok(true),
Err(e) => {
if grpc_clients::error::is_version_unsupported(&e) {
Ok(false)
} else {
Err(e.into())
}
}
}
}
struct AuthInfo {
user_id: String,
device_id: String,
access_token: String,
}
#[instrument]
fn register_user(
username: String,
password: String,
key_payload: String,
key_payload_signature: String,
content_prekey: String,
content_prekey_signature: String,
notif_prekey: String,
notif_prekey_signature: String,
content_one_time_keys: Vec<String>,
notif_one_time_keys: Vec<String>,
promise_id: u32,
) {
RUNTIME.spawn(async move {
let password_user_info = PasswordUserInfo {
username,
password,
key_payload,
key_payload_signature,
content_prekey,
content_prekey_signature,
notif_prekey,
notif_prekey_signature,
content_one_time_keys,
notif_one_time_keys,
};
let result = register_user_helper(password_user_info).await;
handle_string_result_as_callback(result, promise_id);
});
}
struct PasswordUserInfo {
username: String,
password: String,
key_payload: String,
key_payload_signature: String,
content_prekey: String,
content_prekey_signature: String,
notif_prekey: String,
notif_prekey_signature: String,
content_one_time_keys: Vec<String>,
notif_one_time_keys: Vec<String>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct UserIDAndDeviceAccessToken {
#[serde(rename = "userID")]
user_id: String,
access_token: String,
}
async fn register_user_helper(
password_user_info: PasswordUserInfo,
) -> Result<String, Error> {
let mut client_registration = Registration::new();
let opaque_registration_request = client_registration
.start(&password_user_info.password)
.map_err(handle_error)?;
let registration_start_request = RegistrationStartRequest {
opaque_registration_request,
username: password_user_info.username,
device_key_upload: Some(DeviceKeyUpload {
device_key_info: Some(IdentityKeyInfo {
payload: password_user_info.key_payload,
payload_signature: password_user_info.key_payload_signature,
social_proof: None,
}),
content_upload: Some(PreKey {
pre_key: password_user_info.content_prekey,
pre_key_signature: password_user_info.content_prekey_signature,
}),
notif_upload: Some(PreKey {
pre_key: password_user_info.notif_prekey,
pre_key_signature: password_user_info.notif_prekey_signature,
}),
one_time_content_prekeys: password_user_info.content_one_time_keys,
one_time_notif_prekeys: password_user_info.notif_one_time_keys,
device_type: DEVICE_TYPE.into(),
}),
};
let mut identity_client = get_unauthenticated_client(
"http://127.0.0.1:50054",
CODE_VERSION,
DEVICE_TYPE.as_str_name().to_lowercase(),
)
.await?;
- let registration_start_response = identity_client
+ let response = identity_client
.register_password_user_start(registration_start_request)
- .await?
- .into_inner();
+ .await?;
+
+ // We need to get the load balancer cookie from from the response and send it
+ // in the subsequent request to ensure it is routed to the same identity
+ // service instance as the first request
+ let cookie = response
+ .metadata()
+ .get(RESPONSE_METADATA_COOKIE_KEY)
+ .cloned();
+
+ let registration_start_response = response.into_inner();
let opaque_registration_upload = client_registration
.finish(
&password_user_info.password,
&registration_start_response.opaque_registration_response,
)
.map_err(handle_error)?;
+
let registration_finish_request = RegistrationFinishRequest {
session_id: registration_start_response.session_id,
opaque_registration_upload,
};
+ let mut finish_request = Request::new(registration_finish_request);
+
+ // Cookie won't be available in local dev environments
+ if let Some(cookie_metadata) = cookie {
+ finish_request
+ .metadata_mut()
+ .insert(REQUEST_METADATA_COOKIE_KEY, cookie_metadata);
+ }
+
let registration_finish_response = identity_client
- .register_password_user_finish(registration_finish_request)
+ .register_password_user_finish(finish_request)
.await?
.into_inner();
let user_id_and_access_token = UserIDAndDeviceAccessToken {
user_id: registration_finish_response.user_id,
access_token: registration_finish_response.access_token,
};
Ok(serde_json::to_string(&user_id_and_access_token)?)
}
#[instrument]
fn login_password_user(
username: String,
password: String,
key_payload: String,
key_payload_signature: String,
content_prekey: String,
content_prekey_signature: String,
notif_prekey: String,
notif_prekey_signature: String,
content_one_time_keys: Vec<String>,
notif_one_time_keys: Vec<String>,
promise_id: u32,
) {
RUNTIME.spawn(async move {
let password_user_info = PasswordUserInfo {
username,
password,
key_payload,
key_payload_signature,
content_prekey,
content_prekey_signature,
notif_prekey,
notif_prekey_signature,
content_one_time_keys,
notif_one_time_keys,
};
let result = login_password_user_helper(password_user_info).await;
handle_string_result_as_callback(result, promise_id);
});
}
async fn login_password_user_helper(
password_user_info: PasswordUserInfo,
) -> Result<String, Error> {
let mut client_login = Login::new();
let opaque_login_request = client_login
.start(&password_user_info.password)
.map_err(handle_error)?;
let login_start_request = OpaqueLoginStartRequest {
opaque_login_request,
username: password_user_info.username,
device_key_upload: Some(DeviceKeyUpload {
device_key_info: Some(IdentityKeyInfo {
payload: password_user_info.key_payload,
payload_signature: password_user_info.key_payload_signature,
social_proof: None,
}),
content_upload: Some(PreKey {
pre_key: password_user_info.content_prekey,
pre_key_signature: password_user_info.content_prekey_signature,
}),
notif_upload: Some(PreKey {
pre_key: password_user_info.notif_prekey,
pre_key_signature: password_user_info.notif_prekey_signature,
}),
one_time_content_prekeys: password_user_info.content_one_time_keys,
one_time_notif_prekeys: password_user_info.notif_one_time_keys,
device_type: DEVICE_TYPE.into(),
}),
};
let mut identity_client = get_unauthenticated_client(
"http://127.0.0.1:50054",
CODE_VERSION,
DEVICE_TYPE.as_str_name().to_lowercase(),
)
.await?;
- let login_start_response = identity_client
+ let response = identity_client
.login_password_user_start(login_start_request)
- .await?
- .into_inner();
+ .await?;
+
+ // We need to get the load balancer cookie from from the response and send it
+ // in the subsequent request to ensure it is routed to the same identity
+ // service instance as the first request
+ let cookie = response
+ .metadata()
+ .get(RESPONSE_METADATA_COOKIE_KEY)
+ .cloned();
+
+ let login_start_response = response.into_inner();
let opaque_login_upload = client_login
.finish(&login_start_response.opaque_login_response)
.map_err(handle_error)?;
+
let login_finish_request = OpaqueLoginFinishRequest {
session_id: login_start_response.session_id,
opaque_login_upload,
};
+ let mut finish_request = Request::new(login_finish_request);
+
+ // Cookie won't be available in local dev environments
+ if let Some(cookie_metadata) = cookie {
+ finish_request
+ .metadata_mut()
+ .insert(REQUEST_METADATA_COOKIE_KEY, cookie_metadata);
+ }
+
let login_finish_response = identity_client
- .login_password_user_finish(login_finish_request)
+ .login_password_user_finish(finish_request)
.await?
.into_inner();
let user_id_and_access_token = UserIDAndDeviceAccessToken {
user_id: login_finish_response.user_id,
access_token: login_finish_response.access_token,
};
Ok(serde_json::to_string(&user_id_and_access_token)?)
}
struct WalletUserInfo {
siwe_message: String,
siwe_signature: String,
key_payload: String,
key_payload_signature: String,
content_prekey: String,
content_prekey_signature: String,
notif_prekey: String,
notif_prekey_signature: String,
content_one_time_keys: Vec<String>,
notif_one_time_keys: Vec<String>,
social_proof: String,
}
#[instrument]
fn login_wallet_user(
siwe_message: String,
siwe_signature: String,
key_payload: String,
key_payload_signature: String,
content_prekey: String,
content_prekey_signature: String,
notif_prekey: String,
notif_prekey_signature: String,
content_one_time_keys: Vec<String>,
notif_one_time_keys: Vec<String>,
social_proof: String,
promise_id: u32,
) {
RUNTIME.spawn(async move {
let wallet_user_info = WalletUserInfo {
siwe_message,
siwe_signature,
key_payload,
key_payload_signature,
content_prekey,
content_prekey_signature,
notif_prekey,
notif_prekey_signature,
content_one_time_keys,
notif_one_time_keys,
social_proof,
};
let result = login_wallet_user_helper(wallet_user_info).await;
handle_string_result_as_callback(result, promise_id);
});
}
async fn login_wallet_user_helper(
wallet_user_info: WalletUserInfo,
) -> Result<String, Error> {
let login_request = WalletLoginRequest {
siwe_message: wallet_user_info.siwe_message,
siwe_signature: wallet_user_info.siwe_signature,
device_key_upload: Some(DeviceKeyUpload {
device_key_info: Some(IdentityKeyInfo {
payload: wallet_user_info.key_payload,
payload_signature: wallet_user_info.key_payload_signature,
social_proof: Some(wallet_user_info.social_proof),
}),
content_upload: Some(PreKey {
pre_key: wallet_user_info.content_prekey,
pre_key_signature: wallet_user_info.content_prekey_signature,
}),
notif_upload: Some(PreKey {
pre_key: wallet_user_info.notif_prekey,
pre_key_signature: wallet_user_info.notif_prekey_signature,
}),
one_time_content_prekeys: wallet_user_info.content_one_time_keys,
one_time_notif_prekeys: wallet_user_info.notif_one_time_keys,
device_type: DEVICE_TYPE.into(),
}),
};
let mut identity_client = get_unauthenticated_client(
"http://127.0.0.1:50054",
CODE_VERSION,
DEVICE_TYPE.as_str_name().to_lowercase(),
)
.await?;
let login_response = identity_client
.login_wallet_user(login_request)
.await?
.into_inner();
let user_id_and_access_token = UserIDAndDeviceAccessToken {
user_id: login_response.user_id,
access_token: login_response.access_token,
};
Ok(serde_json::to_string(&user_id_and_access_token)?)
}
struct UpdatePasswordInfo {
user_id: String,
device_id: String,
access_token: String,
password: String,
}
fn update_user_password(
user_id: String,
device_id: String,
access_token: String,
password: String,
promise_id: u32,
) {
RUNTIME.spawn(async move {
let update_password_info = UpdatePasswordInfo {
access_token,
user_id,
device_id,
password,
};
let result = update_user_password_helper(update_password_info).await;
handle_void_result_as_callback(result, promise_id);
});
}
async fn update_user_password_helper(
update_password_info: UpdatePasswordInfo,
) -> Result<(), Error> {
let mut client_registration = Registration::new();
let opaque_registration_request = client_registration
.start(&update_password_info.password)
.map_err(handle_error)?;
let update_password_start_request = UpdateUserPasswordStartRequest {
opaque_registration_request,
};
let mut identity_client = get_auth_client(
"http://127.0.0.1:50054",
update_password_info.user_id,
update_password_info.device_id,
update_password_info.access_token,
CODE_VERSION,
DEVICE_TYPE.as_str_name().to_lowercase(),
)
.await?;
- let update_password_start_response = identity_client
+ let response = identity_client
.update_user_password_start(update_password_start_request)
- .await?
- .into_inner();
+ .await?;
+
+ // We need to get the load balancer cookie from from the response and send it
+ // in the subsequent request to ensure it is routed to the same identity
+ // service instance as the first request
+ let cookie = response
+ .metadata()
+ .get(RESPONSE_METADATA_COOKIE_KEY)
+ .cloned();
+
+ let update_password_start_response = response.into_inner();
let opaque_registration_upload = client_registration
.finish(
&update_password_info.password,
&update_password_start_response.opaque_registration_response,
)
.map_err(handle_error)?;
+
let update_password_finish_request = UpdateUserPasswordFinishRequest {
session_id: update_password_start_response.session_id,
opaque_registration_upload,
};
+ let mut finish_request = Request::new(update_password_finish_request);
+
+ // Cookie won't be available in local dev environments
+ if let Some(cookie_metadata) = cookie {
+ finish_request
+ .metadata_mut()
+ .insert(REQUEST_METADATA_COOKIE_KEY, cookie_metadata);
+ }
+
identity_client
- .update_user_password_finish(update_password_finish_request)
+ .update_user_password_finish(finish_request)
.await?;
Ok(())
}
fn delete_user(
user_id: String,
device_id: String,
access_token: String,
promise_id: u32,
) {
RUNTIME.spawn(async move {
let auth_info = AuthInfo {
access_token,
user_id,
device_id,
};
let result = delete_user_helper(auth_info).await;
handle_void_result_as_callback(result, promise_id);
});
}
async fn delete_user_helper(auth_info: AuthInfo) -> Result<(), Error> {
let mut identity_client = get_auth_client(
"http://127.0.0.1:50054",
auth_info.user_id,
auth_info.device_id,
auth_info.access_token,
CODE_VERSION,
DEVICE_TYPE.as_str_name().to_lowercase(),
)
.await?;
identity_client.delete_user(Empty {}).await?;
Ok(())
}
struct GetOutboundKeysRequestInfo {
identifier_type: String,
identifier_value: String,
device_id: String,
}
// This struct should not be altered without also updating
// OutboundKeyInfoResponse in lib/types/identity-service-types.js
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct OutboundKeyInfoResponse {
pub payload: String,
pub payload_signature: String,
pub social_proof: Option<String>,
pub content_prekey: String,
pub content_prekey_signature: String,
pub notif_prekey: String,
pub notif_prekey_signature: String,
pub one_time_content_prekey: Option<String>,
pub one_time_notif_prekey: Option<String>,
}
impl TryFrom<OutboundKeyInfo> for OutboundKeyInfoResponse {
type Error = Error;
fn try_from(key_info: OutboundKeyInfo) -> Result<Self, Error> {
let identity_info =
key_info.identity_info.ok_or(Error::MissingResponseData)?;
let IdentityKeyInfo {
payload,
payload_signature,
social_proof,
} = identity_info;
let content_prekey =
key_info.content_prekey.ok_or(Error::MissingResponseData)?;
let PreKey {
pre_key: content_prekey_value,
pre_key_signature: content_prekey_signature,
} = content_prekey;
let notif_prekey =
key_info.notif_prekey.ok_or(Error::MissingResponseData)?;
let PreKey {
pre_key: notif_prekey_value,
pre_key_signature: notif_prekey_signature,
} = notif_prekey;
let one_time_content_prekey = key_info.one_time_content_prekey;
let one_time_notif_prekey = key_info.one_time_notif_prekey;
Ok(Self {
payload,
payload_signature,
social_proof,
content_prekey: content_prekey_value,
content_prekey_signature,
notif_prekey: notif_prekey_value,
notif_prekey_signature,
one_time_content_prekey,
one_time_notif_prekey,
})
}
}
fn get_outbound_keys_for_user_device(
identifier_type: String,
identifier_value: String,
device_id: String,
promise_id: u32,
) {
RUNTIME.spawn(async move {
let get_outbound_keys_request_info = GetOutboundKeysRequestInfo {
identifier_type,
identifier_value,
device_id,
};
let result =
get_outbound_keys_for_user_device_helper(get_outbound_keys_request_info)
.await;
handle_string_result_as_callback(result, promise_id);
});
}
async fn get_outbound_keys_for_user_device_helper(
get_outbound_keys_request_info: GetOutboundKeysRequestInfo,
) -> Result<String, Error> {
let identifier = match get_outbound_keys_request_info.identifier_type.as_str()
{
"walletAddress" => Some(Identifier::WalletAddress(
get_outbound_keys_request_info.identifier_value,
)),
"username" => Some(Identifier::Username(
get_outbound_keys_request_info.identifier_value,
)),
_ => {
return Err(Error::TonicGRPC(tonic::Status::invalid_argument(
"invalid identifier",
)))
}
};
let mut identity_client = get_unauthenticated_client(
"http://127.0.0.1:50054",
CODE_VERSION,
DEVICE_TYPE.as_str_name().to_lowercase(),
)
.await?;
let mut response = identity_client
.get_outbound_keys_for_user(OutboundKeysForUserRequest { identifier })
.await?
.into_inner();
let outbound_key_info = OutboundKeyInfoResponse::try_from(
response
.devices
.remove(&get_outbound_keys_request_info.device_id)
.ok_or(Error::MissingResponseData)?,
)?;
Ok(serde_json::to_string(&outbound_key_info)?)
}
#[derive(
Debug, derive_more::Display, derive_more::From, derive_more::Error,
)]
pub enum Error {
#[display(...)]
TonicGRPC(Status),
#[display(...)]
SerdeJson(serde_json::Error),
#[display(...)]
MissingResponseData,
GRPClient(grpc_clients::error::Error),
}
#[cfg(test)]
mod tests {
use super::CODE_VERSION;
#[test]
fn test_code_version_exists() {
assert!(CODE_VERSION > 0);
}
}
diff --git a/shared/grpc_clients/src/identity/mod.rs b/shared/grpc_clients/src/identity/mod.rs
index 747f99be1..da3565952 100644
--- a/shared/grpc_clients/src/identity/mod.rs
+++ b/shared/grpc_clients/src/identity/mod.rs
@@ -1,20 +1,21 @@
pub mod authenticated;
pub mod device;
pub mod shared;
pub mod unauthenticated;
pub mod protos {
// This must be named client for authenticated generated code
pub mod client {
tonic::include_proto!("identity.client");
}
pub use client as unauthenticated;
pub mod authenticated {
tonic::include_proto!("identity.authenticated");
}
}
pub use authenticated::get_auth_client;
pub use device::DeviceType;
+pub use shared::{REQUEST_METADATA_COOKIE_KEY, RESPONSE_METADATA_COOKIE_KEY};
pub use unauthenticated::get_unauthenticated_client;
diff --git a/shared/grpc_clients/src/identity/shared.rs b/shared/grpc_clients/src/identity/shared.rs
index f74e13ab0..fad5d27b5 100644
--- a/shared/grpc_clients/src/identity/shared.rs
+++ b/shared/grpc_clients/src/identity/shared.rs
@@ -1,57 +1,60 @@
use tonic::{
metadata::{errors::InvalidMetadataValue, Ascii, MetadataValue},
service::Interceptor,
Request, Status,
};
+pub const RESPONSE_METADATA_COOKIE_KEY: &str = "set-cookie";
+pub const REQUEST_METADATA_COOKIE_KEY: &str = "cookie";
+
pub struct CodeVersionLayer {
pub(crate) version: u64,
pub(crate) device_type: String,
}
impl Interceptor for CodeVersionLayer {
fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
let metadata = request.metadata_mut();
metadata.insert("code_version", self.version.parse_to_ascii()?);
metadata.insert("device_type", self.device_type.parse_to_ascii()?);
Ok(request)
}
}
pub trait ToMetadataValueAscii {
fn parse_to_ascii(&self) -> Result<MetadataValue<Ascii>, Status>;
}
impl ToMetadataValueAscii for u64 {
fn parse_to_ascii(&self) -> Result<MetadataValue<Ascii>, Status> {
let ascii_string = self.to_string();
ascii_string.parse().map_err(|e: InvalidMetadataValue| {
Status::invalid_argument(format!(
"Non-Ascii character present in metadata value: {}",
e
))
})
}
}
pub struct ChainedInterceptor<A, B>
where
A: Interceptor + Send + Sync + 'static,
B: Interceptor + Send + Sync + 'static,
{
pub(crate) first: A,
pub(crate) second: B,
}
impl<A, B> Interceptor for ChainedInterceptor<A, B>
where
A: Interceptor + Send + Sync + 'static,
B: Interceptor + Send + Sync + 'static,
{
fn call(&mut self, request: Request<()>) -> Result<Request<()>, Status> {
let request = self.first.call(request)?;
self.second.call(request)
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 23, 4:59 AM (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2560020
Default Alt Text
(41 KB)

Event Timeline