authkestra_engine/auth/
state.rs1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Identity {
8 pub provider_id: String,
10 pub external_id: String,
12 pub email: Option<String>,
14 pub username: Option<String>,
16 pub attributes: HashMap<String, String>,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct OAuthToken {
23 pub access_token: String,
25 pub token_type: String,
27 #[serde(default, skip_serializing_if = "Option::is_none")]
29 pub expires_in: Option<u64>,
30 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub refresh_token: Option<String>,
33 #[serde(default, skip_serializing_if = "Option::is_none")]
35 pub scope: Option<String>,
36 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub id_token: Option<String>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct OAuth2State {
44 pub state: String,
46 #[serde(default, skip_serializing_if = "Option::is_none")]
48 pub nonce: Option<String>,
49 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub code_verifier: Option<String>,
52 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub success_url: Option<String>,
55 pub provider_id: String,
57 pub expires_at: i64,
59}
60
61impl OAuth2State {
62 pub fn encrypt(&self, key: &[u8; 32]) -> Result<String, crate::auth::error::AuthError> {
64 use aes_gcm::{
65 aead::{Aead, KeyInit},
66 Aes256Gcm, Nonce,
67 };
68 use rand::RngCore;
69
70 let cipher = Aes256Gcm::new(key.into());
71 let mut nonce_bytes = [0u8; 12];
72 rand::rng().fill_bytes(&mut nonce_bytes);
73 let nonce = Nonce::from(nonce_bytes);
74
75 let json = serde_json::to_vec(self).map_err(|e| {
76 crate::auth::error::AuthError::Token(format!("Failed to serialize state: {e}"))
77 })?;
78
79 let ciphertext = cipher
80 .encrypt(&nonce, json.as_slice())
81 .map_err(|e| crate::auth::error::AuthError::Token(format!("Encryption failed: {e}")))?;
82
83 let mut combined = Vec::with_capacity(nonce_bytes.len() + ciphertext.len());
84 combined.extend_from_slice(&nonce_bytes);
85 combined.extend_from_slice(&ciphertext);
86
87 Ok(base64::Engine::encode(
88 &base64::engine::general_purpose::STANDARD,
89 combined,
90 ))
91 }
92
93 pub fn decrypt(encoded: &str, key: &[u8; 32]) -> Result<Self, crate::auth::error::AuthError> {
95 use aes_gcm::{
96 aead::{Aead, KeyInit},
97 Aes256Gcm, Nonce,
98 };
99
100 let combined = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, encoded)
101 .map_err(|e| {
102 crate::auth::error::AuthError::Token(format!("Failed to decode base64 state: {e}"))
103 })?;
104
105 if combined.len() < 12 {
106 return Err(crate::auth::error::AuthError::Token(
107 "Invalid encrypted state".to_string(),
108 ));
109 }
110
111 let (nonce_bytes, ciphertext) = combined.split_at(12);
112 let nonce_arr: [u8; 12] = nonce_bytes.try_into().map_err(|_| {
113 crate::auth::error::AuthError::Token("Invalid nonce length".to_string())
114 })?;
115 let nonce = Nonce::from(nonce_arr);
116 let cipher = Aes256Gcm::new(key.into());
117
118 let decrypted = cipher
119 .decrypt(&nonce, ciphertext)
120 .map_err(|e| crate::auth::error::AuthError::Token(format!("Decryption failed: {e}")))?;
121
122 let state: Self = serde_json::from_slice(&decrypted).map_err(|e| {
123 crate::auth::error::AuthError::Token(format!("Failed to deserialize state: {e}"))
124 })?;
125
126 if chrono::Utc::now().timestamp() > state.expires_at {
127 return Err(crate::auth::error::AuthError::Token(
128 "State expired".to_string(),
129 ));
130 }
131
132 Ok(state)
133 }
134}