From 4a5ada0bb0e08778c71bc10b36eace419aef3265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hlusi=C4=8Dka?= Date: Sun, 8 Feb 2026 01:17:33 +0100 Subject: [PATCH] Fix password fields; show stored users on login page --- firmware/acid-firmware/src/ui/messages.rs | 1 + firmware/acid-firmware/src/ui/mod.rs | 143 +++++++++++++-------- firmware/acid-firmware/src/ui/storage.rs | 56 +++++++- firmware/acid-firmware/ui/login-view.slint | 8 +- firmware/acid-firmware/ui/main.slint | 3 +- 5 files changed, 151 insertions(+), 60 deletions(-) diff --git a/firmware/acid-firmware/src/ui/messages.rs b/firmware/acid-firmware/src/ui/messages.rs index a032ca8..c8e5d33 100644 --- a/firmware/acid-firmware/src/ui/messages.rs +++ b/firmware/acid-firmware/src/ui/messages.rs @@ -11,6 +11,7 @@ pub enum CallbackMessage { pub enum CallbackMessageLogin { PwAccepted { + user_index: i32, username: SharedString, password: SharedString, }, diff --git a/firmware/acid-firmware/src/ui/mod.rs b/firmware/acid-firmware/src/ui/mod.rs index f222e5f..bcc1024 100644 --- a/firmware/acid-firmware/src/ui/mod.rs +++ b/firmware/acid-firmware/src/ui/mod.rs @@ -5,9 +5,9 @@ use core::{cell::RefCell, ffi::CStr, ops::DerefMut}; use alloc::{boxed::Box, ffi::CString, format, rc::Rc, vec}; use embassy_time::Instant; use hex::FromHexError; -use log::{info, warn}; +use log::{error, info, warn}; use password_hash::Key; -use slint::SharedString; +use slint::{Model, ModelExt, ModelRc, SharedString, VecModel}; use spectre_api_sys::{SpectreAlgorithm, SpectreKeyID, SpectreUserKey}; #[cfg(feature = "limit-fps")] @@ -33,7 +33,17 @@ pub mod window_adapter; slint::include_modules!(); -fn spectre_derive_user_key(username: &CStr, password: &CStr) -> SpectreUserKey { +fn spectre_derive_user_key( + username: &CStr, + password: &CStr, + encrypted_key: Option, +) -> SpectreUserKey { + if let Some(encrypted_key) = encrypted_key { + critical_section::with(|cs| { + ACTIVE_ENCRYPTED_USER_KEY.borrow(cs).set(encrypted_key); + }); + } + let user_key_start = Instant::now(); unsafe { @@ -177,13 +187,16 @@ struct State { impl State { async fn new(db: AcidDatabase, main: AppWindow) -> Rc> { + let users = { + let users = Self::load_users(&db).await; + warn!("Users: {users:#?}"); + users + }; + let usernames = users.users.clone().map(|user| user.username); + let state = Rc::new(RefCell::new(State { window: main.clone_strong(), - users: { - let users = Self::load_users(&db).await; - warn!("Users: {users:#?}"); - users - }, + users, db: Rc::new(db), view: AppState::Login, state_login: Default::default(), @@ -195,7 +208,7 @@ impl State { main.on_enter_view({ let state = state.clone(); move |view| { - state.borrow_mut().set_view(view, true); + state.borrow_mut().set_view(view, true, true); } }); @@ -206,12 +219,18 @@ impl State { } }); + main.set_login_usernames(ModelRc::new(usernames)); + main.on_login_pw_accepted({ let state = state.clone(); - move |username, password| { + move |user_index, username, password| { State::process_callback_message( &state, - CallbackMessage::Login(CallbackMessageLogin::PwAccepted { username, password }), + CallbackMessage::Login(CallbackMessageLogin::PwAccepted { + user_index, + username, + password, + }), ); } }); @@ -306,17 +325,25 @@ impl State { } } - fn set_view(&mut self, view: AppState, reset: bool) { + fn reset_view(&mut self) { + match self.view { + AppState::Login => self.state_login = Default::default(), + AppState::Users => self.state_users = Default::default(), + AppState::UserEdit => self.state_user_edit = Default::default(), + AppState::UserSites => self.state_user_sites = Default::default(), + } + } + + fn set_view(&mut self, view: AppState, reset_source: bool, reset_target: bool) { + if reset_source { + self.reset_view(); + } + self.view = view; self.window.set_app_state(view); - if reset { - match self.view { - AppState::Login => self.state_login = Default::default(), - AppState::Users => self.state_users = Default::default(), - AppState::UserEdit => self.state_user_edit = Default::default(), - AppState::UserSites => self.state_user_sites = Default::default(), - } + if reset_target { + self.reset_view(); } } @@ -349,14 +376,22 @@ struct StateLogin {} impl AppViewTrait for StateLogin { fn process_callback_message(state_rc: &Rc>, message: CallbackMessage) { let mut state = state_rc.borrow_mut(); - if let CallbackMessage::Login(CallbackMessageLogin::PwAccepted { username, password }) = - message + if let CallbackMessage::Login(CallbackMessageLogin::PwAccepted { + user_index, + username: _, + password, + }) = message { + let Some(user) = state.users.users.row_data(user_index as usize) else { + error!("Failed to find a user with index {user_index}."); + return; + }; let username_c = - CString::new(&*username).expect("Username cannot be converted to a C string."); + CString::new(&*user.username).expect("Username cannot be converted to a C string."); let password_c = CString::new(&*password).expect("Password cannot be converted to a C string."); - let user_key = spectre_derive_user_key(&username_c, &password_c); + let user_key = + spectre_derive_user_key(&username_c, &password_c, Some(user.encrypted_key)); // let site_key_start = Instant::now(); // let site_key = &*spectre_api_sys::spectre_site_key( @@ -373,22 +408,16 @@ impl AppViewTrait for StateLogin { // ); // TODO: Erase memory before freeing // __spre_free(site_key as *const _ as *mut _); - let Some(user) = state - .users - .users - .iter() - .find(|user| *user.username == *username) - else { - return; - }; if user.key_id == user_key.keyID.bytes { - info!("Correct password entered for user {username:?}."); - state.state_user_sites = Some(StateUserSites { username, user_key }); - state.set_view(AppState::UserSites, true); + info!("Correct password entered for user {:?}.", user.username); + state.state_user_sites = Some(StateUserSites { + username: user.username, + user_key, + }); + state.set_view(AppState::UserSites, true, false); } else { - warn!("Incorrect password entered for user {username:?}."); - // TODO: Clear the input + warn!("Incorrect password entered for user {:?}.", user.username); } } } @@ -402,7 +431,7 @@ impl AppViewTrait for StateUsers { let mut state = state_rc.borrow_mut(); match message { CallbackMessage::Escape => { - state.set_view(AppState::Login, false); + state.set_view(AppState::Login, true, false); } CallbackMessage::Users(CallbackMessageUsers::EditUser { username, new }) => { state.state_user_edit = StateUserEdit { @@ -412,7 +441,7 @@ impl AppViewTrait for StateUsers { encrypted_key: None, }; state.window.set_user_edit_username(username); - state.set_view(AppState::UserEdit, false); + state.set_view(AppState::UserEdit, true, false); } _ => (), } @@ -433,7 +462,7 @@ impl AppViewTrait for StateUserEdit { let mut state = state.borrow_mut(); match message { CallbackMessage::Escape => { - state.set_view(AppState::Users, false); + state.set_view(AppState::Users, true, false); } CallbackMessage::UserEdit(CallbackMessageUserEdit::ComputeIdenticon { password }) => { let username_c = CString::new(&*state.state_user_edit.username) @@ -493,14 +522,11 @@ impl AppViewTrait for StateUserEdit { return; } - critical_section::with(|cs| { - ACTIVE_ENCRYPTED_USER_KEY.borrow(cs).set(key); - }); let username_c = CString::new(&*state.state_user_edit.username) .expect("Username cannot be converted to a C string."); let password_c = CString::new(&**password).expect("Password cannot be converted to a C string."); - let user_key = spectre_derive_user_key(&username_c, &password_c); + let user_key = spectre_derive_user_key(&username_c, &password_c, Some(key)); state.window.set_user_edit_key_error(SharedString::new()); state.window.set_user_edit_key_id( @@ -526,17 +552,30 @@ impl AppViewTrait for StateUserEdit { warn!("Encrypted key is not set."); return; }; + // If a user with that username already exists, overwrite it. - let username = state.state_user_edit.username.clone(); - state - .users - .users - .retain(|user| &*user.username != &*username); - state.users.users.push(SpectreUserConfig { - username, + let user = SpectreUserConfig { + username: state.state_user_edit.username.clone(), encrypted_key, key_id, - }); + }; + + let mut existing_index = None; + + for index in 0..state.users.users.row_count() { + if let Some(current_user) = state.users.users.row_data(index) { + if current_user.username == user.username { + existing_index = Some(index); + } + } + } + + if let Some(existing_index) = existing_index { + state.users.users.set_row_data(existing_index, user); + } else { + state.users.users.push(user); + } + slint::spawn_local({ let state_rc = state_rc.clone(); let db = state.db.clone(); @@ -553,7 +592,7 @@ impl AppViewTrait for StateUserEdit { } CallbackMessage::UserEdit(CallbackMessageUserEdit::ConfirmProcessed) => { state.state_user_edit = Default::default(); - state.set_view(AppState::Users, true); + state.set_view(AppState::Users, true, true); } _ => (), } diff --git a/firmware/acid-firmware/src/ui/storage.rs b/firmware/acid-firmware/src/ui/storage.rs index 3243b7e..062cdaa 100644 --- a/firmware/acid-firmware/src/ui/storage.rs +++ b/firmware/acid-firmware/src/ui/storage.rs @@ -1,13 +1,34 @@ -use alloc::{string::String, vec::Vec}; +use core::fmt::{Debug, Formatter}; + +use alloc::{rc::Rc, string::String, vec::Vec}; use chrono::NaiveDateTime; use password_hash::Key; use serde::{Deserialize, Serialize}; -use slint::SharedString; +use slint::{Model, ModelRc, SharedString, VecModel}; use spectre_api_sys::{SpectreAlgorithm, SpectreCounter, SpectreResultType}; -#[derive(Deserialize, Serialize, Default, Clone, Debug, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Default)] pub struct SpectreUsersConfig { - pub users: Vec, + #[serde(with = "vec_model")] + pub users: Rc>, +} + +impl Debug for SpectreUsersConfig { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("SpectreUsersConfig") + .field_with("users", |f| { + f.debug_list().entries(self.users.iter()).finish() + }) + .finish() + } +} + +impl Clone for SpectreUsersConfig { + fn clone(&self) -> Self { + Self { + users: Rc::new(self.users.iter().collect()), + } + } } #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] @@ -57,6 +78,33 @@ pub struct SpectreSite { pub config: SpectreSiteConfig, } +mod vec_model { + use alloc::{rc::Rc, vec::Vec}; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use slint::{Model, VecModel}; + + #[allow(unused)] + pub fn serialize(value: &Rc>, serializer: S) -> Result + where + T: Serialize, + VecModel: Model, + S: Serializer, + { + let vec: Vec = value.iter().collect(); + vec.serialize(serializer) + } + + #[allow(unused)] + pub fn deserialize<'de, T, D>(deserializer: D) -> Result>, D::Error> + where + T: Deserialize<'de>, + D: Deserializer<'de>, + { + let vec = Vec::::deserialize(deserializer)?; + Ok(Rc::new(VecModel::from(vec))) + } +} + mod with_repr { use serde::{Deserialize, Deserializer, Serializer}; use spectre_api_sys::{SpectreAlgorithm, SpectreResultType}; diff --git a/firmware/acid-firmware/ui/login-view.slint b/firmware/acid-firmware/ui/login-view.slint index b20542b..8747d28 100644 --- a/firmware/acid-firmware/ui/login-view.slint +++ b/firmware/acid-firmware/ui/login-view.slint @@ -7,7 +7,7 @@ export component LoginView inherits VerticalLayout { spacing: Style.spacing; in property <[string]> usernames <=> combo_box_username.model; callback users_clicked(); - callback pw_accepted(string, string); + callback pw_accepted(int, string, string); Rectangle { } HorizontalLayout { @@ -25,14 +25,16 @@ export component LoginView inherits VerticalLayout { input-type: InputType.password; placeholder-text: "Password"; accepted(text) => { - root.pw_accepted(combo_box_username.current-value, text); + root.pw_accepted(combo_box_username.current-index, combo_box_username.current-value, text); + line_edit_user_pw.text = ""; } } IconButton { icon: @image-url("images/log-in.svg"); clicked => { - root.pw_accepted(combo_box_username.current-value, line_edit_user_pw.text); + root.pw_accepted(combo_box_username.current-index, combo_box_username.current-value, line_edit_user_pw.text); + line_edit_user_pw.text = ""; } } } diff --git a/firmware/acid-firmware/ui/main.slint b/firmware/acid-firmware/ui/main.slint index 7ee677a..669b694 100644 --- a/firmware/acid-firmware/ui/main.slint +++ b/firmware/acid-firmware/ui/main.slint @@ -27,7 +27,7 @@ export enum AppState { export component AppWindow inherits Window { // Special characters to generate pre-render glyphs for. - in property dummy: "ÄÖÜÁÉÍÓÚÝŔŚĹŹĆŃĚĽŽŠČŘĎŤŇŮÅäöüáéíóúýŕśĺźćńěľžščřďťňůåß„“”‘’—–@&$%+=¡¿¢£$¥€²³¼½¬¤¦§©®™°´ˇ¨"; + in property dummy: "ÄÖÜÁÉÍÓÚÝŔŚĹŹĆŃĚĽŽŠČŘĎŤŇŮÅäöüáéíóúýŕśĺźćńěľžščřďťňůåß„“”‘’—–@&$%+=¡¿¢£$¥€²³¼½¬¤¦§©®™°´ˇ¨●"; in property dummy_identicon_symbols: "╔╚╰═█░▒▓☺☻╗╝╯═◈◎◐◑◒◓☀☁☂☃☄★☆☎☏⎈⌂☘☢☣☕⌚⌛⏰⚡⛄⛅☔♔♕♖♗♘♙♚♛♜♝♞♟♨♩♪♫⚐⚑⚔⚖⚙⚠⌘⏎✄✆✈✉✌"; default-font-family: "IBM Plex Mono"; default-font-size: 16pt; @@ -38,6 +38,7 @@ export component AppWindow inherits Window { callback escape(); callback enter-view(view: AppState); // Login View + in property <[string]> login_usernames <=> login_view.usernames; callback login_pw_accepted <=> login_view.pw_accepted; // Users View callback users_edit_user <=> users_view.edit_user;