1use crate::common::{EmailAddress, GrantId, Provider};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize)]
10pub struct TokenExchangeRequest {
11 pub code: String,
13
14 pub grant_type: String,
16
17 pub client_id: String,
19
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub client_secret: Option<String>,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub code_verifier: Option<String>,
27
28 pub redirect_uri: String,
30}
31
32#[derive(Debug, Clone, Deserialize)]
34pub struct TokenExchangeResponse {
35 pub access_token: String,
37
38 pub token_type: String,
40
41 pub grant_id: GrantId,
43
44 pub scope: String,
46
47 pub email: EmailAddress,
49
50 pub provider: Provider,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(rename_all = "lowercase")]
57pub enum CodeChallengeMethod {
58 Plain,
60
61 S256,
63}
64
65#[derive(Debug, Clone)]
87pub struct PkceCodePair {
88 pub verifier: String,
90
91 pub challenge: String,
93
94 pub method: CodeChallengeMethod,
96}
97
98impl PkceCodePair {
99 pub fn generate() -> Result<Self, PkceError> {
120 use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
121 use sha2::{Digest, Sha256};
122
123 let mut verifier_bytes = [0u8; 32];
125 getrandom::getrandom(&mut verifier_bytes)
126 .map_err(|e| PkceError::RandomGeneration(e.to_string()))?;
127
128 let verifier = URL_SAFE_NO_PAD.encode(verifier_bytes);
130
131 let mut hasher = Sha256::new();
133 hasher.update(verifier.as_bytes());
134 let challenge_bytes = hasher.finalize();
135
136 let challenge = URL_SAFE_NO_PAD.encode(challenge_bytes);
138
139 Ok(Self {
140 verifier,
141 challenge,
142 method: CodeChallengeMethod::S256,
143 })
144 }
145}
146
147#[derive(Debug, thiserror::Error)]
149pub enum PkceError {
150 #[error("Failed to generate random bytes: {0}")]
152 RandomGeneration(String),
153}
154
155#[derive(Debug, Clone, Serialize)]
157pub struct ProviderDetectRequest {
158 pub email: EmailAddress,
160}
161
162#[derive(Debug, Clone, Deserialize)]
164pub struct ProviderDetectResponse {
165 pub provider: Provider,
167
168 pub detected: bool,
170
171 #[serde(rename = "type")]
173 pub provider_type: String,
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn test_pkce_generation() {
182 let pkce = PkceCodePair::generate().unwrap();
183 assert!(!pkce.verifier.is_empty());
184 assert!(!pkce.challenge.is_empty());
185 assert_eq!(pkce.method, CodeChallengeMethod::S256);
186 }
187
188 #[test]
189 fn test_pkce_verifier_length() {
190 let pkce = PkceCodePair::generate().unwrap();
191 assert_eq!(pkce.verifier.len(), 43);
193 }
194
195 #[test]
196 fn test_pkce_challenge_deterministic() {
197 use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
198 use sha2::{Digest, Sha256};
199
200 let pkce = PkceCodePair::generate().unwrap();
201
202 let mut hasher = Sha256::new();
204 hasher.update(pkce.verifier.as_bytes());
205 let expected_challenge = URL_SAFE_NO_PAD.encode(hasher.finalize());
206
207 assert_eq!(pkce.challenge, expected_challenge);
208 }
209
210 #[test]
211 fn test_token_exchange_request_serialization() {
212 let request = TokenExchangeRequest {
213 code: "auth_code_123".to_string(),
214 grant_type: "authorization_code".to_string(),
215 client_id: "client_123".to_string(),
216 client_secret: Some("secret_123".to_string()),
217 code_verifier: None,
218 redirect_uri: "http://localhost:3000/callback".to_string(),
219 };
220
221 let json = serde_json::to_string(&request).unwrap();
222 assert!(json.contains("auth_code_123"));
223 assert!(json.contains("authorization_code"));
224 assert!(json.contains("client_123"));
225 }
226
227 #[test]
228 fn test_code_challenge_method_serialization() {
229 let s256 = CodeChallengeMethod::S256;
230 let json = serde_json::to_string(&s256).unwrap();
231 assert_eq!(json, "\"s256\"");
232
233 let plain = CodeChallengeMethod::Plain;
234 let json = serde_json::to_string(&plain).unwrap();
235 assert_eq!(json, "\"plain\"");
236 }
237
238 #[test]
239 fn test_provider_detect_request_serialization() {
240 let request = ProviderDetectRequest {
241 email: EmailAddress::new("user@gmail.com").unwrap(),
242 };
243
244 let json = serde_json::to_string(&request).unwrap();
245 assert!(json.contains("user@gmail.com"));
246 }
247}