feat(sdk): OPAQUE auth module with state persistence

This commit is contained in:
2026-03-04 12:39:33 +01:00
parent 011ff541bb
commit 67983c7a40
5 changed files with 442 additions and 0 deletions

View File

@@ -104,6 +104,66 @@ impl QpqClient {
.ok_or(SdkError::NotConnected)
}
/// Register a new user account via OPAQUE.
///
/// Generates a fresh identity keypair, registers it with the server, and
/// stores the identity key locally.
pub async fn register(&mut self, username: &str, password: &str) -> Result<(), SdkError> {
let rpc = self.rpc.as_ref().ok_or(SdkError::NotConnected)?;
let keypair = crate::auth::opaque_register(rpc, username, password, None).await?;
self.identity_key = Some(keypair.public_key_bytes().to_vec());
self.emit(ClientEvent::Registered {
username: username.to_string(),
});
info!(username, "registered");
Ok(())
}
/// Log in via OPAQUE and store the session token.
///
/// Requires an identity key to be set (either from a previous `register()`
/// call or loaded from state). After login, the client is authenticated
/// and subsequent RPC calls include the session token.
pub async fn login(&mut self, username: &str, password: &str) -> Result<(), SdkError> {
let identity_key = self
.identity_key
.as_ref()
.ok_or_else(|| SdkError::AuthFailed("no identity key — register or load state first".into()))?
.clone();
let rpc = self.rpc.as_ref().ok_or(SdkError::NotConnected)?;
let session_token = crate::auth::opaque_login(rpc, username, password, &identity_key).await?;
self.session_token = Some(session_token);
self.username = Some(username.to_string());
self.emit(ClientEvent::LoggedIn {
username: username.to_string(),
});
info!(username, "logged in");
Ok(())
}
/// Clear authentication state (session token, username).
pub fn logout(&mut self) -> Result<(), SdkError> {
self.session_token = None;
let username = self.username.take();
self.emit(ClientEvent::LoggedOut {
username: username.unwrap_or_default(),
});
info!("logged out");
Ok(())
}
/// Set the identity key directly (e.g. after loading from state).
pub fn set_identity_key(&mut self, key: Vec<u8>) {
self.identity_key = Some(key);
}
/// Get the session token, if authenticated.
pub fn session_token(&self) -> Option<&[u8]> {
self.session_token.as_deref()
}
/// Disconnect from the server.
pub fn disconnect(&mut self) {
if let Some(rpc) = self.rpc.take() {