chore: rename quicproquo → quicprochat in Rust workspace
Rename all crate directories, package names, binary names, proto package/module paths, ALPN strings, env var prefixes, config filenames, mDNS service names, and plugin ABI symbols from quicproquo/qpq to quicprochat/qpc.
This commit is contained in:
255
crates/quicprochat-server/src/v2_handlers/auth.rs
Normal file
255
crates/quicprochat-server/src/v2_handlers/auth.rs
Normal file
@@ -0,0 +1,255 @@
|
||||
//! 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<ServerState>,
|
||||
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<ServerState>,
|
||||
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<ServerState>,
|
||||
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::<quicprochat_core::opaque_auth::OpaqueSuite>::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::<quicprochat_core::opaque_auth::OpaqueSuite>::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<ServerState>,
|
||||
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()))
|
||||
}
|
||||
Reference in New Issue
Block a user