totp_rs/
url_error.rs

1#[cfg(feature = "otpauth")]
2use url::ParseError;
3
4use crate::Rfc6238Error;
5
6/// Errors returned mostly upon decoding URL.
7#[derive(Debug, Eq, PartialEq)]
8pub enum TotpUrlError {
9    /// Couldn't decode URL.
10    #[cfg(feature = "otpauth")]
11    #[cfg_attr(docsrs, doc(cfg(feature = "otpauth")))]
12    Url(ParseError),
13    /// Invalid scheme.
14    Scheme(String),
15    /// Invalid host.
16    Host(String),
17    /// Wrong base32 input.
18    Secret(String),
19    /// Invalid secret size. (Too short?)
20    SecretSize(usize),
21    /// Unknown algorithm.
22    Algorithm(String),
23    /// Characters should only be digits.
24    Digits(String),
25    /// Digits should be between 6 and 8.
26    DigitsNumber(usize),
27    /// Couldn't decode step into a number.
28    Step(String),
29    /// Issuer contains invalid character `:`.
30    Issuer(String),
31    /// Couldn't decode issuer.
32    IssuerDecoding(String),
33    /// Issuers should be the same.
34    IssuerMistmatch(String, String),
35    /// Account name contains invalid character `:` or couldn't be decoded.
36    AccountName(String),
37    /// Couldn't parse account name.
38    AccountNameDecoding(String),
39}
40
41impl std::error::Error for TotpUrlError {}
42
43impl std::fmt::Display for TotpUrlError {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        match self {
46            TotpUrlError::AccountName(name) => write!(
47                f,
48                "Account Name can't contain a colon. \"{}\" contains a colon",
49                name
50            ),
51            TotpUrlError::AccountNameDecoding(name) => write!(
52                f,
53                "Couldn't URL decode \"{}\"",
54                name
55            ),
56            TotpUrlError::Algorithm(algo) => write!(
57                f,
58                "Algorithm can only be SHA1, SHA256 or SHA512, not \"{}\"",
59                algo
60            ),
61            TotpUrlError::Digits(digits) => write!(
62                f,
63                "Could not parse \"{}\" as a number.",
64                digits,
65            ),
66            TotpUrlError::DigitsNumber(digits) => write!(
67                f,
68                "Implementations MUST extract a 6-digit code at a minimum and possibly 7 and 8-digit code. {} digits is not allowed",
69                digits,
70            ),
71            TotpUrlError::Host(host) => write!(
72                f,
73                "Host should be totp, not \"{}\"",
74                host
75            ),
76            TotpUrlError::Issuer(issuer) => write!(
77                f,
78                "Issuer can't contain a colon. \"{}\" contains a colon",
79                issuer
80            ),
81            TotpUrlError::IssuerDecoding(issuer) => write!(
82                f,
83                "Couldn't URL decode \"{}\"",
84                issuer
85            ),
86            TotpUrlError::IssuerMistmatch(path_issuer, issuer) => write!(
87                f,
88                "An issuer \"{}\" could be retrieved from the path, but a different issuer \"{}\" was found in the issuer URL parameter",
89                path_issuer,
90                issuer,
91            ),
92            TotpUrlError::Scheme(scheme) => write!(
93                f,
94                "Scheme should be otpauth, not \"{}\"",
95                scheme
96            ),
97            TotpUrlError::Secret(secret) => write!(
98                f,
99                "Secret \"{}\" is not a valid non-padded base32 string",
100                secret,
101            ),
102            TotpUrlError::SecretSize(bits) => write!(
103                f,
104                "The length of the shared secret MUST be at least 128 bits. {} bits is not enough",
105                bits,
106            ),
107            TotpUrlError::Step(step) => write!(
108                f,
109                "Could not parse \"{}\" as a number.",
110                step,
111            ),
112            #[cfg(feature = "otpauth")]
113            TotpUrlError::Url(e) => write!(
114                f,
115                "Error parsing URL: {}",
116                e
117            )
118        }
119    }
120}
121
122impl From<Rfc6238Error> for TotpUrlError {
123    fn from(e: Rfc6238Error) -> Self {
124        match e {
125            Rfc6238Error::InvalidDigits(digits) => TotpUrlError::DigitsNumber(digits),
126            Rfc6238Error::SecretTooSmall(bits) => TotpUrlError::SecretSize(bits),
127        }
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use crate::TotpUrlError;
134
135    #[test]
136    fn account_name() {
137        let error = TotpUrlError::AccountName("Laziz:".to_string());
138        assert_eq!(
139            error.to_string(),
140            "Account Name can't contain a colon. \"Laziz:\" contains a colon"
141        )
142    }
143
144    #[test]
145    fn account_name_decoding() {
146        let error = TotpUrlError::AccountNameDecoding("Laz&iz".to_string());
147        assert_eq!(
148            error.to_string(),
149            "Couldn't URL decode \"Laz&iz\"".to_string()
150        )
151    }
152
153    #[test]
154    fn algorithm() {
155        let error = TotpUrlError::Algorithm("SIKE".to_string());
156        assert_eq!(
157            error.to_string(),
158            "Algorithm can only be SHA1, SHA256 or SHA512, not \"SIKE\"".to_string()
159        )
160    }
161
162    #[test]
163    fn digits() {
164        let error = TotpUrlError::Digits("six".to_string());
165        assert_eq!(
166            error.to_string(),
167            "Could not parse \"six\" as a number.".to_string()
168        )
169    }
170
171    #[test]
172    fn digits_number() {
173        let error = TotpUrlError::DigitsNumber(5);
174        assert_eq!(error.to_string(), "Implementations MUST extract a 6-digit code at a minimum and possibly 7 and 8-digit code. 5 digits is not allowed".to_string())
175    }
176
177    #[test]
178    fn host() {
179        let error = TotpUrlError::Host("hotp".to_string());
180        assert_eq!(
181            error.to_string(),
182            "Host should be totp, not \"hotp\"".to_string()
183        )
184    }
185
186    #[test]
187    fn issuer() {
188        let error = TotpUrlError::Issuer("Iss:uer".to_string());
189        assert_eq!(
190            error.to_string(),
191            "Issuer can't contain a colon. \"Iss:uer\" contains a colon".to_string()
192        )
193    }
194
195    #[test]
196    fn issuer_decoding() {
197        let error = TotpUrlError::IssuerDecoding("iss&uer".to_string());
198        assert_eq!(
199            error.to_string(),
200            "Couldn't URL decode \"iss&uer\"".to_string()
201        )
202    }
203
204    #[test]
205    fn issuer_mismatch() {
206        let error = TotpUrlError::IssuerMistmatch("Google".to_string(), "Github".to_string());
207        assert_eq!(error.to_string(), "An issuer \"Google\" could be retrieved from the path, but a different issuer \"Github\" was found in the issuer URL parameter".to_string())
208    }
209
210    #[test]
211    fn scheme() {
212        let error = TotpUrlError::Scheme("https".to_string());
213        assert_eq!(
214            error.to_string(),
215            "Scheme should be otpauth, not \"https\"".to_string()
216        )
217    }
218
219    #[test]
220    fn secret() {
221        let error = TotpUrlError::Secret("YoLo".to_string());
222        assert_eq!(
223            error.to_string(),
224            "Secret \"YoLo\" is not a valid non-padded base32 string".to_string()
225        )
226    }
227
228    #[test]
229    fn secret_size() {
230        let error = TotpUrlError::SecretSize(112);
231        assert_eq!(
232            error.to_string(),
233            "The length of the shared secret MUST be at least 128 bits. 112 bits is not enough"
234                .to_string()
235        )
236    }
237
238    #[test]
239    #[cfg(feature = "otpauth")]
240    fn step() {
241        let error = TotpUrlError::Url(url::ParseError::EmptyHost);
242        assert_eq!(
243            error.to_string(),
244            "Error parsing URL: empty host".to_string()
245        )
246    }
247}