//! OPAQUE auth handlers — registration and login. use std::sync::Arc; use bytes::Bytes; use prost::Message; use quicprochat_proto::qpc::v1; use quicprochat_rpc::error::RpcStatus; use quicprochat_rpc::method::{HandlerResult, RequestContext}; use crate::auth::{PendingLogin, SessionInfo, SESSION_TTL_SECS}; use crate::domain::auth::AuthService; use crate::domain::types::{RegisterFinishReq, RegisterStartReq}; use super::ServerState; pub async fn handle_opaque_register_start( state: Arc, ctx: RequestContext, ) -> HandlerResult { let req = match v1::OpaqueRegisterStartRequest::decode(ctx.payload) { Ok(r) => r, Err(e) => return HandlerResult::err(RpcStatus::BadRequest, &format!("decode: {e}")), }; if req.username.is_empty() { return HandlerResult::err(RpcStatus::BadRequest, "username must not be empty"); } let svc = AuthService { store: Arc::clone(&state.store), opaque_setup: Arc::clone(&state.opaque_setup), pending_logins: Arc::clone(&state.pending_logins), sessions: Arc::clone(&state.sessions), auth_cfg: Arc::clone(&state.auth_cfg), }; let domain_req = RegisterStartReq { username: req.username, request_bytes: req.request, }; match svc.register_start(domain_req) { Ok(resp) => { let proto = v1::OpaqueRegisterStartResponse { response: resp.response_bytes, }; HandlerResult::ok(Bytes::from(proto.encode_to_vec())) } Err(e) => HandlerResult::err(RpcStatus::Internal, &format!("register_start: {e}")), } } pub async fn handle_opaque_register_finish( state: Arc, ctx: RequestContext, ) -> HandlerResult { let req = match v1::OpaqueRegisterFinishRequest::decode(ctx.payload) { Ok(r) => r, Err(e) => return HandlerResult::err(RpcStatus::BadRequest, &format!("decode: {e}")), }; if req.username.is_empty() { return HandlerResult::err(RpcStatus::BadRequest, "username must not be empty"); } let svc = AuthService { store: Arc::clone(&state.store), opaque_setup: Arc::clone(&state.opaque_setup), pending_logins: Arc::clone(&state.pending_logins), sessions: Arc::clone(&state.sessions), auth_cfg: Arc::clone(&state.auth_cfg), }; let domain_req = RegisterFinishReq { username: req.username.clone(), upload_bytes: req.upload, identity_key: req.identity_key.clone(), }; match svc.register_finish(domain_req) { Ok(resp) => { state .hooks .on_user_registered(&req.username, &req.identity_key); let proto = v1::OpaqueRegisterFinishResponse { success: resp.success, }; HandlerResult::ok(Bytes::from(proto.encode_to_vec())) } Err(e) => HandlerResult::err(RpcStatus::Internal, &format!("register_finish: {e}")), } } pub async fn handle_opaque_login_start( state: Arc, ctx: RequestContext, ) -> HandlerResult { let req = match v1::OpaqueLoginStartRequest::decode(ctx.payload) { Ok(r) => r, Err(e) => return HandlerResult::err(RpcStatus::BadRequest, &format!("decode: {e}")), }; if req.username.is_empty() { return HandlerResult::err(RpcStatus::BadRequest, "username must not be empty"); } // Look up user record. let user_record = match state.store.get_user_record(&req.username) { Ok(Some(r)) => r, Ok(None) => { return HandlerResult::err(RpcStatus::NotFound, "user not found"); } Err(e) => return HandlerResult::err(RpcStatus::Internal, &format!("store: {e}")), }; // Deserialise stored registration. let registration = match opaque_ke::ServerRegistration::::deserialize(&user_record) { Ok(r) => r, Err(e) => { return HandlerResult::err( RpcStatus::Internal, &format!("corrupt user record: {e}"), ) } }; // Start login. let credential_request = match opaque_ke::CredentialRequest::::deserialize(&req.request) { Ok(r) => r, Err(e) => { return HandlerResult::err(RpcStatus::BadRequest, &format!("bad login request: {e}")) } }; let login_start = match opaque_ke::ServerLogin::< quicprochat_core::opaque_auth::OpaqueSuite, >::start( &mut rand::rngs::OsRng, &state.opaque_setup, Some(registration), credential_request, req.username.as_bytes(), Default::default(), ) { Ok(r) => r, Err(e) => { return HandlerResult::err(RpcStatus::Internal, &format!("login start: {e}")); } }; let response_bytes = login_start.message.serialize().to_vec(); // Store pending login state. let now = crate::auth::current_timestamp(); state.pending_logins.insert( req.username.clone(), PendingLogin { state_bytes: login_start.state.serialize().to_vec(), created_at: now, }, ); let proto = v1::OpaqueLoginStartResponse { response: response_bytes, }; HandlerResult::ok(Bytes::from(proto.encode_to_vec())) } pub async fn handle_opaque_login_finish( state: Arc, ctx: RequestContext, ) -> HandlerResult { let req = match v1::OpaqueLoginFinishRequest::decode(ctx.payload) { Ok(r) => r, Err(e) => return HandlerResult::err(RpcStatus::BadRequest, &format!("decode: {e}")), }; if req.username.is_empty() { return HandlerResult::err(RpcStatus::BadRequest, "username must not be empty"); } // Retrieve pending login state. let pending = match state.pending_logins.remove(&req.username) { Some((_, p)) => p, None => { return HandlerResult::err( RpcStatus::BadRequest, "no pending login for this username", ); } }; let login_state = match opaque_ke::ServerLogin::< quicprochat_core::opaque_auth::OpaqueSuite, >::deserialize(&pending.state_bytes) { Ok(s) => s, Err(e) => { return HandlerResult::err( RpcStatus::Internal, &format!("corrupt pending login: {e}"), ) } }; let finalization = match opaque_ke::CredentialFinalization::< quicprochat_core::opaque_auth::OpaqueSuite, >::deserialize(&req.finalization) { Ok(f) => f, Err(e) => { return HandlerResult::err(RpcStatus::BadRequest, &format!("bad finalization: {e}")); } }; if let Err(e) = login_state.finish(finalization, Default::default()) { state.hooks.on_auth(&crate::hooks::AuthEvent { username: req.username.clone(), success: false, failure_reason: format!("{e}"), }); return HandlerResult::err(RpcStatus::Unauthorized, &format!("login failed: {e}")); } // Generate session token. let mut token = vec![0u8; 32]; rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut token); let now = crate::auth::current_timestamp(); state.sessions.insert( token.clone(), SessionInfo { username: req.username.clone(), identity_key: req.identity_key.clone(), created_at: now, expires_at: now + SESSION_TTL_SECS, }, ); state.hooks.on_auth(&crate::hooks::AuthEvent { username: req.username, success: true, failure_reason: String::new(), }); let proto = v1::OpaqueLoginFinishResponse { session_token: token, }; HandlerResult::ok(Bytes::from(proto.encode_to_vec())) }