Skip to content

Commit

Permalink
Add client registration crate
Browse files Browse the repository at this point in the history
  • Loading branch information
Pranay Tulugu authored and ptulugu committed Sep 20, 2023
1 parent 7534f23 commit f0cdfd0
Show file tree
Hide file tree
Showing 10 changed files with 350 additions and 27 deletions.
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 18 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,29 @@ authors = ["Stan Drozd <[email protected]>"]
edition = "2018"

[workspace]
members = ["althea_kernel_interface", "settings", "clu", "exit_db", "antenna_forwarding_client", "antenna_forwarding_protocol", "auto_bridge","rita_common","rita_exit","rita_client", "rita_bin", "test_runner", "integration_tests"]
members = [
"althea_kernel_interface",
"settings",
"clu",
"exit_db",
"antenna_forwarding_client",
"antenna_forwarding_protocol",
"auto_bridge",
"rita_common",
"rita_exit",
"rita_client",
"rita_client_registration",
"rita_bin",
"test_runner",
"integration_tests",
]

# Production relase profile, every trick is used to reduce binary size
[profile.release]
opt-level = "z"
strip = true
lto = true
codegen-units=1
codegen-units = 1
incremental = false

# testrunner should be fast to execute but also to compile
Expand All @@ -23,4 +38,4 @@ inherits = "dev"
opt-level = 2

[workspace.dependencies]
deep_space = {version = "2", features = ["althea"], default-features=false}
deep_space = { version = "2", features = ["althea"], default-features = false }
15 changes: 15 additions & 0 deletions rita_client_registration/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "rita_client_registration"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
lazy_static = "1.4"
althea_types = { path = "../althea_types" }
log = { version = "0.4", features = ["release_max_level_info"] }
serde = "1.0"
clarity = "1.2"
phonenumber = "0.3"
awc = "3.1"
28 changes: 28 additions & 0 deletions rita_client_registration/src/client_db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::net::IpAddr;

use althea_types::{Identity, WgKey};
use clarity::Address;

pub fn get_all_regsitered_clients() -> Vec<Identity> {
unimplemented!()
}

pub fn get_registered_client_using_wgkey(_key: WgKey) -> Option<Identity> {
unimplemented!()
}

pub fn get_registered_client_using_ethkey(_key: Address) -> Option<Identity> {
unimplemented!()
}

pub fn get_registered_client_using_meship(_ip: IpAddr) -> Option<Identity> {
unimplemented!()
}

pub fn get_clients_exit_cluster_list(_key: WgKey) -> Vec<Identity> {
unimplemented!()
}

pub fn add_client_to_registered_list(_c: Identity) {
unimplemented!()
}
269 changes: 269 additions & 0 deletions rita_client_registration/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
#![deny(unused_crate_dependencies)]
use std::{
collections::HashMap,
sync::{Arc, RwLock},
};

use althea_types::{ExitClientIdentity, WgKey};
use phonenumber::PhoneNumber;
use serde::{Deserialize, Serialize};

use crate::client_db::{
add_client_to_registered_list, get_registered_client_using_ethkey,
get_registered_client_using_meship, get_registered_client_using_wgkey,
};

#[macro_use]
extern crate log;
#[macro_use]
extern crate lazy_static;

pub mod client_db;

lazy_static! {
/// A map that stores number of texts sent to a client during registration
static ref TEXTS_SENT: Arc<RwLock<HashMap<WgKey, u8>>> = Arc::new(RwLock::new(HashMap::new()));
}

/// Return struct from check_text and Send Text. Verified indicates status from api http req,
/// bad phone number is an error parsing clients phone number
/// Internal server error is an error while querying api endpoint
enum TextApiError {
BadPhoneNumber,
InternalServerError { error: String },
}

/// Return struct from Registration server to exit
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ExitSignupReturn {
RegistrationOk,
PendingRegistration,
BadPhoneNumber,
InternalServerError { e: String },
}
// Lazy static setters and getters
fn increment_texts_sent(key: WgKey) {
let lock = &mut *TEXTS_SENT.write().unwrap();
let txt_sent = lock.get_mut(&key);
if let Some(val) = txt_sent {
*val += 1;
} else {
lock.insert(key, 1);
}
}

fn reset_texts_sent(key: WgKey) {
TEXTS_SENT.write().unwrap().remove(&key);
}

fn get_texts_sent(key: WgKey) -> u8 {
*TEXTS_SENT.read().unwrap().get(&key).unwrap_or(&0u8)
}

#[derive(Serialize)]
pub struct SmsCheck {
api_key: String,
verification_code: String,
phone_number: String,
country_code: String,
}

#[derive(Serialize)]
pub struct SmsRequest {
api_key: String,
via: String,
phone_number: String,
country_code: String,
}

/// True if there is any client with the same eth address, wg key, or ip address already registered
pub fn client_conflict(client: &ExitClientIdentity) -> bool {
// we can't possibly have a conflict if we have exactly this client already
// since client exists checks all major details this is safe and will return false
// if it's not exactly the same client
if client_exists(client) {
return false;
}
trace!("Checking if client exists");
let ip = client.global.mesh_ip;
let wg = client.global.wg_public_key;
let key = client.global.eth_address;

let ip_exists = get_registered_client_using_meship(ip).is_some();
let wg_exists = get_registered_client_using_wgkey(wg).is_some();
let eth_exists = get_registered_client_using_ethkey(key).is_some();

info!(
"Signup conflict ip {} eth {} wg {}",
ip_exists, eth_exists, wg_exists
);
ip_exists || eth_exists || wg_exists
}

