@@ -370,6 +389,11 @@ pub fn App() -> impl IntoView {
"rustkvm"
+
+
+ "No password is set. Anyone on this network can connect, control this machine and open a root shell on the device. After connecting, set a password in ⚙ Settings → Password."
+
+
bool {
}
}
+/// Whether the device runs without a password (`authMode: "noPassword"`), in
+/// which case anyone who can reach the web UI gets full control.
+pub async fn no_password() -> bool {
+ match Request::get("/device").credentials(RequestCredentials::Include).send().await {
+ Ok(resp) if resp.ok() => resp
+ .json::()
+ .await
+ .ok()
+ .and_then(|v| v.get("authMode").and_then(|m| m.as_str().map(|m| m == "noPassword")))
+ .unwrap_or(false),
+ _ => false,
+ }
+}
+
pub async fn is_setup() -> bool {
match Request::get("/device/status").credentials(RequestCredentials::Include).send().await {
Ok(resp) => resp
@@ -36,6 +50,18 @@ pub async fn setup(mode: &str, password: Option<&str>) -> Result<(), String> {
if resp.ok() { Ok(()) } else { Err(format!("setup HTTP {}", resp.status())) }
}
+/// Sets the first password on a device in no-password mode.
+pub async fn set_password(password: &str) -> Result<(), String> {
+ let resp = Request::post("/auth/password-local")
+ .credentials(RequestCredentials::Include)
+ .json(&json!({ "password": password }))
+ .map_err(|e| format!("set-password serialize: {e}"))?
+ .send()
+ .await
+ .map_err(|e| format!("set-password request failed: {e}"))?;
+ if resp.ok() { Ok(()) } else { Err(format!("set-password HTTP {}", resp.status())) }
+}
+
pub async fn change_password(old: &str, new: &str) -> Result<(), String> {
let resp = Request::put("/auth/password-local")
.credentials(RequestCredentials::Include)
diff --git a/crates/rkvm-web/src/settings.rs b/crates/rkvm-web/src/settings.rs
index 1149ff9..d8d68e9 100644
--- a/crates/rkvm-web/src/settings.rs
+++ b/crates/rkvm-web/src/settings.rs
@@ -28,6 +28,7 @@ pub fn Settings(
open: RwSignal,
rpc: StoredValue>, LocalStorage>,
peer: StoredValue >, LocalStorage>,
+ no_password: RwSignal,
) -> impl IntoView {
let codec = RwSignal::new(String::new());
let rotation = RwSignal::new(String::new());
@@ -385,10 +386,17 @@ pub fn Settings(
pw_msg.set("New password too short".to_string());
return;
}
+ let first = no_password.get_untracked();
spawn_local(async move {
- match rest::change_password(&o, &n).await {
+ let result = if first {
+ rest::set_password(&n).await
+ } else {
+ rest::change_password(&o, &n).await
+ };
+ match result {
Ok(()) => {
- pw_msg.set("Password updated".to_string());
+ no_password.set(false);
+ pw_msg.set(if first { "Password set" } else { "Password updated" }.to_string());
old_pw.set(String::new());
new_pw.set(String::new());
}
@@ -470,20 +478,24 @@ pub fn Settings(
diff --git a/crates/rkvm-web/src/terminal.rs b/crates/rkvm-web/src/terminal.rs
index d041ef6..0dd0a12 100644
--- a/crates/rkvm-web/src/terminal.rs
+++ b/crates/rkvm-web/src/terminal.rs
@@ -169,7 +169,7 @@ pub fn Terminal(
- "Serial / Terminal"
+ "Device shell (root)"
"✕"