1use std::fmt;
2
3use zeroize::Zeroize;
4
5#[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#[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 pub auth_session_id: Option<String>,
55}
56
57#[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#[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}