From 3a78a771a8df6d446801f27dd10e3a8f88cdc376 Mon Sep 17 00:00:00 2001 From: Donovan Date: Fri, 19 Dec 2025 18:05:45 -0600 Subject: [PATCH] testing client app --- src/client/app.rs | 111 +++++++++++++++++------------ src/client/client_auth_api.rs | 38 +++++++--- src/client/mod.rs | 2 +- src/client/opaque.rs | 4 +- src/client/repo.rs | 51 ++++++++++++- src/client/states.rs | 5 +- src/shared/opaque.rs | 2 +- tests/in_memory_client_app_test.rs | 49 +++++++++++++ 8 files changed, 200 insertions(+), 62 deletions(-) create mode 100644 tests/in_memory_client_app_test.rs diff --git a/src/client/app.rs b/src/client/app.rs index c365d18..4070a59 100644 --- a/src/client/app.rs +++ b/src/client/app.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::marker::PhantomData; use crate::client::client_auth_api::ClientAuth; use crate::client::opaque::{ServerConnectionLogin, ServerConnectionRegister}; @@ -9,14 +8,15 @@ use crate::shared::email::Email; use crate::shared::opaque::UserSecretKey; use crate::shared::user_api::UserAPI; -struct ClientApp<'a, State, R, U, C> +pub struct ClientApp<'a,'b ,State, K,C, U, R> where - R: ServerConnectionRegister + ServerConnectionLogin, + K: ServerConnectionRegister + ServerConnectionLogin, + C: ServerConnectionRegister + ServerConnectionLogin, U: UserAPI, - C: ClientRepo + R: ClientRepo { - auth_api: ClientAuth<'a, R, U>, - client_repo: C, + auth_api: ClientAuth<'a,'b ,K,C, U>, + client_repo: R, code_reg: Option, key_login: Option>, code_login: Option, @@ -25,13 +25,14 @@ where -impl <'a,State, R, U, C>ClientApp<'a,State, R, U, C> +impl <'a,'b ,State, K,C, U, R>ClientApp<'a,'b ,State, K,C, U, R> where - R: ServerConnectionRegister + ServerConnectionLogin, + K: ServerConnectionRegister + ServerConnectionLogin, + C: ServerConnectionRegister + ServerConnectionLogin, U: UserAPI, - C: ClientRepo, + R: ClientRepo, { - pub fn new(auth_api: ClientAuth<'a, R, U>, client_repo: C) -> Self { + pub fn new(auth_api: ClientAuth<'a,'b , K,C, U>, client_repo: R) -> Self { Self { auth_api, client_repo, @@ -41,7 +42,8 @@ where _state: PhantomData } } - fn into_state(self) -> ClientApp<'a, NextState, R, U, C> { + + fn into_state(self) -> ClientApp<'a,'b, NextState, K,C, U, R> { ClientApp { auth_api: self.auth_api, client_repo: self.client_repo, @@ -54,13 +56,14 @@ where } pub struct NewUserRegisterKey; -impl <'a, R, U, C>ClientApp<'a, NewUserRegisterKey, R, U, C> +impl <'a,'b, K, C, U, R> ClientApp<'a,'b, NewUserRegisterKey, K,C, U, R> where - R: ServerConnectionRegister + ServerConnectionLogin, + K: ServerConnectionRegister + ServerConnectionLogin, + C: ServerConnectionRegister + ServerConnectionLogin, U: UserAPI, - C: ClientRepo, + R: ClientRepo, { - async fn new_user(mut self, email: Email, secret_key: UserSecretKey) -> Result, String> { + pub async fn new_user(mut self, email: Email, secret_key: UserSecretKey) -> Result, String> { let key_register = UserStateKey::::new(email.clone(), secret_key.clone()); self.client_repo.add_secret_key(email.clone(), secret_key.clone()).await?; let key_login = match key_register.register(&self.auth_api).await.map_err(|e| format!("error: {e:?}")) { @@ -71,35 +74,22 @@ where } }; let key_logged_in = key_login.login(&self.auth_api).await.map_err(|e| format!("error: {e:?}"))?; - let code_register = key_logged_in.register_code(&self.auth_api).await?; + let code_register = key_logged_in.to_register_code(&self.auth_api).await?; self.code_reg = Some(code_register); Ok(self.into_state::()) } } -pub struct UserKeyLogin; -impl <'a, R, U, C>ClientApp<'a, UserKeyLogin, R, U, C> -where - R: ServerConnectionRegister + ServerConnectionLogin, - U: UserAPI, - C: ClientRepo, -{ - async fn login(mut self, email: Email, secret_key: UserSecretKey) -> Result<(), String> { - let key_login = UserStateKey::::new(email, secret_key); - self.key_login = Some(key_login); - let key_log_in = self.into_state::(); - todo!() - } -} - pub struct NewUserRegisterCode; -impl <'a, R, U, C>ClientApp<'a, NewUserRegisterCode, R, U, C> + +impl <'a,'b, K, C, U, R> ClientApp<'a,'b, NewUserRegisterCode, K,C, U, R> where - R: ServerConnectionRegister + ServerConnectionLogin, + K: ServerConnectionRegister + ServerConnectionLogin, + C: ServerConnectionRegister + ServerConnectionLogin, U: UserAPI, - C: ClientRepo, + R: ClientRepo, { - async fn get_new_user_icons(&self) -> Result, String> { + pub async fn get_new_user_icons(&self) -> Result, String> { Ok( self.code_reg .clone() @@ -108,18 +98,49 @@ where ) } - async fn new_user_register_code(mut self, email: Email, selected_icons: Vec) -> Result, String> { + pub async fn new_user_register_code(mut self, selected_icons: Vec) -> Result, String> { let code_register = self.code_reg.ok_or("no code register".to_string())?; - let code_login = code_register.register(&self.auth_api,selected_icons).await?; - todo!() - // Ok() + self.code_reg = None; + self.code_login = Some(code_register.clone().register(&self.auth_api,selected_icons).await?); + Ok(self.into_state::()) } } -// async fn user_login(&mut self, email: Email, selected_keys: &Vec) -> Result<(), String> { -// let mut code_login = self.user_login.remove(&email).unwrap(); // Todo should be an error -// let code_logged_in = code_login.login(&self.auth_api, &selected_keys).await?; -// self.client_repo.add_code_logged_in(&email, code_logged_in).await?; -// Ok(()) -// } +pub struct UserKeyLogin; + +impl <'a,'b, K, C, U, R> ClientApp<'a,'b, UserKeyLogin, K,C, U, R> +where + K: ServerConnectionRegister + ServerConnectionLogin, + C: ServerConnectionRegister + ServerConnectionLogin, + U: UserAPI, + R: ClientRepo, +{ + pub async fn login(mut self, email: Email, secret_key: UserSecretKey) -> Result, String> { + self.code_login = Some( + UserStateKey::::new(email, secret_key) + .login(&self.auth_api).await? + .to_code_login(&self.auth_api).await? + ); + Ok(self.into_state::()) + } +} + +pub struct UserCodeLogin; +impl <'a,'b, K, C, U, R> ClientApp<'a,'b, UserCodeLogin, K,C, U, R> +where + K: ServerConnectionRegister + ServerConnectionLogin, + C: ServerConnectionRegister + ServerConnectionLogin, + U: UserAPI, + R: ClientRepo, +{ + pub async fn get_keypad(&self) -> Result>, String> { + let code_login = self.code_login.clone().ok_or("no code login")?; + Ok(code_login.get_keypad().await) + } + + pub async fn login(self, key_selection: Vec) -> Result<(), String> { + let code_login = self.code_login.unwrap().login(&self.auth_api, &key_selection).await?; + self.client_repo.add_code_logged_in(code_login.0.email.clone(), code_login).await + } +} diff --git a/src/client/client_auth_api.rs b/src/client/client_auth_api.rs index a8216d5..4a0da78 100644 --- a/src/client/client_auth_api.rs +++ b/src/client/client_auth_api.rs @@ -9,22 +9,42 @@ use crate::client::opaque::{OpaqueAuthData, OpaqueAuth, ServerConnectionLogin, S use crate::shared::signed_session_data::SignedSessionData; use crate::shared::user_api::UserAPI; -pub struct ClientAuth<'a, R, U> +pub struct ClientAuth<'a,'b, K,C, U> where - R: ServerConnectionRegister + ServerConnectionLogin, + K: ServerConnectionRegister + ServerConnectionLogin, + C: ServerConnectionRegister + ServerConnectionLogin, U: UserAPI { - opaque_key_register: OpaqueAuth<'a, R>, - opaque_key_login: OpaqueAuth<'a, R>, - opaque_code_register: OpaqueAuth<'a, R>, - opaque_code_login: OpaqueAuth<'a, R>, + opaque_key_register: OpaqueAuth<'a, K>, + opaque_key_login: OpaqueAuth<'a, K>, + opaque_code_register: OpaqueAuth<'b, C>, + opaque_code_login: OpaqueAuth<'b, C>, user_api: U } -#[async_trait] -impl<'a, R, U> AuthAPI for ClientAuth<'a, R, U> +impl<'a,'b, K,C, U> ClientAuth<'a,'b, K,C, U> where - R: ServerConnectionRegister + ServerConnectionLogin, + K: ServerConnectionRegister + ServerConnectionLogin, + C: ServerConnectionRegister + ServerConnectionLogin, + U: UserAPI, +{ + pub fn new(key_connection: &'a K, code_connection: &'b C, user_api: U) -> Self { + Self{ + opaque_key_register: OpaqueAuth::new(key_connection), + opaque_key_login: OpaqueAuth::new(key_connection), + opaque_code_login: OpaqueAuth::new(code_connection), + opaque_code_register: OpaqueAuth::new(code_connection), + user_api + } + } +} + + +#[async_trait] +impl<'a,'b, K,C, U> AuthAPI for ClientAuth<'a,'b, K,C, U> +where + K: ServerConnectionRegister + ServerConnectionLogin, + C: ServerConnectionRegister + ServerConnectionLogin, U: UserAPI, { async fn register_key(&self, email: &Email, secret_key: &UserSecretKey) -> Result<(), String> { diff --git a/src/client/mod.rs b/src/client/mod.rs index d02dd81..c36b3c0 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -2,4 +2,4 @@ pub mod client_auth_api; pub mod opaque; pub mod states; pub mod app; -mod repo; \ No newline at end of file +pub mod repo; \ No newline at end of file diff --git a/src/client/opaque.rs b/src/client/opaque.rs index a1c5ba6..d1762aa 100644 --- a/src/client/opaque.rs +++ b/src/client/opaque.rs @@ -87,8 +87,8 @@ pub trait ServerConnectionLogin: Send + Sync { pub struct OpaqueAuth<'a, S>(&'a S); impl<'a, S> OpaqueAuth<'a, S> { - pub fn new(server: &'a S) -> Self { - Self(server) + pub fn new(server_connection: &'a S) -> Self { + Self(server_connection) } } diff --git a/src/client/repo.rs b/src/client/repo.rs index 40e8f8a..c576818 100644 --- a/src/client/repo.rs +++ b/src/client/repo.rs @@ -2,16 +2,63 @@ use async_trait::async_trait; use crate::shared::email::Email; use crate::shared::models::app::CodeLoggedInSession; use crate::shared::opaque::UserSecretKey; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; #[async_trait] pub trait ClientRepo { async fn add_secret_key(&self, email: Email, user_secret_key: UserSecretKey) -> Result<(), String>; async fn remove_secret_key(&self, email: &Email) -> Result<(), String>; - async fn add_code_logged_in(&self, email: &Email, login_session: CodeLoggedInSession) -> Result<(), String>; + async fn add_code_logged_in(&self, email: Email, login_session: CodeLoggedInSession) -> Result<(), String>; + async fn get_code_logged_in(&self, email: &Email) -> Result; } -// TODO: Implement in memory repo +#[derive(Debug, Default)] +pub struct InMemoryClientRepo { + secret_keys: RwLock>, + code_logged_in: RwLock>, +} + +impl InMemoryClientRepo { + pub fn new() -> Self { + Self::default() + } + + /// Convenience if you want to share it across services. + pub fn shared() -> Arc { + Arc::new(Self::new()) + } +} + +#[async_trait] +impl ClientRepo for InMemoryClientRepo { + async fn add_secret_key(&self, email: Email, user_secret_key: UserSecretKey) -> Result<(), String> { + let mut map = self.secret_keys.write().await; + map.insert(email, user_secret_key); + Ok(()) + } + + async fn remove_secret_key(&self, email: &Email) -> Result<(), String> { + let mut map = self.secret_keys.write().await; + map.remove(email); + Ok(()) + } + + async fn add_code_logged_in(&self, email: Email, login_session: CodeLoggedInSession) -> Result<(), String> { + let mut map = self.code_logged_in.write().await; + map.insert(email, login_session); + Ok(()) + } + + async fn get_code_logged_in(&self, email: &Email) -> Result { + let map = self.code_logged_in.read().await; + map.get(email) + .cloned() + .ok_or_else(|| "code_logged_in session not found".to_string()) + } +} // TODO: Implement flutter-storage // https://chatgpt.com/c/69441737-c990-8333-9737-7ac75232da1d \ No newline at end of file diff --git a/src/client/states.rs b/src/client/states.rs index c578ac3..73d0bbe 100644 --- a/src/client/states.rs +++ b/src/client/states.rs @@ -53,7 +53,7 @@ pub struct UserStateKeyLoggedIn { } impl UserStateKeyLoggedIn { - pub async fn register_code(self, api: &dyn AuthAPI) -> Result { + pub async fn to_register_code(self, api: &dyn AuthAPI) -> Result { let icon_nonce = Nonce::new(); let icons = self.get_icons(api, &icon_nonce).await?; Ok(UserStateCodeRegister { @@ -77,7 +77,7 @@ impl UserStateKeyLoggedIn { Ok(icons) } - pub async fn login_code(self, api: &dyn AuthAPI) -> Result { + pub async fn to_code_login(self, api: &dyn AuthAPI) -> Result { let login_data = api.get_login_data(&self.key_login).await?; let icons = self.get_icons(api, &login_data.icon_nonce).await?; let policy = api.get_policy().await?; @@ -144,6 +144,7 @@ impl UserStateCodeRegister { } } +#[derive(Clone)] pub struct UserStateCodeLogin { email: Email, mask: Vec, diff --git a/src/shared/opaque.rs b/src/shared/opaque.rs index 727563b..70b4701 100644 --- a/src/shared/opaque.rs +++ b/src/shared/opaque.rs @@ -13,7 +13,7 @@ use rand::rngs::OsRng; const USER_KEY_SIZE: usize = 16; -#[derive(Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct UserSecretKey(Zeroizing<[u8; USER_KEY_SIZE]>); impl UserSecretKey { diff --git a/tests/in_memory_client_app_test.rs b/tests/in_memory_client_app_test.rs new file mode 100644 index 0000000..fcd4608 --- /dev/null +++ b/tests/in_memory_client_app_test.rs @@ -0,0 +1,49 @@ +use opaque_ke::argon2::password_hash::rand_core::OsRng; +use nkode_protocol::client::app::{ClientApp, NewUserRegisterKey}; +use nkode_protocol::client::client_auth_api::ClientAuth; +use nkode_protocol::client::repo::InMemoryClientRepo; +use nkode_protocol::server::app::ServerApp; +use nkode_protocol::server::repository::in_memory::in_memory_opaque_db::InMemoryOpaqueDB; +use nkode_protocol::server::repository::in_memory::in_memory_opaque_session::InMemoryOpaqueSession; +use nkode_protocol::server::repository::in_memory::in_memory_transport::{InMemoryCodeServer, InMemoryKeyServer, InMemoryServerTransport}; +use nkode_protocol::server::repository::in_memory::in_memory_user_db::InMemoryUserDB; +use nkode_protocol::shared::email::Email; +use nkode_protocol::shared::models::app::IconID; +use nkode_protocol::shared::opaque::{NKodeServerSetup, UserSecretKey}; + +#[tokio::test] +async fn in_memory_client_app() { + + let mut rng = OsRng; + let server_setup = NKodeServerSetup::new(&mut rng); + let server = ServerApp::new( + server_setup, + InMemoryOpaqueDB::new(), + InMemoryOpaqueSession::new(), + InMemoryUserDB::new() + ); + let key_transport: InMemoryKeyServer = InMemoryServerTransport::new(&server); + let code_transport: InMemoryCodeServer = InMemoryServerTransport::new(&server); + let user_db = InMemoryUserDB::new(); + let client_auth = ClientAuth::new(&key_transport, &code_transport, user_db); + let client_repo = InMemoryClientRepo::new(); + let client_app_new_user: ClientApp< + '_, + '_, + NewUserRegisterKey, + InMemoryServerTransport<'_, _>, + InMemoryServerTransport<'_, _>, + _, + _ + > = ClientApp::new(client_auth, client_repo); + let user_email = Email::new("a@b.com").unwrap(); + let user_secret_key = UserSecretKey::new(); + let client_app_register_code = client_app_new_user.new_user(user_email, user_secret_key).await.unwrap(); + let _: Vec = client_app_register_code + .get_new_user_icons().await.unwrap() + .iter() + .map( + |el| el.id().clone() + ).collect(); + // let client_app_user_login = client_app_register_code.new_user_register_code(icons[0..4].to_vec()).await.unwrap(); +}