feat(sdk): OPAQUE auth module with state persistence
This commit is contained in:
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user