From a503b2a36fca3a3f7fb4d79b99efa9b2154d3fdeec50e8fcff56744bb82a3980 Mon Sep 17 00:00:00 2001 From: Kira Date: Sat, 30 May 2026 00:33:34 +0200 Subject: [PATCH] Fixes and initial work on security stage. --- src/kira_sysinfo.rs | 2 +- src/locales/en.json | 7 +- src/main.rs | 36 ++++++- src/stages/mod.rs | 1 + src/stages/partition/mod.rs | 15 ++- src/stages/security/mod.rs | 181 ++++++++++++++++++++++++++++++++++++ 6 files changed, 229 insertions(+), 13 deletions(-) create mode 100644 src/stages/security/mod.rs diff --git a/src/kira_sysinfo.rs b/src/kira_sysinfo.rs index ba51827..5d7d2ea 100644 --- a/src/kira_sysinfo.rs +++ b/src/kira_sysinfo.rs @@ -96,7 +96,7 @@ pub fn get_gpus_list() -> Vec { res } -use serde::{Deserialize, Serialize}; +use serde::Deserialize; #[derive(Debug, Clone, Deserialize)] pub struct SecureBootStatus { diff --git a/src/locales/en.json b/src/locales/en.json index 8cca6f2..264f184 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -38,6 +38,11 @@ "partition.use_zram": "Use zram technology", "partition.use_secure_boot": "Setup secure boot", "partition.current_dev_layout": "Current device layout", - "partition.current_after_install": "After installation device will look like this" + "partition.current_after_install": "After installation device will look like this", + "security.user_name": "User login name", + "security.user_full_name": "User full name/description", + "security.user_password": "User password", + "security.reuse_user_password": "Reuse user password for root user", + "security.root_password": "Root password" } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e25aa32..6052ae1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,6 +52,7 @@ enum Views { Locale(stages::locale::LocaleStage), Keyboard(stages::keyboard::KeyboardStage), Partition(stages::partition::PartitionStage), + Security(stages::security::SecurityStage), } enum Message { @@ -63,6 +64,7 @@ enum Message { Locale(stages::locale::Message), Keyboard(stages::keyboard::Message), Partition(stages::partition::Message), + Security(stages::security::Message), } struct KiraState { @@ -111,6 +113,7 @@ fn view(k_state: &KiraState) -> Element<'_, Message> { Views::Locale(locale_stage) => locale_stage.view().map(Message::Locale), Views::Keyboard(keyboard_stage) => keyboard_stage.view().map(Message::Keyboard), Views::Partition(partition_stage) => partition_stage.view().map(Message::Partition), + Views::Security(security_stage) => security_stage.view().map(Message::Security) } } @@ -259,13 +262,14 @@ fn update(k_state: &mut KiraState, message: Message) -> Task { } else { Task::none() } - } + }, Message::Partition(partition_msg) => { if let Views::Partition(partition_view) = &mut k_state.current_view { match partition_view.update(partition_msg) { StageAction::Next(partition_res) => { k_state.config.config_trail.push(partition_res); - iced::exit() + k_state.current_view = Views::Security(stages::security::SecurityStage::new()); + Task::none() } StageAction::Back => { k_state.config.config_trail.pop(); @@ -278,7 +282,33 @@ fn update(k_state: &mut KiraState, message: Message) -> Task { } else { Task::none() } - } + }, + Message::Security(security_msg) => { + if let Views::Security(security_view) = &mut k_state.current_view { + match security_view.update(security_msg) { + StageAction::Next(security_res) => { + k_state.config.config_trail.push(security_res); + Task::none() + } + StageAction::Back => { + k_state.config.config_trail.pop(); + match stages::partition::PartitionStage::new(&k_state.toml_config) { + Ok(part_stage) => { + k_state.current_view = Views::Partition(part_stage); + } + Err(ex) => { + log::error!("Error, cannot init partition stage: {}", ex); + } + } + Task::none() + } + _ => Task::none(), + } + } else { + Task::none() + } + }, + } } diff --git a/src/stages/mod.rs b/src/stages/mod.rs index 6d80d26..b112d03 100644 --- a/src/stages/mod.rs +++ b/src/stages/mod.rs @@ -22,3 +22,4 @@ pub mod timezone; pub mod locale; pub mod keyboard; pub mod partition; +pub mod security; \ No newline at end of file diff --git a/src/stages/partition/mod.rs b/src/stages/partition/mod.rs index 89ca03c..fcef97b 100644 --- a/src/stages/partition/mod.rs +++ b/src/stages/partition/mod.rs @@ -264,22 +264,21 @@ impl PartitionStage { self.device = Some(dev); StageAction::None - } - + }, Message::SelectSwapMode(sm) => { self.swap_mode = Some(sm); self.gen_layout(); StageAction::None - } + }, Message::ToggleSecureBoot(t) => { self.secure_boot = t; StageAction::None - } + }, Message::ToggleZram(t) => { self.use_zram = t; self.gen_layout(); StageAction::None - } + }, Message::SwapSizeIncrement => { self.custom_swap_size_mb = (self.custom_swap_size_mb / 1024u32) .strict_mul(1024u32) @@ -287,7 +286,7 @@ impl PartitionStage { self.custom_swap_size_str = format!("{}MB", self.custom_swap_size_mb); self.gen_layout(); StageAction::None - } + }, Message::SwapSizeDecrement => { self.custom_swap_size_mb = (self.custom_swap_size_mb / 1024u32) .strict_mul(1024u32) @@ -295,7 +294,7 @@ impl PartitionStage { self.custom_swap_size_str = format!("{}MB", self.custom_swap_size_mb); self.gen_layout(); StageAction::None - } + }, Message::SwapSizeFieldChange(f_v) => { let mut only_num = f_v.trim().to_string(); only_num.retain(char::is_numeric); @@ -303,7 +302,7 @@ impl PartitionStage { self.custom_swap_size_str = format!("{}MB", self.custom_swap_size_mb); self.gen_layout(); StageAction::None - } + }, Message::Back => StageAction::Back, Message::Next => StageAction::Next(self.gen_result()), } diff --git a/src/stages/security/mod.rs b/src/stages/security/mod.rs new file mode 100644 index 0000000..6da0841 --- /dev/null +++ b/src/stages/security/mod.rs @@ -0,0 +1,181 @@ +// +// Copyright (C) <2026> + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +/* + This is Security stage, setup users logins and such +*/ + +use crate::{ + kira_theming, + stage::{StageAction, StageResult}, +}; +use iced::{Alignment, widget}; +use rust_i18n::t; + +#[derive(Debug, Clone)] +pub struct SecurityStage { + user_name: String, + user_full_name: String, + user_password: String, + root_password: String, + reuse_user_password_for_roor: bool, + user_pass_is_secure: bool, + root_pass_is_secure: bool, +} + +#[derive(Debug, Clone)] +pub enum Message { + ToggleReusePass(bool), + UserNameChange(String), + UserFullNameChange(String), + UserPasswordChange(String), + RootPasswordChange(String), + ToggleUserPassSecure, + ToggleRootPassSecure, + Next, + Exit, +} + +/// Mothod safely retain first n chars in utf-8 string avoiding cutting utf-8 char in the middle +fn retain_first_n(s: &str, n: usize) -> &str { + s.char_indices() + .nth(n) + .and_then(|(idx, _)| Some(&s[..idx])) + .unwrap_or(s) +} + +impl SecurityStage { + pub fn new() -> Self { + Self { + user_name: "".into(), + user_full_name: "".into(), + user_password: "".into(), + root_password: "".into(), + reuse_user_password_for_roor: false, + user_pass_is_secure: true, + root_pass_is_secure: true, + } + } + + fn gen_result(&self) -> StageResult { + StageResult::new("security") + .add_val_string("user_name", self.user_name.clone()) + .add_val_string("user_full_name", self.user_full_name.clone()) + .add_val_string("user_full_name", self.user_full_name.clone()) + .add_val_string("user_password", self.user_password.clone()) + .add_val_string("root_password", self.root_password.clone()) + } + + pub fn update(&mut self, message: Message) -> StageAction { + match message { + Message::ToggleReusePass(v) => { + self.reuse_user_password_for_roor = v; + StageAction::None + } + Message::UserNameChange(u_name) => { + let mut trimmed = u_name + .trim_start_matches(|c: char| !c.is_ascii_alphabetic()) + .to_string(); + trimmed.retain(|c| c.is_ascii_alphanumeric() || c == '-'); + trimmed.truncate(30); // it is safe bacuse we retained only ascii characters + self.user_name = trimmed; + StageAction::None + } + Message::UserFullNameChange(f_name) => { + let mut res = f_name.clone(); + res.retain(|c| c.is_alphanumeric() || ['_', '-', ' ', '.', ','].contains(&c)); + self.user_full_name = retain_first_n(res.trim(), 128).to_string(); + StageAction::None + } + Message::UserPasswordChange(u_pass) => { + let mut res = u_pass.trim().to_string(); + res.retain(|c: char| !c.is_control()); + self.user_password = res.trim().to_string(); + if self.reuse_user_password_for_roor { + self.root_password = self.user_password.clone(); + } + StageAction::None + } + Message::RootPasswordChange(r_pass) => { + let mut res = r_pass.trim().to_string(); + res.retain(|c: char| !c.is_control()); + self.root_password = res.trim().to_string(); + StageAction::None + }, + Message::ToggleUserPassSecure => { + self.user_pass_is_secure = !self.user_pass_is_secure; + StageAction::None + }, + Message::ToggleRootPassSecure => { + self.root_pass_is_secure = !self.root_pass_is_secure; + StageAction::None + }, + Message::Exit => StageAction::Abort, + Message::Next => StageAction::Next(self.gen_result()), + } + } + + pub fn view(&self) -> iced::Element<'_, Message> { + let next_button = widget::button(widget::text(t!("button.next"))).on_press(Message::Next); + let exit_button = widget::button(widget::text(t!("button.exit"))).on_press(Message::Exit); + + let user_pass_sec_text = if self.user_pass_is_secure {widget::text("👁")} else {widget::text("*")}; + let root_pass_sec_text = if self.root_pass_is_secure {widget::text("👁")} else {widget::text("*")}; + let mut root_pass_input = widget::text_input("", &self.root_password).secure(self.root_pass_is_secure); + if self.reuse_user_password_for_roor {root_pass_input = root_pass_input.on_input(Message::RootPasswordChange);} + widget::column![ + widget::container( + widget::column![ + widget::text(t!("security.user_name")), + widget::text_input("", &self.user_name).on_input(Message::UserNameChange), + widget::text(t!("security.user_full_name")), + widget::text_input("", &self.user_full_name).on_input(Message::UserFullNameChange), + widget::text(t!("security.user_password")), + widget::row![ + widget::text_input("", &self.user_password).on_input(Message::UserPasswordChange) + .secure(self.user_pass_is_secure), + widget::button(user_pass_sec_text).on_press(Message::ToggleUserPassSecure), + ], + widget::rule::horizontal(2), + widget::text(t!("security.root_password")), + widget::row![ + root_pass_input, + widget::button(root_pass_sec_text).on_press(Message::ToggleRootPassSecure), + ], + + ] + .padding(10) + .spacing(10) + .align_x(Alignment::Center) + ) + .height(iced::Length::Fill) + .width(iced::Length::Fill) + .align_x(Alignment::Center) + .align_y(Alignment::Center), + widget::row![ + exit_button, + widget::space::horizontal(), // Pushes the right button to the far right + next_button + ] + .width(iced::Length::Fill) + .align_y(Alignment::End) + .padding(10), + ] + .align_x(Alignment::Center) + .spacing(10) + .into() + } +}