Skip to main content

pacsea/logic/
long_run_auth.rs

1//! Long-running privilege/auth readiness helpers.
2
3use crate::logic::privilege::{AuthMode, PrivilegeTool};
4use crate::theme::Settings;
5
6/// What: User-facing readiness level for long-running privileged operations.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum LongRunReadiness {
9    /// Configuration is ready with minimal interruption risk.
10    Ready,
11    /// Configuration is workable, but prompts are expected during longer runs.
12    ReadyViaPam,
13    /// Configuration is workable with in-app prompt flow.
14    ReadyWithPrompt,
15    /// Configuration is usable but has elevated interruption risk.
16    Warn,
17    /// Configuration is degraded or unresolved.
18    Degraded,
19}
20
21/// What: Structured readiness evaluation result for long-running operations.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct LongRunAuthReadiness {
24    /// Active privilege tool resolved from settings/environment.
25    pub tool: Option<PrivilegeTool>,
26    /// Effective authentication mode after capability coercion.
27    pub auth_mode: AuthMode,
28    /// Chosen readiness classification.
29    pub readiness: LongRunReadiness,
30    /// Whether the result should show one-time preflight guidance.
31    pub should_warn: bool,
32}
33
34/// What: Evaluate long-running authentication readiness for update/install paths.
35///
36/// Inputs:
37/// - `settings`: Active settings used to resolve effective auth mode.
38///
39/// Output:
40/// - Structured readiness result for caller routing and UX hints.
41#[must_use]
42pub fn evaluate_long_run_auth_readiness(settings: &Settings) -> LongRunAuthReadiness {
43    let auth_mode = crate::logic::password::resolve_auth_mode(settings);
44    let tool = crate::logic::privilege::resolve_privilege_tool(settings.privilege_mode).ok();
45    let readiness = match (tool, auth_mode) {
46        (Some(PrivilegeTool::Sudo | PrivilegeTool::Doas), AuthMode::Prompt) => {
47            LongRunReadiness::ReadyWithPrompt
48        }
49        (Some(PrivilegeTool::Sudo | PrivilegeTool::Doas), AuthMode::PasswordlessOnly) => {
50            LongRunReadiness::Ready
51        }
52        (Some(PrivilegeTool::Sudo), AuthMode::Interactive) => LongRunReadiness::Warn,
53        (Some(PrivilegeTool::Doas), AuthMode::Interactive) => LongRunReadiness::ReadyViaPam,
54        (None, _) => LongRunReadiness::Degraded,
55    };
56    let should_warn = matches!(
57        readiness,
58        LongRunReadiness::Warn | LongRunReadiness::Degraded
59    );
60    LongRunAuthReadiness {
61        tool,
62        auth_mode,
63        readiness,
64        should_warn,
65    }
66}
67
68/// What: Build localized preflight guidance lines for at-risk long-running auth paths.
69///
70/// Inputs:
71/// - `app`: Application state used for i18n.
72///
73/// Output:
74/// - Multi-line warning text suitable for alert/toast usage.
75#[must_use]
76pub fn build_long_run_warning_message(app: &crate::state::AppState) -> String {
77    [
78        crate::i18n::t(app, "app.modals.doas_persist_setup.preflight_warning_1"),
79        crate::i18n::t(app, "app.modals.doas_persist_setup.preflight_warning_2"),
80        crate::i18n::t(app, "app.modals.doas_persist_setup.preflight_warning_3"),
81    ]
82    .join("\n")
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn readiness_is_degraded_when_tool_unresolved() {
91        let _guard = crate::global_test_mutex_lock();
92        unsafe {
93            std::env::set_var("PACSEA_INTEGRATION_TEST", "1");
94            std::env::set_var("PACSEA_TEST_PRIVILEGE_AVAILABLE", "none");
95        }
96        let settings = crate::theme::Settings::default();
97        let result = evaluate_long_run_auth_readiness(&settings);
98        unsafe {
99            std::env::remove_var("PACSEA_TEST_PRIVILEGE_AVAILABLE");
100            std::env::remove_var("PACSEA_INTEGRATION_TEST");
101        }
102        assert_eq!(result.readiness, LongRunReadiness::Degraded);
103        assert!(result.should_warn);
104    }
105
106    #[test]
107    fn readiness_is_ready_via_pam_for_doas_interactive() {
108        let _guard = crate::global_test_mutex_lock();
109        unsafe {
110            std::env::set_var("PACSEA_INTEGRATION_TEST", "1");
111            std::env::set_var("PACSEA_TEST_PRIVILEGE_AVAILABLE", "doas");
112            std::env::set_var("PACSEA_TEST_AUTH_MODE", "interactive");
113        }
114        let settings = crate::theme::Settings::default();
115        let result = evaluate_long_run_auth_readiness(&settings);
116        unsafe {
117            std::env::remove_var("PACSEA_TEST_AUTH_MODE");
118            std::env::remove_var("PACSEA_TEST_PRIVILEGE_AVAILABLE");
119            std::env::remove_var("PACSEA_INTEGRATION_TEST");
120        }
121        assert_eq!(result.readiness, LongRunReadiness::ReadyViaPam);
122        assert!(!result.should_warn);
123    }
124
125    #[test]
126    fn warning_message_is_three_non_empty_lines() {
127        let app = crate::state::AppState::default();
128        let message = build_long_run_warning_message(&app);
129        let parts: Vec<&str> = message.lines().collect();
130        assert_eq!(parts.len(), 3);
131        assert!(parts.iter().all(|line| !line.trim().is_empty()));
132    }
133}