Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3333701
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
41 KB
Referenced Files
None
Subscribers
None
View Options
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,
®istration_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,
®istration_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
Details
Attached
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)
Attached To
Mode
rCOMM Comm
Attached
Detach File
Event Timeline
Log In to Comment