fn client_exists(client: &ExitClientIdentity) -> bool {
trace!("Checking if client exists");
let c_id = get_registered_client_using_wgkey(client.global.wg_public_key);
match c_id {
Some(a) => client.global == a,
None => false,
}
}

/// Handles the minutia of phone registration states
pub async fn handle_sms_registration(
client: ExitClientIdentity,
api_key: String,
magic_number: Option<String>,
) -> ExitSignupReturn {
info!(
"Handling phone registration for {}",
client.global.wg_public_key
);

// Get magic phone number
let magic_phone_number = magic_number;

let text_num = get_texts_sent(client.global.wg_public_key);
let sent_more_than_allowed_texts = text_num > 10;

match (
client.reg_details.phone.clone(),
client.reg_details.phone_code.clone(),
sent_more_than_allowed_texts,
) {
// all texts exhausted, but they can still submit the correct code
(Some(number), Some(code), true) => {
let is_magic =
magic_phone_number.is_some() && magic_phone_number.unwrap() == number.clone();
let check_text = match check_text(number.clone(), code, api_key).await {
Ok(a) => a,
Err(e) => return return_api_error(e),
};
let result = is_magic || check_text;
if result {
info!(
"Phone registration complete for {}",
client.global.wg_public_key
);
add_client_to_registered_list(client.global);
reset_texts_sent(client.global.wg_public_key);
ExitSignupReturn::RegistrationOk
} else {
ExitSignupReturn::PendingRegistration
}
}
// user has exhausted attempts but is still not submitting code
(Some(_number), None, true) => ExitSignupReturn::PendingRegistration,
// user has attempts remaining and is requesting the code be resent
(Some(number), None, false) => {
if let Err(e) = send_text(number, api_key).await {
return return_api_error(e);
}
increment_texts_sent(client.global.wg_public_key);
ExitSignupReturn::PendingRegistration
}
// user has attempts remaining and is submitting a code
(Some(number), Some(code), false) => {
let is_magic =
magic_phone_number.is_some() && magic_phone_number.unwrap() == number.clone();
let check_text = match check_text(number.clone(), code, api_key).await {
Ok(a) => a,
Err(e) => return return_api_error(e),
};

let result = is_magic | check_text;
trace!("Check text returned {}", result);
if result {
info!(
"Phone registration complete for {}",
client.global.wg_public_key
);
add_client_to_registered_list(client.global);
reset_texts_sent(client.global.wg_public_key);
ExitSignupReturn::RegistrationOk
} else {
ExitSignupReturn::PendingRegistration
}
}
// user did not submit a phonenumber
(None, _, _) => ExitSignupReturn::BadPhoneNumber,
}
}

fn return_api_error(e: TextApiError) -> ExitSignupReturn {
match e {
TextApiError::BadPhoneNumber => ExitSignupReturn::BadPhoneNumber,
TextApiError::InternalServerError { error } => {
ExitSignupReturn::InternalServerError { e: error }
}
}
}

/// Posts to the validation endpoint with the code, will return success if the code
/// is the same as the one sent to the user
async fn check_text(number: String, code: String, api_key: String) -> Result<bool, TextApiError> {
trace!("About to check text message status for {}", number);
let number: PhoneNumber = match number.parse() {
Ok(number) => number,
Err(e) => {
error!("Phone parse error: {}", e);
return Err(TextApiError::BadPhoneNumber);
}
};
let url = "https://api.authy.com/protected/json/phones/verification/check";

let client = awc::Client::default();
let response = match client
.get(url)
.send_form(&SmsCheck {
api_key,
verification_code: code,
phone_number: number.national().to_string(),
country_code: number.code().value().to_string(),
})
.await
{
Ok(a) => a,
Err(e) => {
return Err(TextApiError::InternalServerError {
error: e.to_string(),
})
}
};

trace!("Got {} back from check text", response.status());
Ok(response.status().is_success())
}

/// Sends the authy verification text by hitting the api endpoint
async fn send_text(number: String, api_key: String) -> Result<(), TextApiError> {
info!("Sending message for {}", number);
let url = "https://api.authy.com/protected/json/phones/verification/start";
let number: PhoneNumber = match number.parse() {
Ok(number) => number,
Err(e) => {
error!("Parse phone number error {}", e);
return Err(TextApiError::BadPhoneNumber);
}
};

let client = awc::Client::default();
match client
.post(url)
.send_form(&SmsRequest {
api_key,
via: "sms".to_string(),
phone_number: number.national().to_string(),
country_code: number.code().value().to_string(),
})
.await
{
Ok(_a) => Ok(()),
Err(e) => {
error!("Send text error! {}", e);
Err(TextApiError::InternalServerError {
error: e.to_string(),
})
}
}
}
1 change: 1 addition & 0 deletions rita_exit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ license = "Apache-2.0"
sodiumoxide = "0.2"
num256 = "0.5"
rita_common = { path = "../rita_common" }
rita_client_registration = { path = "../rita_client_registration" }
althea_kernel_interface = { path = "../althea_kernel_interface" }
althea_types = { path = "../althea_types" }
settings = { path = "../settings" }
Expand Down
Loading

0 comments on commit f0cdfd0

Please sign in to comment.