mz_frontegg_auth/
app_password.rs1use std::error::Error;
11use std::fmt;
12use std::str::FromStr;
13
14use base64::Engine;
15use base64::engine::general_purpose::URL_SAFE_NO_PAD;
16use base64::{
17 alphabet,
18 engine::{GeneralPurpose, GeneralPurposeConfig},
19};
20use serde::Deserialize;
21use uuid::Uuid;
22
23pub const PREFIX: &str = "mzp_";
25
26#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
44pub struct AppPassword {
45 pub client_id: Uuid,
47 pub secret_key: Uuid,
49}
50
51impl fmt::Display for AppPassword {
52 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
53 let mut buf = vec![];
54 buf.extend(self.client_id.as_bytes());
55 buf.extend(self.secret_key.as_bytes());
56 let encoded = URL_SAFE_NO_PAD.encode(buf);
57 f.write_str(PREFIX)?;
58 f.write_str(&encoded)
59 }
60}
61
62impl FromStr for AppPassword {
63 type Err = AppPasswordParseError;
64
65 fn from_str(password: &str) -> Result<AppPassword, AppPasswordParseError> {
66 let password = password.strip_prefix(PREFIX).ok_or(AppPasswordParseError)?;
67 if password.len() == 43 || password.len() == 44 {
68 let url_safe_engine = GeneralPurpose::new(
71 &alphabet::URL_SAFE,
72 GeneralPurposeConfig::new()
73 .with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent),
74 );
75 let buf = url_safe_engine
76 .decode(password)
77 .map_err(|_| AppPasswordParseError)?;
78 let client_id = Uuid::from_slice(&buf[..16]).map_err(|_| AppPasswordParseError)?;
79 let secret_key = Uuid::from_slice(&buf[16..]).map_err(|_| AppPasswordParseError)?;
80 Ok(AppPassword {
81 client_id,
82 secret_key,
83 })
84 } else if password.len() >= 64 {
85 let mut chars = password.chars().filter(|c| c.is_alphanumeric());
89 let client_id = Uuid::parse_str(&chars.by_ref().take(32).collect::<String>())
90 .map_err(|_| AppPasswordParseError)?;
91 let secret_key = Uuid::parse_str(&chars.take(32).collect::<String>())
92 .map_err(|_| AppPasswordParseError)?;
93 Ok(AppPassword {
94 client_id,
95 secret_key,
96 })
97 } else {
98 Err(AppPasswordParseError)
100 }
101 }
102}
103
104#[derive(Clone, Debug)]
106pub struct AppPasswordParseError;
107
108impl Error for AppPasswordParseError {}
109
110impl fmt::Display for AppPasswordParseError {
111 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112 f.write_str("invalid app password format")
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use uuid::Uuid;
119
120 use super::AppPassword;
121
122 #[mz_ore::test]
123 fn test_app_password() {
124 struct TestCase {
125 input: &'static str,
126 expected_output: &'static str,
127 expected_client_id: Uuid,
128 expected_secret_key: Uuid,
129 }
130
131 for tc in [
132 TestCase {
133 input: "mzp_7ce3c1e8ea854594ad5d785f17d1736f1947fdcef5404adb84a47347e5d30c9f",
134 expected_output: "mzp_fOPB6OqFRZStXXhfF9FzbxlH_c71QErbhKRzR-XTDJ8",
135 expected_client_id: "7ce3c1e8-ea85-4594-ad5d-785f17d1736f".parse().unwrap(),
136 expected_secret_key: "1947fdce-f540-4adb-84a4-7347e5d30c9f".parse().unwrap(),
137 },
138 TestCase {
139 input: "mzp_fOPB6OqFRZStXXhfF9FzbxlH_c71QErbhKRzR-XTDJ8",
140 expected_output: "mzp_fOPB6OqFRZStXXhfF9FzbxlH_c71QErbhKRzR-XTDJ8",
141 expected_client_id: "7ce3c1e8-ea85-4594-ad5d-785f17d1736f".parse().unwrap(),
142 expected_secret_key: "1947fdce-f540-4adb-84a4-7347e5d30c9f".parse().unwrap(),
143 },
144 TestCase {
145 input: "mzp_0445db36-5826-41af-84f6-e09402fc6171:a0c11434-07ba-426a-b83d-cc4f192325a3",
146 expected_output: "mzp_BEXbNlgmQa-E9uCUAvxhcaDBFDQHukJquD3MTxkjJaM",
147 expected_client_id: "0445db36-5826-41af-84f6-e09402fc6171".parse().unwrap(),
148 expected_secret_key: "a0c11434-07ba-426a-b83d-cc4f192325a3".parse().unwrap(),
149 },
150 ] {
151 let app_password: AppPassword = tc.input.parse().unwrap();
152 assert_eq!(app_password.to_string(), tc.expected_output);
153 assert_eq!(app_password.client_id, tc.expected_client_id);
154 assert_eq!(app_password.secret_key, tc.expected_secret_key);
155 }
156 }
157}