Skip to main content

openvpn_mgmt_codec/
auth.rs

1use std::str::FromStr;
2
3/// Error returned when a string is not a recognized auth type.
4#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
5#[error("unrecognized auth type: {0:?}")]
6pub struct ParseAuthTypeError(pub String);
7
8/// Error returned when a string is not a recognized auth retry mode.
9#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
10#[error("unrecognized auth retry mode: {0:?}")]
11pub struct ParseAuthRetryModeError(pub String);
12
13/// Authentication credential type. OpenVPN identifies credential requests
14/// by a quoted type string — usually `"Auth"` or `"Private Key"`, but
15/// plugins can define custom types.
16#[derive(Debug, Clone, PartialEq, Eq, strum::Display)]
17pub enum AuthType {
18    /// Standard `--auth-user-pass` credentials. Wire: `"Auth"`.
19    Auth,
20
21    /// Private key passphrase (encrypted key file). Wire: `"Private Key"`.
22    #[strum(to_string = "Private Key")]
23    PrivateKey,
24
25    /// HTTP proxy credentials. Wire: `"HTTP Proxy"`.
26    #[strum(to_string = "HTTP Proxy")]
27    HttpProxy,
28
29    /// SOCKS proxy credentials. Wire: `"SOCKS Proxy"`.
30    #[strum(to_string = "SOCKS Proxy")]
31    SocksProxy,
32
33    /// Plugin-defined or otherwise unrecognized auth type.
34    #[strum(default)]
35    Unknown(String),
36}
37
38impl FromStr for AuthType {
39    type Err = ParseAuthTypeError;
40
41    /// Parse a recognized auth type string.
42    ///
43    /// Recognized values: `Auth`, `PrivateKey` / `Private Key`,
44    /// `HTTPProxy` / `HTTP Proxy`, `SOCKSProxy` / `SOCKS Proxy`.
45    /// Returns `Err` for anything else — use [`AuthType::Unknown`] explicitly
46    /// if forward-compatible fallback is desired.
47    fn from_str(s: &str) -> Result<Self, Self::Err> {
48        match s {
49            "Auth" => Ok(Self::Auth),
50            "PrivateKey" | "Private Key" => Ok(Self::PrivateKey),
51            "HTTPProxy" | "HTTP Proxy" => Ok(Self::HttpProxy),
52            "SOCKSProxy" | "SOCKS Proxy" => Ok(Self::SocksProxy),
53            other => Err(ParseAuthTypeError(other.to_string())),
54        }
55    }
56}
57
58/// Controls how OpenVPN retries after authentication failure.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display)]
60#[strum(serialize_all = "lowercase")]
61pub enum AuthRetryMode {
62    /// Don't retry — exit on auth failure.
63    None,
64
65    /// Retry, re-prompting for credentials.
66    Interact,
67
68    /// Retry without re-prompting.
69    #[strum(to_string = "nointeract")]
70    NoInteract,
71}
72
73impl FromStr for AuthRetryMode {
74    type Err = ParseAuthRetryModeError;
75
76    /// Parse an auth-retry mode: `none`, `interact`, or `nointeract`.
77    fn from_str(s: &str) -> Result<Self, Self::Err> {
78        match s {
79            "none" => Ok(Self::None),
80            "interact" => Ok(Self::Interact),
81            "nointeract" => Ok(Self::NoInteract),
82            other => Err(ParseAuthRetryModeError(other.to_string())),
83        }
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn auth_type_roundtrip() {
93        for at in [
94            AuthType::Auth,
95            AuthType::PrivateKey,
96            AuthType::HttpProxy,
97            AuthType::SocksProxy,
98        ] {
99            let s = at.to_string();
100            assert_eq!(s.parse::<AuthType>().unwrap(), at);
101        }
102    }
103
104    #[test]
105    fn auth_type_aliases() {
106        assert_eq!(
107            "PrivateKey".parse::<AuthType>().unwrap(),
108            AuthType::PrivateKey
109        );
110        assert_eq!(
111            "Private Key".parse::<AuthType>().unwrap(),
112            AuthType::PrivateKey
113        );
114        assert_eq!(
115            "HTTPProxy".parse::<AuthType>().unwrap(),
116            AuthType::HttpProxy
117        );
118        assert_eq!(
119            "SOCKSProxy".parse::<AuthType>().unwrap(),
120            AuthType::SocksProxy
121        );
122    }
123
124    #[test]
125    fn auth_type_unknown_is_err() {
126        assert!("MyPlugin".parse::<AuthType>().is_err());
127    }
128
129    #[test]
130    fn auth_type_unknown_falls_back() {
131        let s = "MyPlugin";
132        let at: AuthType = s
133            .parse()
134            .unwrap_or_else(|_| AuthType::Unknown(s.to_string()));
135        assert_eq!(at, AuthType::Unknown("MyPlugin".to_string()));
136    }
137
138    #[test]
139    fn auth_retry_roundtrip() {
140        for mode in [
141            AuthRetryMode::None,
142            AuthRetryMode::Interact,
143            AuthRetryMode::NoInteract,
144        ] {
145            let s = mode.to_string();
146            assert_eq!(s.parse::<AuthRetryMode>().unwrap(), mode);
147        }
148    }
149
150    #[test]
151    fn auth_retry_invalid() {
152        assert!("bogus".parse::<AuthRetryMode>().is_err());
153    }
154}