Implement spectre user addition
This commit is contained in:
parent
3ac1656d33
commit
40b9b5d278
23
firmware/.vscode/settings.json
vendored
23
firmware/.vscode/settings.json
vendored
|
|
@ -1,4 +1,25 @@
|
||||||
{
|
{
|
||||||
|
"rust-analyzer.linkedProjects": [
|
||||||
|
"acid-firmware/Cargo.toml"
|
||||||
|
],
|
||||||
"rust-analyzer.cargo.noDefaultFeatures": true,
|
"rust-analyzer.cargo.noDefaultFeatures": true,
|
||||||
"rust-analyzer.cargo.features": ["probe"],
|
"rust-analyzer.cargo.features": ["develop"],
|
||||||
|
"rust-analyzer.cargo.target": "xtensa-esp32s3-none-elf",
|
||||||
|
"rust-analyzer.cargo.targetDir": "target/rust-analyzer",
|
||||||
|
"rust-analyzer.cargo.extraEnv": {
|
||||||
|
"RUSTUP_TOOLCHAIN": "esp"
|
||||||
|
},
|
||||||
|
"rust-analyzer.check.extraArgs": [
|
||||||
|
"-Zbuild-std=core,alloc"
|
||||||
|
],
|
||||||
|
"rust-analyzer.check.extraEnv": {
|
||||||
|
"EXPLICITLY_INCLUDE_DEFAULT_DIRS": "true",
|
||||||
|
"XKBCOMMON_BUILD_DIR": "../libxkbcommon/build-esp32s3",
|
||||||
|
"SPECTRE_API_BUILD_DIR": "../spectre-api-c/build-esp32s3",
|
||||||
|
"SODIUM_INSTALL_DIR": "../libsodium/install",
|
||||||
|
"XKBCOMMON_BUILD_DIR_NAME": "build-esp32s3",
|
||||||
|
"SPECTRE_API_BUILD_DIR_NAME": "build-esp32s3",
|
||||||
|
"SODIUM_INSTALL_DIR_NAME": "install",
|
||||||
|
"SLINT_FONT_SIZES": "8,11,10,12,13,14,15,16,18,20,22,24,32"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
"extraArgs": ["-Zbuild-std=core,alloc"],
|
"extraArgs": ["-Zbuild-std=core,alloc"],
|
||||||
// Enable device support and a wide set of features on the esp-rtos crate.
|
// Enable device support and a wide set of features on the esp-rtos crate.
|
||||||
"noDefaultFeatures": true,
|
"noDefaultFeatures": true,
|
||||||
"features": ["rtt-log"],
|
"features": ["develop"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
2
firmware/Cargo.lock
generated
2
firmware/Cargo.lock
generated
|
|
@ -37,6 +37,7 @@ dependencies = [
|
||||||
"esp-storage",
|
"esp-storage",
|
||||||
"esp-sync",
|
"esp-sync",
|
||||||
"gix",
|
"gix",
|
||||||
|
"hex",
|
||||||
"hmac",
|
"hmac",
|
||||||
"i-slint-common",
|
"i-slint-common",
|
||||||
"i-slint-core",
|
"i-slint-core",
|
||||||
|
|
@ -3956,6 +3957,7 @@ dependencies = [
|
||||||
"rgb",
|
"rgb",
|
||||||
"scoped-tls-hkt",
|
"scoped-tls-hkt",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
|
"serde",
|
||||||
"skrifa",
|
"skrifa",
|
||||||
"slab",
|
"slab",
|
||||||
"strum",
|
"strum",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["acid-firmware", "password-hash"]
|
members = ["acid-firmware", "password-hash"]
|
||||||
default-members = []
|
default-members = ["acid-firmware"]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
spectre-api-sys = { git = "https://github.com/Limeth/spectre-api-sys", rev = "9e844eb056c3dfee8286ac21ec40fa689a8b8aa2" }
|
spectre-api-sys = { git = "https://github.com/Limeth/spectre-api-sys", rev = "9e844eb056c3dfee8286ac21ec40fa689a8b8aa2" }
|
||||||
|
|
|
||||||
|
|
@ -86,10 +86,11 @@ serde_bytes = { version = "0.11.19", default-features = false, features = ["allo
|
||||||
chrono = { version = "0.4.43", default-features = false, features = ["alloc", "serde"] } # TODO: defmt
|
chrono = { version = "0.4.43", default-features = false, features = ["alloc", "serde"] } # TODO: defmt
|
||||||
tinyvec = { version = "1.10.0", default-features = false, features = ["alloc"] }
|
tinyvec = { version = "1.10.0", default-features = false, features = ["alloc"] }
|
||||||
esp-metadata-generated = { version = "0.3.0", features = ["esp32s3"] }
|
esp-metadata-generated = { version = "0.3.0", features = ["esp32s3"] }
|
||||||
|
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
|
||||||
|
|
||||||
# A fork of slint with patches for `allocator_api` support.
|
# A fork of slint with patches for `allocator_api` support.
|
||||||
# Don't forget to change `slint-build` in build dependencies, if this is changed.
|
# Don't forget to change `slint-build` in build dependencies, if this is changed.
|
||||||
slint = { version = "1.14.1", git = "https://github.com/Limeth/slint", rev = "c2e5d05df2476557a299a78664e148d2fe62427d", default-features = false, features = ["compat-1-2", "libm", "log", "unsafe-single-threaded", "renderer-software"]}
|
slint = { version = "1.14.1", git = "https://github.com/Limeth/slint", rev = "c2e5d05df2476557a299a78664e148d2fe62427d", default-features = false, features = ["compat-1-2", "libm", "log", "unsafe-single-threaded", "renderer-software", "serde"] }
|
||||||
i-slint-common = { version = "1.14.1", git = "https://github.com/Limeth/slint", rev = "c2e5d05df2476557a299a78664e148d2fe62427d" }
|
i-slint-common = { version = "1.14.1", git = "https://github.com/Limeth/slint", rev = "c2e5d05df2476557a299a78664e148d2fe62427d" }
|
||||||
i-slint-core = { version = "1.14.1", git = "https://github.com/Limeth/slint", rev = "c2e5d05df2476557a299a78664e148d2fe62427d", default-features = false }
|
i-slint-core = { version = "1.14.1", git = "https://github.com/Limeth/slint", rev = "c2e5d05df2476557a299a78664e148d2fe62427d", default-features = false }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
use core::{
|
use core::{
|
||||||
cell::RefCell,
|
cell::{Cell, RefCell},
|
||||||
ffi::{c_char, c_int, c_size_t, c_uchar, c_ulonglong},
|
ffi::{c_char, c_int, c_size_t, c_uchar, c_ulonglong},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use critical_section::Mutex;
|
||||||
use data_encoding_macro::hexlower;
|
use data_encoding_macro::hexlower;
|
||||||
use embassy_sync::blocking_mutex::{self, raw::CriticalSectionRawMutex};
|
use embassy_sync::blocking_mutex::{self, raw::CriticalSectionRawMutex};
|
||||||
|
use esp_sync::RawMutex;
|
||||||
use hmac::digest::{FixedOutput, KeyInit, Update};
|
use hmac::digest::{FixedOutput, KeyInit, Update};
|
||||||
|
use password_hash::Key;
|
||||||
use sha2::{
|
use sha2::{
|
||||||
Digest,
|
Digest,
|
||||||
digest::{consts::U32, generic_array::GenericArray},
|
digest::{consts::U32, generic_array::GenericArray},
|
||||||
|
|
@ -36,6 +39,10 @@ unsafe extern "C" fn __spre_crypto_hash_sha256(
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is the encrypted user key currently being used in the key derivation function of spectre.
|
||||||
|
/// It decrypts using the user's password into the key that would be derived with the original password hashing function.
|
||||||
|
pub static ACTIVE_ENCRYPTED_USER_KEY: Mutex<Cell<Key>> = Mutex::new(Cell::new([0; _]));
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
unsafe extern "C" fn __spre_crypto_pwhash_scryptsalsa208sha256_ll(
|
unsafe extern "C" fn __spre_crypto_pwhash_scryptsalsa208sha256_ll(
|
||||||
|
|
@ -51,23 +58,19 @@ unsafe extern "C" fn __spre_crypto_pwhash_scryptsalsa208sha256_ll(
|
||||||
) -> c_int {
|
) -> c_int {
|
||||||
assert_eq!(output_len, 64);
|
assert_eq!(output_len, 64);
|
||||||
|
|
||||||
// TODO: Implement account storage. This is just a key for test:test.
|
let encryption_key = unsafe {
|
||||||
const ENCRYPTED_USER_KEY: [u8; 64] = hexlower!(
|
|
||||||
"e338e425b4f8d17ac6c349f7ec84087d59a56b6850bcfe950de1af3f04a609d9b1490479086360a38dc209070213c7915e91733a07eced2cec4c6356e050c2be"
|
|
||||||
);
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let password: &[u8] = core::slice::from_raw_parts(password, password_len);
|
let password: &[u8] = core::slice::from_raw_parts(password, password_len);
|
||||||
let salt: &[u8] = core::slice::from_raw_parts(salt, salt_len);
|
let salt: &[u8] = core::slice::from_raw_parts(salt, salt_len);
|
||||||
let output: &mut [u8] = core::slice::from_raw_parts_mut(output, output_len);
|
|
||||||
|
|
||||||
let purpose = spectre_purpose_scope(SpectreKeyPurpose::Authentication);
|
let purpose = spectre_purpose_scope(SpectreKeyPurpose::Authentication);
|
||||||
let encryption_key = password_hash::derive_encryption_key(salt, password, &PSRAM_ALLOCATOR);
|
|
||||||
let mut user_key = ENCRYPTED_USER_KEY;
|
password_hash::derive_encryption_key(salt, password, &PSRAM_ALLOCATOR)
|
||||||
|
};
|
||||||
|
|
||||||
|
let output: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(output, output_len) };
|
||||||
|
let mut user_key = critical_section::with(|cs| ACTIVE_ENCRYPTED_USER_KEY.borrow(cs).get());
|
||||||
|
|
||||||
password_hash::decrypt_with(&mut user_key, &encryption_key);
|
password_hash::decrypt_with(&mut user_key, &encryption_key);
|
||||||
output.copy_from_slice(&user_key);
|
output.copy_from_slice(&user_key);
|
||||||
}
|
|
||||||
|
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ pub mod usb {
|
||||||
|
|
||||||
/// Alternative logger via UART.
|
/// Alternative logger via UART.
|
||||||
#[cfg(feature = "alt-log")]
|
#[cfg(feature = "alt-log")]
|
||||||
|
#[macro_use]
|
||||||
pub mod uart {
|
pub mod uart {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::console;
|
use crate::console;
|
||||||
|
|
@ -80,7 +81,6 @@ pub mod uart {
|
||||||
use esp_hal::{
|
use esp_hal::{
|
||||||
Blocking,
|
Blocking,
|
||||||
gpio::interconnect::{PeripheralInput, PeripheralOutput},
|
gpio::interconnect::{PeripheralInput, PeripheralOutput},
|
||||||
peripherals::UART2,
|
|
||||||
uart::{Uart, UartTx},
|
uart::{Uart, UartTx},
|
||||||
};
|
};
|
||||||
use log::{Log, info};
|
use log::{Log, info};
|
||||||
|
|
@ -101,10 +101,9 @@ pub mod uart {
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
macro_rules! println {
|
macro_rules! println {
|
||||||
// TODO: I don't think this is necessary. Consider removing.
|
() => {{
|
||||||
// () => {{
|
do_print(Default::default());
|
||||||
// do_print(Default::default());
|
}};
|
||||||
// }};
|
|
||||||
|
|
||||||
($($arg:tt)*) => {{
|
($($arg:tt)*) => {{
|
||||||
do_print(::core::format_args!($($arg)*));
|
do_print(::core::format_args!($($arg)*));
|
||||||
|
|
@ -190,7 +189,7 @@ pub mod uart {
|
||||||
pub mod rtt {
|
pub mod rtt {
|
||||||
use super::*;
|
use super::*;
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
use ::rtt_target::{rprint as print, rprintln as println};
|
pub use ::rtt_target::{rprint as print, rprintln as println};
|
||||||
use panic_rtt_target as _; // Use the RTT panic handler.
|
use panic_rtt_target as _; // Use the RTT panic handler.
|
||||||
use rtt_target::ChannelMode;
|
use rtt_target::ChannelMode;
|
||||||
|
|
||||||
|
|
@ -200,3 +199,35 @@ pub mod rtt {
|
||||||
async {}
|
async {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #[macro_export]
|
||||||
|
// macro_rules! dbg {
|
||||||
|
// () => {
|
||||||
|
// $crate::logging::implementation::println!("[{}:{}:{}]", $crate::file!(), $crate::line!(), $crate::column!())
|
||||||
|
// };
|
||||||
|
// ($val:expr $(,)?) => {
|
||||||
|
// match $val {
|
||||||
|
// tmp => {
|
||||||
|
// $crate::logging::uart::println!("[{}:{}:{}] {} = {:#?}",
|
||||||
|
// file!(),
|
||||||
|
// line!(),
|
||||||
|
// column!(),
|
||||||
|
// stringify!($val),
|
||||||
|
// // The `&T: Debug` check happens here (not in the format literal desugaring)
|
||||||
|
// // to avoid format literal related messages and suggestions.
|
||||||
|
// &&tmp as &dyn ::core::fmt::Debug,
|
||||||
|
// );
|
||||||
|
// tmp
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// ($($val:expr),+ $(,)?) => {
|
||||||
|
// ($($crate::dbg!($val)),+,)
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[cfg(feature = "alt-log")]
|
||||||
|
// pub use uart as implementation;
|
||||||
|
|
||||||
|
// #[cfg(feature = "rtt-log")]
|
||||||
|
// pub use rtt as implementation;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ use core::cell::RefCell;
|
||||||
use core::sync::atomic::{AtomicBool, Ordering};
|
use core::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
|
use alloc::collections::vec_deque::VecDeque;
|
||||||
|
use alloc::sync::Arc;
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
use embassy_embedded_hal::adapter::BlockingAsync;
|
use embassy_embedded_hal::adapter::BlockingAsync;
|
||||||
use embassy_embedded_hal::flash::partition::Partition;
|
use embassy_embedded_hal::flash::partition::Partition;
|
||||||
|
|
@ -97,9 +99,9 @@ esp_bootloader_esp_idf::esp_app_desc!();
|
||||||
// A panic such as `memory allocation of 3740121773 bytes failed` is caused by a heap overflow. The size is `DEEDBAAD` in hex.
|
// A panic such as `memory allocation of 3740121773 bytes failed` is caused by a heap overflow. The size is `DEEDBAAD` in hex.
|
||||||
|
|
||||||
/// Total heap size
|
/// Total heap size
|
||||||
const HEAP_SIZE: usize = 128 * 1024;
|
const HEAP_SIZE: usize = 112 * 1024;
|
||||||
/// Size of the app core's stack
|
/// Size of the app core's stack
|
||||||
const STACK_SIZE_CORE_APP: usize = 64 * 1024;
|
const STACK_SIZE_CORE_APP: usize = 80 * 1024;
|
||||||
|
|
||||||
// const FRAME_DURATION_MIN: Duration = Duration::from_millis(40); // 25 FPS
|
// const FRAME_DURATION_MIN: Duration = Duration::from_millis(40); // 25 FPS
|
||||||
const FRAME_DURATION_MIN: Duration = Duration::from_millis(100); // 10 FPS
|
const FRAME_DURATION_MIN: Duration = Duration::from_millis(100); // 10 FPS
|
||||||
|
|
@ -392,6 +394,8 @@ async fn main(_spawner: Spawner) {
|
||||||
window_size,
|
window_size,
|
||||||
window: RefCell::new(None),
|
window: RefCell::new(None),
|
||||||
framebuffer: framebuffer_ptr,
|
framebuffer: framebuffer_ptr,
|
||||||
|
quit_event_loop: Default::default(),
|
||||||
|
events: Arc::new(critical_section::Mutex::new(RefCell::new(VecDeque::new()))),
|
||||||
};
|
};
|
||||||
spawner.must_spawn(ui::run_renderer_task(slint_backend, flash_part_acid));
|
spawner.must_spawn(ui::run_renderer_task(slint_backend, flash_part_acid));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,20 @@
|
||||||
use core::{cell::RefCell, time::Duration};
|
use core::{
|
||||||
|
cell::RefCell,
|
||||||
|
sync::atomic::{AtomicBool, Ordering},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use alloc::{rc::Rc, string::ToString};
|
use alloc::{
|
||||||
|
boxed::Box, collections::vec_deque::VecDeque, rc::Rc, string::ToString, sync::Arc, vec::Vec,
|
||||||
|
};
|
||||||
|
use critical_section::Mutex;
|
||||||
use esp_hal::time::Instant;
|
use esp_hal::time::Instant;
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
|
use rmk::futures::sink::drain;
|
||||||
use slint::{
|
use slint::{
|
||||||
PhysicalSize, SharedString, WindowSize,
|
EventLoopError, PhysicalSize, SharedString, WindowSize,
|
||||||
platform::{
|
platform::{
|
||||||
Key, WindowEvent,
|
EventLoopProxy, Key, WindowEvent,
|
||||||
software_renderer::{RenderingRotation, RepaintBufferType, Rgb565Pixel, SoftwareRenderer},
|
software_renderer::{RenderingRotation, RepaintBufferType, Rgb565Pixel, SoftwareRenderer},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -24,7 +32,8 @@ pub struct SlintBackend {
|
||||||
pub window_size: [u32; 2],
|
pub window_size: [u32; 2],
|
||||||
pub window: RefCell<Option<Rc<SoftwareWindowAdapter>>>,
|
pub window: RefCell<Option<Rc<SoftwareWindowAdapter>>>,
|
||||||
pub framebuffer: FramebufferPtr,
|
pub framebuffer: FramebufferPtr,
|
||||||
// pub peripherals: RefCell<Option<Peripherals>>,
|
pub quit_event_loop: Arc<AtomicBool>,
|
||||||
|
pub events: Arc<Mutex<RefCell<VecDeque<Box<dyn FnOnce() + Send>>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl slint::platform::Platform for SlintBackend {
|
impl slint::platform::Platform for SlintBackend {
|
||||||
|
|
@ -49,12 +58,31 @@ impl slint::platform::Platform for SlintBackend {
|
||||||
Duration::from_millis(Instant::now().duration_since_epoch().as_millis())
|
Duration::from_millis(Instant::now().duration_since_epoch().as_millis())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
|
||||||
|
Some(Box::new(AcidEventLoopProxy {
|
||||||
|
quit_event_loop: self.quit_event_loop.clone(),
|
||||||
|
events: self.events.clone(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
|
fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
|
||||||
// Instead of `loop`ing here, we execute a single iteration and handle `loop`ing
|
// Instead of `loop`ing here, we execute a single iteration and handle `loop`ing
|
||||||
// in `crate::run_renderer_task`, where we can make use of `await`.
|
// in `crate::run_renderer_task`, where we can make use of `await`.
|
||||||
|
|
||||||
/* loop */
|
/* loop */
|
||||||
{
|
{
|
||||||
|
let drained_events = critical_section::with(|cs| {
|
||||||
|
self.events
|
||||||
|
.borrow(cs)
|
||||||
|
.borrow_mut()
|
||||||
|
.drain(..)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
|
||||||
|
for event in drained_events {
|
||||||
|
(event)();
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(window) = self.window.borrow().clone() {
|
if let Some(window) = self.window.borrow().clone() {
|
||||||
// Handle key presses
|
// Handle key presses
|
||||||
while let Ok(mut key_message) = KEY_MESSAGE_CHANNEL.try_receive() {
|
while let Ok(mut key_message) = KEY_MESSAGE_CHANNEL.try_receive() {
|
||||||
|
|
@ -62,7 +90,8 @@ impl slint::platform::Platform for SlintBackend {
|
||||||
|
|
||||||
if let Some(string) = key_message.string.as_ref()
|
if let Some(string) = key_message.string.as_ref()
|
||||||
&& (Keysym::a..=Keysym::z).contains(&key_message.keysym)
|
&& (Keysym::a..=Keysym::z).contains(&key_message.keysym)
|
||||||
&& let &[code] = string.as_bytes() {
|
&& let &[code] = string.as_bytes()
|
||||||
|
{
|
||||||
const UNICODE_CTRL_A: char = '\u{1}';
|
const UNICODE_CTRL_A: char = '\u{1}';
|
||||||
|
|
||||||
let letter_index_from_keysym =
|
let letter_index_from_keysym =
|
||||||
|
|
@ -74,9 +103,8 @@ impl slint::platform::Platform for SlintBackend {
|
||||||
key_message.keysym =
|
key_message.keysym =
|
||||||
Keysym::new(Keysym::a.raw() + letter_index_from_keysym);
|
Keysym::new(Keysym::a.raw() + letter_index_from_keysym);
|
||||||
// TODO: Avoid allocation
|
// TODO: Avoid allocation
|
||||||
key_message.string = Some(
|
key_message.string =
|
||||||
((b'a' + letter_index_from_keysym as u8) as char).to_string(),
|
Some(((b'a' + letter_index_from_keysym as u8) as char).to_string());
|
||||||
);
|
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"Translating CTRL-{letter} to {letter}",
|
"Translating CTRL-{letter} to {letter}",
|
||||||
|
|
@ -118,3 +146,25 @@ impl slint::platform::Platform for SlintBackend {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct AcidEventLoopProxy {
|
||||||
|
pub quit_event_loop: Arc<AtomicBool>,
|
||||||
|
pub events: Arc<Mutex<RefCell<VecDeque<Box<dyn FnOnce() + Send>>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventLoopProxy for AcidEventLoopProxy {
|
||||||
|
fn quit_event_loop(&self) -> Result<(), EventLoopError> {
|
||||||
|
self.quit_event_loop.store(true, Ordering::SeqCst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invoke_from_event_loop(
|
||||||
|
&self,
|
||||||
|
event: Box<dyn FnOnce() + Send>,
|
||||||
|
) -> Result<(), EventLoopError> {
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
self.events.borrow(cs).borrow_mut().push_back(event);
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,10 @@ pub enum CallbackMessageUsers {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum CallbackMessageUserEdit {
|
pub enum CallbackMessageUserEdit {
|
||||||
ComputeIdenticon {
|
ComputeIdenticon { password: SharedString },
|
||||||
encrypted_key: SharedString,
|
ComputeKeyId { key: SharedString },
|
||||||
password: SharedString,
|
ConfirmRequest { encrypted_key: SharedString },
|
||||||
},
|
ConfirmProcessed,
|
||||||
Confirm {
|
|
||||||
encrypted_key: SharedString,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum CallbackMessageUserSites {}
|
pub enum CallbackMessageUserSites {}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,27 @@
|
||||||
// #![cfg_attr(not(feature = "simulator"), no_main)]
|
// #![cfg_attr(not(feature = "simulator"), no_main)]
|
||||||
|
|
||||||
use core::{cell::RefCell, ffi::CStr};
|
use core::{cell::RefCell, ffi::CStr, ops::DerefMut};
|
||||||
|
|
||||||
use alloc::{boxed::Box, ffi::CString, format, rc::Rc, vec};
|
use alloc::{boxed::Box, ffi::CString, format, rc::Rc, vec};
|
||||||
use embassy_time::Instant;
|
use embassy_time::Instant;
|
||||||
|
use hex::FromHexError;
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
|
use password_hash::Key;
|
||||||
use slint::SharedString;
|
use slint::SharedString;
|
||||||
use spectre_api_sys::{SpectreAlgorithm, SpectreUserKey};
|
use spectre_api_sys::{SpectreAlgorithm, SpectreKeyID, SpectreUserKey};
|
||||||
|
|
||||||
#[cfg(feature = "limit-fps")]
|
#[cfg(feature = "limit-fps")]
|
||||||
use crate::FRAME_DURATION_MIN;
|
use crate::FRAME_DURATION_MIN;
|
||||||
use crate::{
|
use crate::{
|
||||||
PSRAM_ALLOCATOR, SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER,
|
PSRAM_ALLOCATOR, SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER,
|
||||||
db::{AcidDatabase, DbKey, DbPathSpectreUsers, PartitionAcid, ReadTransactionExt},
|
db::{AcidDatabase, DbKey, DbPathSpectreUsers, PartitionAcid, ReadTransactionExt},
|
||||||
ffi::alloc::__spre_free,
|
ffi::{alloc::__spre_free, crypto::ACTIVE_ENCRYPTED_USER_KEY},
|
||||||
ui::{
|
ui::{
|
||||||
backend::SlintBackend,
|
backend::SlintBackend,
|
||||||
messages::{
|
messages::{
|
||||||
CallbackMessage, CallbackMessageLogin, CallbackMessageUserEdit, CallbackMessageUsers,
|
CallbackMessage, CallbackMessageLogin, CallbackMessageUserEdit, CallbackMessageUsers,
|
||||||
},
|
},
|
||||||
storage::SpectreUsersConfig,
|
storage::{SpectreUserConfig, SpectreUsersConfig},
|
||||||
},
|
},
|
||||||
util::DurationExt,
|
util::DurationExt,
|
||||||
};
|
};
|
||||||
|
|
@ -155,13 +157,14 @@ pub async fn run_renderer_task(backend: SlintBackend, flash_part_acid: Partition
|
||||||
|
|
||||||
let main = AppWindow::new().unwrap();
|
let main = AppWindow::new().unwrap();
|
||||||
let state = State::new(db, main).await;
|
let state = State::new(db, main).await;
|
||||||
|
let window = state.borrow().window.clone_strong();
|
||||||
|
|
||||||
state.borrow().run_event_loop().await;
|
State::run_event_loop(window).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
window: AppWindow,
|
window: AppWindow,
|
||||||
db: AcidDatabase,
|
db: Rc<AcidDatabase>,
|
||||||
users: SpectreUsersConfig,
|
users: SpectreUsersConfig,
|
||||||
/// Currently active view.
|
/// Currently active view.
|
||||||
view: AppState,
|
view: AppState,
|
||||||
|
|
@ -177,18 +180,11 @@ impl State {
|
||||||
let state = Rc::new(RefCell::new(State {
|
let state = Rc::new(RefCell::new(State {
|
||||||
window: main.clone_strong(),
|
window: main.clone_strong(),
|
||||||
users: {
|
users: {
|
||||||
let read = db.read_transaction().await;
|
let users = Self::load_users(&db).await;
|
||||||
let mut buffer = vec![0_u8; 128];
|
warn!("Users: {users:#?}");
|
||||||
match read
|
users
|
||||||
.read_to_vec(&DbKey::new(DbPathSpectreUsers), &mut buffer)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(bytes) => postcard::from_bytes::<SpectreUsersConfig>(bytes).unwrap(),
|
|
||||||
Err(ekv::ReadError::KeyNotFound) => Default::default(),
|
|
||||||
Err(error) => panic!("Failed to read the users config: {error:?}"),
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
db,
|
db: Rc::new(db),
|
||||||
view: AppState::Login,
|
view: AppState::Login,
|
||||||
state_login: Default::default(),
|
state_login: Default::default(),
|
||||||
state_users: Default::default(),
|
state_users: Default::default(),
|
||||||
|
|
@ -206,56 +202,61 @@ impl State {
|
||||||
main.on_escape({
|
main.on_escape({
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
move || {
|
move || {
|
||||||
state
|
State::process_callback_message(&state, CallbackMessage::Escape);
|
||||||
.borrow_mut()
|
|
||||||
.process_callback_message(CallbackMessage::Escape);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on_login_pw_accepted({
|
main.on_login_pw_accepted({
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
move |username, password| {
|
move |username, password| {
|
||||||
state
|
State::process_callback_message(
|
||||||
.borrow_mut()
|
&state,
|
||||||
.process_callback_message(CallbackMessage::Login(
|
CallbackMessage::Login(CallbackMessageLogin::PwAccepted { username, password }),
|
||||||
CallbackMessageLogin::PwAccepted { username, password },
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on_users_edit_user({
|
main.on_users_edit_user({
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
move |username, new| {
|
move |username, new| {
|
||||||
state
|
State::process_callback_message(
|
||||||
.borrow_mut()
|
&state,
|
||||||
.process_callback_message(CallbackMessage::Users(
|
CallbackMessage::Users(CallbackMessageUsers::EditUser { username, new }),
|
||||||
CallbackMessageUsers::EditUser { username, new },
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on_user_edit_compute_identicon({
|
main.on_user_edit_compute_identicon({
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
move |encrypted_key, password| {
|
move |password| {
|
||||||
state
|
State::process_callback_message(
|
||||||
.borrow_mut()
|
&state,
|
||||||
.process_callback_message(CallbackMessage::UserEdit(
|
CallbackMessage::UserEdit(CallbackMessageUserEdit::ComputeIdenticon {
|
||||||
CallbackMessageUserEdit::ComputeIdenticon {
|
|
||||||
encrypted_key,
|
|
||||||
password,
|
password,
|
||||||
},
|
}),
|
||||||
));
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on_user_edit_compute_key_id({
|
||||||
|
let state = state.clone();
|
||||||
|
move |key| {
|
||||||
|
State::process_callback_message(
|
||||||
|
&state,
|
||||||
|
CallbackMessage::UserEdit(CallbackMessageUserEdit::ComputeKeyId { key }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on_user_edit_confirm({
|
main.on_user_edit_confirm({
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
move |encrypted_key| {
|
move |encrypted_key| {
|
||||||
state
|
State::process_callback_message(
|
||||||
.borrow_mut()
|
&state,
|
||||||
.process_callback_message(CallbackMessage::UserEdit(
|
CallbackMessage::UserEdit(CallbackMessageUserEdit::ConfirmRequest {
|
||||||
CallbackMessageUserEdit::Confirm { encrypted_key },
|
encrypted_key,
|
||||||
));
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -272,12 +273,36 @@ impl State {
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_callback_message(&mut self, message: CallbackMessage) {
|
async fn load_users(db: &AcidDatabase) -> SpectreUsersConfig {
|
||||||
match self.view {
|
let read = db.read_transaction().await;
|
||||||
AppState::Login => StateLogin::process_callback_message(self, message),
|
let mut buffer = vec![0_u8; 128];
|
||||||
AppState::Users => StateUsers::process_callback_message(self, message),
|
match read
|
||||||
AppState::UserEdit => StateUserEdit::process_callback_message(self, message),
|
.read_to_vec(&DbKey::new(DbPathSpectreUsers), &mut buffer)
|
||||||
AppState::UserSites => StateUserSites::process_callback_message(self, message),
|
.await
|
||||||
|
{
|
||||||
|
Ok(bytes) => postcard::from_bytes::<SpectreUsersConfig>(bytes).unwrap(),
|
||||||
|
Err(ekv::ReadError::KeyNotFound) => Default::default(),
|
||||||
|
Err(error) => panic!("Failed to read the users config: {error:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn save_users(db: &AcidDatabase, users: &SpectreUsersConfig) {
|
||||||
|
let mut write = db.write_transaction().await;
|
||||||
|
let buffer = postcard::to_allocvec(&users).unwrap();
|
||||||
|
write
|
||||||
|
.write(&DbKey::new(DbPathSpectreUsers), &buffer)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
write.commit().await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_callback_message(state_rc: &Rc<RefCell<State>>, message: CallbackMessage) {
|
||||||
|
let view = state_rc.borrow().view;
|
||||||
|
match view {
|
||||||
|
AppState::Login => StateLogin::process_callback_message(state_rc, message),
|
||||||
|
AppState::Users => StateUsers::process_callback_message(state_rc, message),
|
||||||
|
AppState::UserEdit => StateUserEdit::process_callback_message(state_rc, message),
|
||||||
|
AppState::UserSites => StateUserSites::process_callback_message(state_rc, message),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -298,8 +323,8 @@ impl State {
|
||||||
/// Instead of having a `loop` in the non-async `SlintBackend::run_event_loop`, we achieve
|
/// Instead of having a `loop` in the non-async `SlintBackend::run_event_loop`, we achieve
|
||||||
/// async by having only one iteration of the loop run, and `await`ing here.
|
/// async by having only one iteration of the loop run, and `await`ing here.
|
||||||
/// The following block is analogous to `main.run()`.
|
/// The following block is analogous to `main.run()`.
|
||||||
async fn run_event_loop(&self) -> ! {
|
async fn run_event_loop(window: AppWindow) -> ! {
|
||||||
self.window.show().unwrap();
|
window.show().unwrap();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
slint::run_event_loop().unwrap();
|
slint::run_event_loop().unwrap();
|
||||||
|
|
@ -310,19 +335,20 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(unreachable_code)]
|
#[expect(unreachable_code)]
|
||||||
self.window.hide().unwrap();
|
window.hide().unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait AppViewTrait {
|
trait AppViewTrait {
|
||||||
fn process_callback_message(_state: &mut State, _message: CallbackMessage) {}
|
fn process_callback_message(_state_rc: &Rc<RefCell<State>>, _message: CallbackMessage) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct StateLogin {}
|
struct StateLogin {}
|
||||||
|
|
||||||
impl AppViewTrait for StateLogin {
|
impl AppViewTrait for StateLogin {
|
||||||
fn process_callback_message(state: &mut State, message: CallbackMessage) {
|
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 }) =
|
if let CallbackMessage::Login(CallbackMessageLogin::PwAccepted { username, password }) =
|
||||||
message
|
message
|
||||||
{
|
{
|
||||||
|
|
@ -372,7 +398,8 @@ impl AppViewTrait for StateLogin {
|
||||||
struct StateUsers {}
|
struct StateUsers {}
|
||||||
|
|
||||||
impl AppViewTrait for StateUsers {
|
impl AppViewTrait for StateUsers {
|
||||||
fn process_callback_message(state: &mut State, message: CallbackMessage) {
|
fn process_callback_message(state_rc: &Rc<RefCell<State>>, message: CallbackMessage) {
|
||||||
|
let mut state = state_rc.borrow_mut();
|
||||||
match message {
|
match message {
|
||||||
CallbackMessage::Escape => {
|
CallbackMessage::Escape => {
|
||||||
state.set_view(AppState::Login, false);
|
state.set_view(AppState::Login, false);
|
||||||
|
|
@ -381,7 +408,8 @@ impl AppViewTrait for StateUsers {
|
||||||
state.state_user_edit = StateUserEdit {
|
state.state_user_edit = StateUserEdit {
|
||||||
username: username.clone(),
|
username: username.clone(),
|
||||||
new,
|
new,
|
||||||
hashed: None,
|
password: None,
|
||||||
|
encrypted_key: None,
|
||||||
};
|
};
|
||||||
state.window.set_user_edit_username(username);
|
state.window.set_user_edit_username(username);
|
||||||
state.set_view(AppState::UserEdit, false);
|
state.set_view(AppState::UserEdit, false);
|
||||||
|
|
@ -395,19 +423,19 @@ impl AppViewTrait for StateUsers {
|
||||||
struct StateUserEdit {
|
struct StateUserEdit {
|
||||||
username: SharedString,
|
username: SharedString,
|
||||||
new: bool,
|
new: bool,
|
||||||
hashed: Option<ProposedPassword>,
|
password: Option<SharedString>,
|
||||||
|
encrypted_key: Option<(Key, SpectreUserKey)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppViewTrait for StateUserEdit {
|
impl AppViewTrait for StateUserEdit {
|
||||||
fn process_callback_message(state: &mut State, message: CallbackMessage) {
|
fn process_callback_message(state_rc: &Rc<RefCell<State>>, message: CallbackMessage) {
|
||||||
|
let state = state_rc.clone();
|
||||||
|
let mut state = state.borrow_mut();
|
||||||
match message {
|
match message {
|
||||||
CallbackMessage::Escape => {
|
CallbackMessage::Escape => {
|
||||||
state.set_view(AppState::Users, false);
|
state.set_view(AppState::Users, false);
|
||||||
}
|
}
|
||||||
CallbackMessage::UserEdit(CallbackMessageUserEdit::ComputeIdenticon {
|
CallbackMessage::UserEdit(CallbackMessageUserEdit::ComputeIdenticon { password }) => {
|
||||||
encrypted_key,
|
|
||||||
password,
|
|
||||||
}) => {
|
|
||||||
let username_c = CString::new(&*state.state_user_edit.username)
|
let username_c = CString::new(&*state.state_user_edit.username)
|
||||||
.expect("Username cannot be converted to a C string.");
|
.expect("Username cannot be converted to a C string.");
|
||||||
let password_c =
|
let password_c =
|
||||||
|
|
@ -431,24 +459,107 @@ impl AppViewTrait for StateUserEdit {
|
||||||
|
|
||||||
warn!("Identicon: {identicon} ({identicon:?})");
|
warn!("Identicon: {identicon} ({identicon:?})");
|
||||||
state.window.set_user_edit_identicon(identicon.clone());
|
state.window.set_user_edit_identicon(identicon.clone());
|
||||||
state.state_user_edit.hashed = Some(ProposedPassword {
|
state.state_user_edit.password = Some(password);
|
||||||
encrypted_key,
|
|
||||||
identicon,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
CallbackMessage::UserEdit(CallbackMessageUserEdit::Confirm { encrypted_key: _ }) => {
|
CallbackMessage::UserEdit(CallbackMessageUserEdit::ComputeKeyId {
|
||||||
// TODO
|
key: key_string,
|
||||||
|
}) => {
|
||||||
|
let Some(password) = state.state_user_edit.password.as_ref() else {
|
||||||
|
warn!("Attempted to compute a key ID when no password has been entered.");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut key: Key = [0; _];
|
||||||
|
if let Err(key_decode_error) = hex::decode_to_slice(&*key_string, &mut key) {
|
||||||
|
let message = match key_decode_error {
|
||||||
|
FromHexError::InvalidStringLength | FromHexError::OddLength => {
|
||||||
|
let required_size = key.len() * 2;
|
||||||
|
let provided_size = key_string.len();
|
||||||
|
let delta = provided_size as i32 - required_size as i32;
|
||||||
|
|
||||||
|
if delta < 0 {
|
||||||
|
slint::format!("Missing {} characters.", -delta)
|
||||||
|
} else if delta > 0 {
|
||||||
|
slint::format!("{} too many characters.", delta)
|
||||||
|
} else {
|
||||||
|
slint::format!("Invalid key length.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FromHexError::InvalidHexCharacter { c, index } => {
|
||||||
|
slint::format!("Invalid character {c:?} at position {index}.")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
state.window.set_user_edit_key_error(message);
|
||||||
|
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);
|
||||||
|
|
||||||
|
state.window.set_user_edit_key_error(SharedString::new());
|
||||||
|
state.window.set_user_edit_key_id(
|
||||||
|
CStr::from_bytes_with_nul(&user_key.keyID.hex)
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
state.state_user_edit.encrypted_key = Some((key, user_key));
|
||||||
|
}
|
||||||
|
CallbackMessage::UserEdit(CallbackMessageUserEdit::ConfirmRequest {
|
||||||
|
encrypted_key: _,
|
||||||
|
}) => {
|
||||||
|
let Some((
|
||||||
|
encrypted_key,
|
||||||
|
SpectreUserKey {
|
||||||
|
keyID: SpectreKeyID { bytes: key_id, .. },
|
||||||
|
..
|
||||||
|
},
|
||||||
|
)) = state.state_user_edit.encrypted_key.take()
|
||||||
|
else {
|
||||||
|
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,
|
||||||
|
encrypted_key,
|
||||||
|
key_id,
|
||||||
|
});
|
||||||
|
slint::spawn_local({
|
||||||
|
let state_rc = state_rc.clone();
|
||||||
|
let db = state.db.clone();
|
||||||
|
let users = state.users.clone();
|
||||||
|
async move {
|
||||||
|
State::save_users(&db, &users).await;
|
||||||
|
State::process_callback_message(
|
||||||
|
&state_rc,
|
||||||
|
CallbackMessage::UserEdit(CallbackMessageUserEdit::ConfirmProcessed),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
CallbackMessage::UserEdit(CallbackMessageUserEdit::ConfirmProcessed) => {
|
||||||
|
state.state_user_edit = Default::default();
|
||||||
|
state.set_view(AppState::Users, true);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ProposedPassword {
|
|
||||||
encrypted_key: SharedString,
|
|
||||||
identicon: SharedString,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct StateUserSites {
|
struct StateUserSites {
|
||||||
username: SharedString,
|
username: SharedString,
|
||||||
user_key: SpectreUserKey,
|
user_key: SpectreUserKey,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use alloc::{string::String, vec::Vec};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use password_hash::Key;
|
use password_hash::Key;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use slint::SharedString;
|
||||||
use spectre_api_sys::{SpectreAlgorithm, SpectreCounter, SpectreResultType};
|
use spectre_api_sys::{SpectreAlgorithm, SpectreCounter, SpectreResultType};
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Default, Clone, Debug, PartialEq, Eq)]
|
#[derive(Deserialize, Serialize, Default, Clone, Debug, PartialEq, Eq)]
|
||||||
|
|
@ -11,7 +12,7 @@ pub struct SpectreUsersConfig {
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
|
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct SpectreUserConfig {
|
pub struct SpectreUserConfig {
|
||||||
pub username: String,
|
pub username: SharedString,
|
||||||
#[serde(with = "serde_bytes")]
|
#[serde(with = "serde_bytes")]
|
||||||
pub encrypted_key: Key,
|
pub encrypted_key: Key,
|
||||||
#[serde(with = "serde_bytes")]
|
#[serde(with = "serde_bytes")]
|
||||||
|
|
@ -26,10 +27,10 @@ pub struct SpectreSiteConfig {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
#[serde(with = "with_repr")]
|
#[serde(with = "with_repr")]
|
||||||
pub result_type: SpectreResultType,
|
pub result_type: SpectreResultType,
|
||||||
pub password: Option<String>,
|
pub password: Option<SharedString>,
|
||||||
#[serde(with = "with_repr")]
|
#[serde(with = "with_repr")]
|
||||||
pub login_type: SpectreResultType,
|
pub login_type: SpectreResultType,
|
||||||
pub login_name: Option<String>,
|
pub login_name: Option<SharedString>,
|
||||||
pub uses: u32,
|
pub uses: u32,
|
||||||
pub last_used: NaiveDateTime,
|
pub last_used: NaiveDateTime,
|
||||||
}
|
}
|
||||||
|
|
@ -51,8 +52,8 @@ impl Default for SpectreSiteConfig {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct SpectreSite {
|
pub struct SpectreSite {
|
||||||
pub username: String,
|
pub username: SharedString,
|
||||||
pub site_name: String,
|
pub site_name: SharedString,
|
||||||
pub config: SpectreSiteConfig,
|
pub config: SpectreSiteConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,11 @@ export component AppWindow inherits Window {
|
||||||
callback users_edit_user <=> users_view.edit_user;
|
callback users_edit_user <=> users_view.edit_user;
|
||||||
// User Edit View
|
// User Edit View
|
||||||
in property <string> user_edit_username <=> user_edit_view.username;
|
in property <string> user_edit_username <=> user_edit_view.username;
|
||||||
|
in property <string> user_edit_key_error <=> user_edit_view.key_error;
|
||||||
in-out property <string> user_edit_identicon <=> user_edit_view.identicon;
|
in-out property <string> user_edit_identicon <=> user_edit_view.identicon;
|
||||||
|
in-out property <string> user_edit_key_id <=> user_edit_view.key_id;
|
||||||
callback user_edit_compute_identicon <=> user_edit_view.compute_identicon;
|
callback user_edit_compute_identicon <=> user_edit_view.compute_identicon;
|
||||||
|
callback user_edit_compute_key_id <=> user_edit_view.compute_key_id;
|
||||||
callback user_edit_confirm <=> user_edit_view.confirm;
|
callback user_edit_confirm <=> user_edit_view.confirm;
|
||||||
// User Sites View
|
// User Sites View
|
||||||
in property <[StandardListViewItem]> sites <=> user_sites_view.model;
|
in property <[StandardListViewItem]> sites <=> user_sites_view.model;
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,11 @@ export component UserEditView inherits HorizontalLayout {
|
||||||
padding: Style.spacing;
|
padding: Style.spacing;
|
||||||
spacing: Style.spacing;
|
spacing: Style.spacing;
|
||||||
in property <string> username;
|
in property <string> username;
|
||||||
|
in property <string> key_error;
|
||||||
in-out property <string> identicon;
|
in-out property <string> identicon;
|
||||||
callback compute_identicon(encrypted_key: string, password: string);
|
in-out property <string> key_id;
|
||||||
|
callback compute_identicon(password: string);
|
||||||
|
callback compute_key_id(encrypted_key: string);
|
||||||
callback confirm(encrypted_key: string);
|
callback confirm(encrypted_key: string);
|
||||||
callback cancel <=> button_cancel.clicked;
|
callback cancel <=> button_cancel.clicked;
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
|
|
@ -18,25 +21,16 @@ export component UserEditView inherits HorizontalLayout {
|
||||||
text: "Enter " + username + "'s encrypted key and press Enter:";
|
text: "Enter " + username + "'s encrypted key and press Enter:";
|
||||||
}
|
}
|
||||||
|
|
||||||
line_edit_encrypted_key := LineEdit {
|
|
||||||
input-type: InputType.text;
|
|
||||||
placeholder-text: "Encrypted key";
|
|
||||||
edited(text) => {
|
|
||||||
root.identicon = "";
|
|
||||||
}
|
|
||||||
accepted(text) => {
|
|
||||||
line_edit_password.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
line_edit_password := LineEdit {
|
line_edit_password := LineEdit {
|
||||||
input-type: InputType.text;
|
input-type: InputType.password;
|
||||||
placeholder-text: "Password";
|
placeholder-text: "Password";
|
||||||
edited(text) => {
|
edited(text) => {
|
||||||
root.identicon = "";
|
root.identicon = "";
|
||||||
|
root.key_id = "";
|
||||||
}
|
}
|
||||||
accepted(text) => {
|
accepted(text) => {
|
||||||
compute_identicon(line_edit_encrypted_key.text, text);
|
compute_identicon(text);
|
||||||
|
line_edit_password.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,6 +38,23 @@ export component UserEditView inherits HorizontalLayout {
|
||||||
text: identicon.is-empty ? "" : ("Check the identicon: " + identicon);
|
text: identicon.is-empty ? "" : ("Check the identicon: " + identicon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
line_edit_encrypted_key := LineEdit {
|
||||||
|
input-type: InputType.text;
|
||||||
|
placeholder-text: "Encrypted key";
|
||||||
|
enabled: !identicon.is-empty;
|
||||||
|
edited(text) => {
|
||||||
|
root.key_id = "";
|
||||||
|
}
|
||||||
|
accepted(text) => {
|
||||||
|
compute_key_id(text);
|
||||||
|
button_confirm.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: !key_error.is-empty ? key_error : (!key_id.is-empty ? ("Key ID: " + key_id) : "");
|
||||||
|
}
|
||||||
|
|
||||||
HorizontalLayout {
|
HorizontalLayout {
|
||||||
spacing: Style.spacing;
|
spacing: Style.spacing;
|
||||||
button_cancel := Button {
|
button_cancel := Button {
|
||||||
|
|
@ -52,7 +63,7 @@ export component UserEditView inherits HorizontalLayout {
|
||||||
|
|
||||||
button_confirm := Button {
|
button_confirm := Button {
|
||||||
text: "Confirm";
|
text: "Confirm";
|
||||||
enabled: !identicon.is-empty;
|
enabled: !identicon.is-empty && !key_id.is-empty;
|
||||||
clicked => {
|
clicked => {
|
||||||
confirm(line_edit_encrypted_key.text);
|
confirm(line_edit_encrypted_key.text);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue