Skip to main content

limes_proto/
auth.rs

1use std::fmt;
2
3use zeroize::Zeroize;
4
5/// Credentials collected by a frontend and submitted to the backend.
6#[derive(Clone, PartialEq, Eq)]
7pub struct AuthRequest {
8    pub username: String,
9    pub password: String,
10    pub tty: Option<String>,
11}
12
13impl AuthRequest {
14    #[must_use]
15    pub fn new(username: impl Into<String>, password: impl Into<String>) -> Self {
16        Self {
17            username: username.into(),
18            password: password.into(),
19            tty: None,
20        }
21    }
22
23    pub fn clear_secret(&mut self) {
24        self.password.zeroize();
25        self.password.clear();
26    }
27}
28
29impl Drop for AuthRequest {
30    fn drop(&mut self) {
31        self.clear_secret();
32    }
33}
34
35impl fmt::Debug for AuthRequest {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        f.debug_struct("AuthRequest")
38            .field("username", &self.username)
39            .field("password", &"<redacted>")
40            .field("tty", &self.tty)
41            .finish()
42    }
43}
44
45/// Successful authentication result.
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct AuthSuccess {
48    pub username: String,
49    pub uid: u32,
50    pub gid: u32,
51    pub home: Option<String>,
52    pub shell: Option<String>,
53    /// Opaque backend session id used to pair PAM open/close calls.
54    pub auth_session_id: Option<String>,
55}
56
57/// Authentication failure categories frontends can render differently.
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum AuthFailure {
60    InvalidCredentials,
61    LockedOut,
62    BackendUnavailable(String),
63    Internal(String),
64}
65
66impl fmt::Display for AuthFailure {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        match self {
69            Self::InvalidCredentials => f.write_str("invalid credentials"),
70            Self::LockedOut => f.write_str("account locked out"),
71            Self::BackendUnavailable(reason) => {
72                write!(f, "authentication backend unavailable: {reason}")
73            }
74            Self::Internal(reason) => write!(f, "authentication error: {reason}"),
75        }
76    }
77}
78
79pub type AuthOutcome = Result<AuthSuccess, AuthFailure>;
80
81/// PAM conversation message kinds reported by authentication backends.
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum PamMessageKind {
84    PromptEchoOn,
85    PromptEchoOff,
86    TextInfo,
87    Error,
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn auth_request_debug_redacts_password() {
96        let request = AuthRequest::new("alice", "secret");
97
98        let rendered = format!("{request:?}");
99
100        assert!(rendered.contains("alice"));
101        assert!(rendered.contains("<redacted>"));
102        assert!(!rendered.contains("secret"));
103    }
104
105    #[test]
106    fn clear_secret_removes_password_value() {
107        let mut request = AuthRequest::new("alice", "secret");
108
109        request.clear_secret();
110
111        assert!(request.password.is_empty());
112    }
113}