Lines
87.27 %
Functions
60 %
Branches
100 %
//! User-domain natives. Wraps `server::command::VerifyUserPassword` only;
//! account-creation/registration is reserved for the auth-flow path, not the
//! eval channel.
//!
//! `verify-user-password` is exposed verbatim from the server command —
//! takes (email, password), returns the matching UUID on success or nil
//! on bad creds. The session is already authenticated by the time forms
//! reach the eval channel, so this native exists for "confirm current
//! identity before sensitive action" workflows the emacs client may
//! drive (e.g. re-prompting on session-reauth UX).
use scripting::runtime::{alloc_string_ref, read_string_arg};
use server::command::CmdResult;
use server::command::user::VerifyUserPassword;
use wasmtime::{ArrayRef, Caller, Linker, Rooted};
use crate::session::SessionData;
pub const REGISTERED_COMMANDS: &[&str] = &["verify-user-password"];
pub fn register(linker: &mut Linker<SessionData>) -> wasmtime::Result<()> {
linker.func_wrap_async(
"nomi",
"user_verify_user_password",
|mut caller: Caller<'_, SessionData>,
(email_arg, password_arg): (Option<Rooted<ArrayRef>>, Option<Rooted<ArrayRef>>)|
-> Box<
dyn std::future::Future<Output = wasmtime::Result<Option<Rooted<ArrayRef>>>> + Send,
> {
Box::new(async move {
let email = read_string_arg(&mut caller, email_arg)?;
let password = read_string_arg(&mut caller, password_arg)?;
match run_verify_user_password(email, password).await? {
Some(id) => Ok(Some(alloc_string_ref(&mut caller, id.as_bytes())?)),
None => Ok(None),
}
})
},
)?;
Ok(())
/// On success returns `Some(<uuid-string>)`; bad credentials surface as
/// `Ok(None)` (null `ref null $i8_array` on the wire). Validation/runtime
/// failures trap via `wasmtime::Error` and the rpc envelope renders them
/// as `:error` records.
async fn run_verify_user_password(
email_arg: Option<String>,
password_arg: Option<String>,
) -> wasmtime::Result<Option<String>> {
let email = email_arg
.filter(|s| !s.is_empty())
.ok_or_else(|| wasmtime::Error::msg("verify-user-password: missing or empty :email arg"))?;
let password = password_arg
.ok_or_else(|| wasmtime::Error::msg("verify-user-password: missing :password arg"))?;
match VerifyUserPassword::new()
.email(email)
.password(password)
.run()
.await
{
Ok(Some(CmdResult::Uuid(id))) => Ok(Some(id.to_string())),
Ok(Some(CmdResult::Bool(false))) | Ok(None) => Ok(None),
Ok(Some(other)) => Err(wasmtime::Error::msg(format!(
"verify-user-password: unexpected variant {other:?}"
))),
Err(err) => Err(wasmtime::Error::msg(format!("verify-user-password: {err}"))),
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn run_verify_user_password_missing_email_emits_error() {
let err = run_verify_user_password(None, Some("pw".into()))
.unwrap_err();
assert!(err.to_string().contains(":email"), "got: {err}");
async fn run_verify_user_password_empty_email_emits_error() {
let err = run_verify_user_password(Some(String::new()), Some("pw".into()))
async fn run_verify_user_password_missing_password_emits_error() {
let err = run_verify_user_password(Some("u@example.com".into()), None)
assert!(err.to_string().contains(":password"), "got: {err}");