Fix password fields; show stored users on login page
This commit is contained in:
parent
d4c8d69cf3
commit
4a5ada0bb0
|
|
@ -11,6 +11,7 @@ pub enum CallbackMessage {
|
|||
|
||||
pub enum CallbackMessageLogin {
|
||||
PwAccepted {
|
||||
user_index: i32,
|
||||
username: SharedString,
|
||||
password: SharedString,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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<Key>,
|
||||
) -> 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<RefCell<Self>> {
|
||||
let state = Rc::new(RefCell::new(State {
|
||||
window: main.clone_strong(),
|
||||
users: {
|
||||
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,
|
||||
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,11 +325,7 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_view(&mut self, view: AppState, reset: bool) {
|
||||
self.view = view;
|
||||
self.window.set_app_state(view);
|
||||
|
||||
if reset {
|
||||
fn reset_view(&mut self) {
|
||||
match self.view {
|
||||
AppState::Login => self.state_login = Default::default(),
|
||||
AppState::Users => self.state_users = Default::default(),
|
||||
|
|
@ -318,6 +333,18 @@ impl State {
|
|||
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_target {
|
||||
self.reset_view();
|
||||
}
|
||||
}
|
||||
|
||||
/// Instead of having a `loop` in the non-async `SlintBackend::run_event_loop`, we achieve
|
||||
|
|
@ -349,14 +376,22 @@ struct StateLogin {}
|
|||
impl AppViewTrait for StateLogin {
|
||||
fn process_callback_message(state_rc: &Rc<RefCell<State>>, 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);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<SpectreUserConfig>,
|
||||
#[serde(with = "vec_model")]
|
||||
pub users: Rc<VecModel<SpectreUserConfig>>,
|
||||
}
|
||||
|
||||
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<T, S>(value: &Rc<VecModel<T>>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
VecModel<T>: Model<Data = T>,
|
||||
S: Serializer,
|
||||
{
|
||||
let vec: Vec<T> = value.iter().collect();
|
||||
vec.serialize(serializer)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<Rc<VecModel<T>>, D::Error>
|
||||
where
|
||||
T: Deserialize<'de>,
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let vec = Vec::<T>::deserialize(deserializer)?;
|
||||
Ok(Rc::new(VecModel::from(vec)))
|
||||
}
|
||||
}
|
||||
|
||||
mod with_repr {
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
use spectre_api_sys::{SpectreAlgorithm, SpectreResultType};
|
||||
|
|
|
|||
|
|
@ -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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export enum AppState {
|
|||
|
||||
export component AppWindow inherits Window {
|
||||
// Special characters to generate pre-render glyphs for.
|
||||
in property <string> dummy: "ÄÖÜÁÉÍÓÚÝŔŚĹŹĆŃĚĽŽŠČŘĎŤŇŮÅäöüáéíóúýŕśĺźćńěľžščřďťňůåß„“”‘’—–@&$%+=¡¿¢£$¥€²³¼½¬¤¦§©®™°´ˇ¨";
|
||||
in property <string> dummy: "ÄÖÜÁÉÍÓÚÝŔŚĹŹĆŃĚĽŽŠČŘĎŤŇŮÅäöüáéíóúýŕśĺźćńěľžščřďťňůåß„“”‘’—–@&$%+=¡¿¢£$¥€²³¼½¬¤¦§©®™°´ˇ¨●";
|
||||
in property <string> 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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue