refactor mutibility

This commit is contained in:
2025-12-17 17:57:42 -06:00
parent ac2ddf86df
commit fe499add9e
8 changed files with 64 additions and 126 deletions

View File

@@ -23,7 +23,7 @@ impl<R: OpaqueDatabaseRepo, S: OpaqueSessionRepo, U: UserRepo> ServerApp<R, S, U
} }
pub async fn reg_start<K: CredKind>( pub async fn reg_start<K: CredKind>(
&mut self, &self,
identifier: &[u8], identifier: &[u8],
request: RegistrationRequest<NKodeCipherSuite>, request: RegistrationRequest<NKodeCipherSuite>,
) -> Result<OpaqueRegisterSession, String> { ) -> Result<OpaqueRegisterSession, String> {
@@ -42,7 +42,7 @@ impl<R: OpaqueDatabaseRepo, S: OpaqueSessionRepo, U: UserRepo> ServerApp<R, S, U
} }
pub async fn reg_finish<K: CredKind>( pub async fn reg_finish<K: CredKind>(
&mut self, &self,
session_id: &Uuid, session_id: &Uuid,
password_file: PasswordFile, password_file: PasswordFile,
) -> Result<(), String> { ) -> Result<(), String> {
@@ -51,7 +51,7 @@ impl<R: OpaqueDatabaseRepo, S: OpaqueSessionRepo, U: UserRepo> ServerApp<R, S, U
.map_err(|e| format!("get reg session: {e}"))?; .map_err(|e| format!("get reg session: {e}"))?;
K::prereq_for_register(&self.opaque_db, sess.identifier.as_slice()).await K::prereq_for_register(&self.opaque_db, sess.identifier.as_slice()).await
.map_err(|e| format!("registration prereq failed: {e:?}"))?; .map_err(|e| format!("registration prereq failed: {e:?}"))?;
K::set_password_file(&mut self.opaque_db, sess.identifier.as_slice(), password_file).await K::set_password_file(&self.opaque_db, sess.identifier.as_slice(), password_file).await
.map_err(|e| format!("repo write: {e:?}"))?; .map_err(|e| format!("repo write: {e:?}"))?;
self.opaque_sess self.opaque_sess
.clear_reg_session(session_id).await .clear_reg_session(session_id).await
@@ -59,7 +59,7 @@ impl<R: OpaqueDatabaseRepo, S: OpaqueSessionRepo, U: UserRepo> ServerApp<R, S, U
} }
pub async fn login_start<K: CredKind>( pub async fn login_start<K: CredKind>(
&mut self, &self,
identifier: &[u8], identifier: &[u8],
request: CredentialRequest<NKodeCipherSuite>, request: CredentialRequest<NKodeCipherSuite>,
) -> Result<OpaqueLoginSession, String> { ) -> Result<OpaqueLoginSession, String> {
@@ -85,7 +85,7 @@ impl<R: OpaqueDatabaseRepo, S: OpaqueSessionRepo, U: UserRepo> ServerApp<R, S, U
} }
pub async fn login_finish( pub async fn login_finish(
&mut self, &self,
session_id: &Uuid, session_id: &Uuid,
finalize: CredentialFinalization<NKodeCipherSuite>, finalize: CredentialFinalization<NKodeCipherSuite>,
) -> Result<LoggedInSession, String> { ) -> Result<LoggedInSession, String> {
@@ -108,39 +108,39 @@ impl<R: OpaqueDatabaseRepo, S: OpaqueSessionRepo, U: UserRepo> ServerApp<R, S, U
} }
pub async fn key_login_finish( pub async fn key_login_finish(
&mut self, &self,
session_id: &Uuid, session_id: &Uuid,
finalize: CredentialFinalization<NKodeCipherSuite>, finalize: CredentialFinalization<NKodeCipherSuite>,
) -> Result<KeyLoggedInSession, String> { ) -> Result<KeyLoggedInSession, String> {
let session = KeyLoggedInSession(self.login_finish(session_id, finalize).await?); let session = KeyLoggedInSession(self.login_finish(session_id, finalize).await?);
self.user_db.set_key_session(session.clone())?; self.user_db.set_key_session(session.clone()).await?;
Ok(session) Ok(session)
} }
pub async fn code_login_finish( pub async fn code_login_finish(
&mut self, &self,
session_id: &Uuid, session_id: &Uuid,
finalize: CredentialFinalization<NKodeCipherSuite>, finalize: CredentialFinalization<NKodeCipherSuite>,
) -> Result<CodeLoggedInSession, String> { ) -> Result<CodeLoggedInSession, String> {
let session = CodeLoggedInSession(self.login_finish(session_id, finalize).await?); let session = CodeLoggedInSession(self.login_finish(session_id, finalize).await?);
self.user_db.set_code_session(session.clone())?; self.user_db.set_code_session(session.clone()).await?;
Ok(session) Ok(session)
} }
pub async fn get_key_session(&mut self, session_id: &Uuid) -> Result<KeyLoggedInSession, String> { pub async fn get_key_session(&self, session_id: &Uuid) -> Result<KeyLoggedInSession, String> {
self.user_db.get_key_session(session_id) self.user_db.get_key_session(session_id).await
} }
pub async fn get_code_session(&mut self, session_id: &Uuid) -> Result<CodeLoggedInSession, String> { pub async fn get_code_session(&self, session_id: &Uuid) -> Result<CodeLoggedInSession, String> {
self.user_db.get_code_session(session_id) self.user_db.get_code_session(session_id).await
} }
pub async fn get_code_login_data(&mut self, email: &Email) -> Result<CodeLoginData, String> { pub async fn get_code_login_data(&self, email: &Email) -> Result<CodeLoginData, String> {
self.user_db.get_code_login_data(email) self.user_db.get_code_login_data(email).await
} }
pub async fn set_code_login_data(&mut self, email: Email, data: CodeLoginData) -> Result<(), String> { pub async fn set_code_login_data(&self, email: Email, data: CodeLoginData) -> Result<(), String> {
self.user_db.set_code_login_data(email, data) self.user_db.set_code_login_data(email, data).await
} }
} }
@@ -158,7 +158,7 @@ impl CredKind for Key {
async fn get_password_file<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> Result<PasswordFile, AuthRepoError> { async fn get_password_file<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> Result<PasswordFile, AuthRepoError> {
repo.get_key_passcode_file(id).await repo.get_key_passcode_file(id).await
} }
async fn set_password_file<R: OpaqueDatabaseRepo>(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError> { async fn set_password_file<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError> {
repo.new_key(id, pf).await repo.new_key(id, pf).await
} }
} }
@@ -171,7 +171,7 @@ impl CredKind for Code {
async fn get_password_file<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> Result<PasswordFile, AuthRepoError> { async fn get_password_file<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> Result<PasswordFile, AuthRepoError> {
repo.get_code_passcode_file(id).await repo.get_code_passcode_file(id).await
} }
async fn set_password_file<R: OpaqueDatabaseRepo>(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError> { async fn set_password_file<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError> {
repo.new_code(id, pf).await repo.new_code(id, pf).await
} }
async fn prereq_for_register<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> Result<(), AuthRepoError> { async fn prereq_for_register<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> Result<(), AuthRepoError> {

View File

@@ -19,7 +19,7 @@ pub struct LoginCache {
pub trait CredKind { pub trait CredKind {
async fn has<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> bool; async fn has<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> bool;
async fn get_password_file<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> Result<PasswordFile, AuthRepoError>; async fn get_password_file<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> Result<PasswordFile, AuthRepoError>;
async fn set_password_file<R: OpaqueDatabaseRepo>(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError>; async fn set_password_file<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError>;
async fn prereq_for_register<R: OpaqueDatabaseRepo>(_repo: &R, _id: &[u8]) -> Result<(), AuthRepoError> { async fn prereq_for_register<R: OpaqueDatabaseRepo>(_repo: &R, _id: &[u8]) -> Result<(), AuthRepoError> {
Ok(()) Ok(())
} }

View File

@@ -1,7 +1,5 @@
use async_trait::async_trait; use async_trait::async_trait;
use std::marker::PhantomData; use std::marker::PhantomData;
use tokio::sync::Mutex;
use std::sync::Arc;
use uuid::Uuid; use uuid::Uuid;
use opaque_ke::{CredentialFinalization, CredentialRequest, RegistrationRequest}; use opaque_ke::{CredentialFinalization, CredentialRequest, RegistrationRequest};
use crate::shared::models::opaque::{NKodeCipherSuite, NKodeServerSetup, OpaqueLoginSession, OpaqueRegisterSession, PasswordFile}; use crate::shared::models::opaque::{NKodeCipherSuite, NKodeServerSetup, OpaqueLoginSession, OpaqueRegisterSession, PasswordFile};
@@ -14,23 +12,20 @@ use crate::server::repository::in_memory::in_memory_opaque_session::InMemoryOpaq
use crate::server::repository::in_memory::in_memory_user_db::InMemoryUserDB; use crate::server::repository::in_memory::in_memory_user_db::InMemoryUserDB;
use crate::shared::models::app::LoggedInSession; use crate::shared::models::app::LoggedInSession;
#[derive(Clone)]
pub struct InMemoryServer<K: CredKind> { pub struct InMemoryServer<K: CredKind> {
auth_db: Arc<Mutex<ServerApp<InMemoryOpaqueDB, InMemoryOpaqueSession, InMemoryUserDB>>>, auth_db: ServerApp<InMemoryOpaqueDB, InMemoryOpaqueSession, InMemoryUserDB>,
_kind: PhantomData<K>, _kind: PhantomData<K>,
} }
impl<K: CredKind> InMemoryServer<K> { impl<K: CredKind> InMemoryServer<K> {
pub fn new(server_setup: NKodeServerSetup) -> Self { pub fn new(server_setup: NKodeServerSetup) -> Self {
Self { Self {
auth_db: Arc::new(Mutex::new( auth_db: ServerApp::new(
ServerApp::new(
server_setup, server_setup,
InMemoryOpaqueDB::new(), InMemoryOpaqueDB::new(),
InMemoryOpaqueSession::new(), InMemoryOpaqueSession::new(),
InMemoryUserDB::new() InMemoryUserDB::new()
) ),
)),
_kind: PhantomData, _kind: PhantomData,
} }
} }
@@ -51,7 +46,7 @@ where
) -> Result<OpaqueRegisterSession, ClientAuthError> { ) -> Result<OpaqueRegisterSession, ClientAuthError> {
// Server API takes ownership; client trait gives us a reference. // Server API takes ownership; client trait gives us a reference.
// opaque-ke request types are typically Clone; if not, you'll need to adjust signatures. // opaque-ke request types are typically Clone; if not, you'll need to adjust signatures.
self.auth_db.lock().await self.auth_db
.reg_start::<K>(identifier, message.clone()) .reg_start::<K>(identifier, message.clone())
.await .await
.map_err(|e| ClientAuthError::Transport(e)) .map_err(|e| ClientAuthError::Transport(e))
@@ -62,8 +57,7 @@ where
session_id: &Uuid, session_id: &Uuid,
password_file: PasswordFile, password_file: PasswordFile,
) -> Result<(), ClientAuthError> { ) -> Result<(), ClientAuthError> {
self.auth_db.lock().await self.auth_db.reg_finish::<K>(session_id, password_file)
.reg_finish::<K>(session_id, password_file)
.await .await
.map_err(|e| ClientAuthError::Transport(e)) .map_err(|e| ClientAuthError::Transport(e))
} }
@@ -79,7 +73,7 @@ where
identifier: &[u8], identifier: &[u8],
request: &CredentialRequest<NKodeCipherSuite>, request: &CredentialRequest<NKodeCipherSuite>,
) -> Result<OpaqueLoginSession, ClientAuthError> { ) -> Result<OpaqueLoginSession, ClientAuthError> {
self.auth_db.lock().await self.auth_db
.login_start::<K>(identifier, request.clone()) .login_start::<K>(identifier, request.clone())
.await .await
.map_err(|e| ClientAuthError::Transport(e)) .map_err(|e| ClientAuthError::Transport(e))
@@ -91,7 +85,7 @@ where
message: &CredentialFinalization<NKodeCipherSuite>, message: &CredentialFinalization<NKodeCipherSuite>,
) -> Result<LoggedInSession, ClientAuthError> { ) -> Result<LoggedInSession, ClientAuthError> {
Ok(self Ok(self
.auth_db.lock().await .auth_db
.login_finish(session_id, message.clone()) .login_finish(session_id, message.clone())
.await .await
.map_err(|e| ClientAuthError::Transport(e))? .map_err(|e| ClientAuthError::Transport(e))?

View File

@@ -1,21 +1,24 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use tokio::sync::Mutex;
use uuid::Uuid; use uuid::Uuid;
use crate::server::repository::user_repo::UserRepo; use crate::server::repository::user_repo::UserRepo;
use crate::shared::models::app::{CodeLoggedInSession, CodeLoginData, KeyLoggedInSession}; use crate::shared::models::app::{CodeLoggedInSession, CodeLoginData, KeyLoggedInSession};
use crate::shared::models::email::Email; use crate::shared::models::email::Email;
pub struct InMemoryUserDB { pub struct InMemoryUserDB {
key_session: HashMap<Uuid, KeyLoggedInSession>, key_session: Arc<Mutex<HashMap<Uuid, KeyLoggedInSession>>>,
code_session: HashMap<Uuid, CodeLoggedInSession>, code_session: Arc<Mutex<HashMap<Uuid, CodeLoggedInSession>>>,
code_data: HashMap<Email, CodeLoginData>, code_data: Arc<Mutex<HashMap<Email, CodeLoginData>>>,
} }
impl InMemoryUserDB { impl InMemoryUserDB {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
key_session: HashMap::new(), key_session: Arc::new(Mutex::new(HashMap::new())),
code_session: HashMap::new(), code_session: Arc::new(Mutex::new(HashMap::new())),
code_data: HashMap::new(), code_data: Arc::new(Mutex::new(HashMap::new())),
} }
} }
} }
@@ -26,40 +29,39 @@ impl Default for InMemoryUserDB {
} }
} }
#[async_trait]
impl UserRepo for InMemoryUserDB { impl UserRepo for InMemoryUserDB {
fn get_key_session(&mut self, session_id: &Uuid) -> Result<KeyLoggedInSession, String> { async fn get_key_session(&self, session_id: &Uuid) -> Result<KeyLoggedInSession, String> {
self.key_session self.key_session.lock().await
.get(&session_id) .get(&session_id)
.cloned() .cloned()
.ok_or_else(|| format!("key session not found for session_id={}", session_id)) .ok_or_else(|| format!("key session not found for session_id={}", session_id))
} }
fn get_code_session(&mut self, session_id: &Uuid) -> Result<CodeLoggedInSession, String> { async fn get_code_session(&self, session_id: &Uuid) -> Result<CodeLoggedInSession, String> {
self.code_session self.code_session.lock().await
.get(&session_id) .get(&session_id)
.cloned() .cloned()
.ok_or_else(|| format!("code session not found for session_id={}", session_id)) .ok_or_else(|| format!("code session not found for session_id={}", session_id))
} }
fn set_key_session(&mut self, session: KeyLoggedInSession) -> Result<(), String> { async fn set_key_session(&self, session: KeyLoggedInSession) -> Result<(), String> {
// Assumes KeyLoggedInSession has a session_id: Uuid field (common pattern) self.key_session.lock().await.insert(session.0.session_id, session);
self.key_session.insert(session.0.session_id, session);
Ok(()) Ok(())
} }
fn set_code_session(&mut self, session: CodeLoggedInSession) -> Result<(), String> { async fn set_code_session(&self, session: CodeLoggedInSession) -> Result<(), String> {
// Assumes CodeLoggedInSession has a session_id: Uuid field (common pattern) self.code_session.lock().await.insert(session.0.session_id, session);
self.code_session.insert(session.0.session_id, session);
Ok(()) Ok(())
} }
fn set_code_login_data(&mut self, email: Email, data: CodeLoginData) -> Result<(), String> { async fn set_code_login_data(&self, email: Email, data: CodeLoginData) -> Result<(), String> {
self.code_data.insert(email, data); self.code_data.lock().await.insert(email, data);
Ok(()) Ok(())
} }
fn get_code_login_data(&mut self, email: &Email) -> Result<CodeLoginData, String> { async fn get_code_login_data(&self, email: &Email) -> Result<CodeLoginData, String> {
self.code_data self.code_data.lock().await
.get(email) .get(email)
.cloned() .cloned()
.ok_or_else(|| "code login data not found for email".to_string()) .ok_or_else(|| "code login data not found for email".to_string())

View File

@@ -1,14 +1,16 @@
use async_trait::async_trait;
use uuid::Uuid; use uuid::Uuid;
use crate::shared::models::app::{CodeLoggedInSession, CodeLoginData, KeyLoggedInSession}; use crate::shared::models::app::{CodeLoggedInSession, CodeLoginData, KeyLoggedInSession};
use crate::shared::models::email::Email; use crate::shared::models::email::Email;
#[async_trait]
pub trait UserRepo { pub trait UserRepo {
fn get_key_session(&mut self, session_id: &Uuid) -> Result<KeyLoggedInSession, String>; async fn get_key_session(&self, session_id: &Uuid) -> Result<KeyLoggedInSession, String>;
fn get_code_session(&mut self, session_id: &Uuid) -> Result<CodeLoggedInSession, String>; async fn get_code_session(&self, session_id: &Uuid) -> Result<CodeLoggedInSession, String>;
fn set_key_session(&mut self, session: KeyLoggedInSession) -> Result<(), String>; async fn set_key_session(&self, session: KeyLoggedInSession) -> Result<(), String>;
fn set_code_session(&mut self, session: CodeLoggedInSession) -> Result<(), String>; async fn set_code_session(&self, session: CodeLoggedInSession) -> Result<(), String>;
fn set_code_login_data(&mut self, email: Email, data: CodeLoginData) -> Result<(), String>; async fn set_code_login_data(&self, email: Email, data: CodeLoginData) -> Result<(), String>;
fn get_code_login_data(&mut self, email: &Email) -> Result<CodeLoginData, String>; async fn get_code_login_data(&self, email: &Email) -> Result<CodeLoginData, String>;
} }

View File

@@ -8,7 +8,6 @@ use uuid::Uuid;
use crate::shared::models::email::Email; use crate::shared::models::email::Email;
use crate::shared::models::opaque::{OpaqueSessionKey, UserSecretKey}; use crate::shared::models::opaque::{OpaqueSessionKey, UserSecretKey};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct LoggedInSession { pub struct LoggedInSession {
pub(crate) session_id: Uuid, pub(crate) session_id: Uuid,
@@ -73,4 +72,3 @@ pub trait AuthAPI {
async fn is_code_registered(&self, key_login_session: &KeyLoggedInSession) -> Result<bool, String>; async fn is_code_registered(&self, key_login_session: &KeyLoggedInSession) -> Result<bool, String>;
async fn get_policy(&self) -> Result<NKodePolicy, String>; async fn get_policy(&self) -> Result<NKodePolicy, String>;
} }

View File

@@ -1,5 +1,4 @@
use std::{fmt, str::FromStr}; use std::fmt;
use email_address::{EmailAddress, Options}; use email_address::{EmailAddress, Options};
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -18,11 +17,8 @@ impl Email {
if s.is_empty() { if s.is_empty() {
return Err(EmailError::Empty); return Err(EmailError::Empty);
} }
// Strict "address only" validation (no "Name <a@b.com>")
EmailAddress::parse_with_options(s, Options::default().without_display_text()) EmailAddress::parse_with_options(s, Options::default().without_display_text())
.map_err(|_| EmailError::InvalidFormat)?; .map_err(|_| EmailError::InvalidFormat)?;
Ok(Self(s.to_owned())) Ok(Self(s.to_owned()))
} }
@@ -48,63 +44,8 @@ impl Email {
} }
} }
/* ---- std traits ---- */
impl fmt::Display for Email { impl fmt::Display for Email {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str()) f.write_str(self.as_str())
} }
} }
impl FromStr for Email {
type Err = EmailError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Email::new(s)
}
}
impl TryFrom<String> for Email {
type Error = EmailError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Email::new(value)
}
}
impl TryFrom<&str> for Email {
type Error = EmailError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Email::new(value)
}
}
impl TryFrom<&[u8]> for Email {
type Error = EmailError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Email::from_bytes(value)
}
}
impl TryFrom<Vec<u8>> for Email {
type Error = EmailError;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
Email::from_bytes(&value)
}
}
impl AsRef<str> for Email {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<[u8]> for Email {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl From<Email> for Vec<u8> {
fn from(value: Email) -> Self {
value.into_bytes()
}
}

View File

@@ -8,13 +8,14 @@ async fn opaque_key_registration_and_login_roundtrip() {
let mut rng = OsRng; let mut rng = OsRng;
let server_setup = NKodeServerSetup::new(&mut rng); let server_setup = NKodeServerSetup::new(&mut rng);
let server = InMemoryKeyServer::new(server_setup); let server = InMemoryKeyServer::new(server_setup);
let auth_reg = OpaqueAuthRegister::new(server.clone()); let auth_reg = OpaqueAuthRegister::new(server);
let auth_data = AuthenticationData::from_secret_key("a@b.com", b"supersecret16bytes"); let auth_data = AuthenticationData::from_secret_key("a@b.com", b"supersecret16bytes");
auth_reg.register(&auth_data).await.expect("registration should succeed"); auth_reg.register(&auth_data).await.expect("registration should succeed");
let login_reg = OpaqueAuthLogin::new(server); let login_reg = OpaqueAuthLogin::new(server);
let _ =login_reg.login(&auth_data) let _ =login_reg.login(&auth_data)
.await .await
.expect("login should succeed"); .expect("login should succeed");
// assert!(!session_key.is_empty());
} }
#[tokio::test] #[tokio::test]