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 enum AuthResult {
23 Success(Identity),
25 MfaRequired {
27 mfa_token: String,
29 user_id: String,
31 allowed_methods: Vec<String>,
33 },
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct MfaTokenClaims {
39 pub sub: String,
41 pub mfa_pending: bool,
43 pub exp: usize,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct OAuthToken {
50 pub access_token: String,
52 pub token_type: String,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub expires_in: Option<u64>,
57 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub refresh_token: Option<String>,
60 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub scope: Option<String>,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub id_token: Option<String>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct OAuth2State {
71 pub state: String,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub nonce: Option<String>,
76 #[serde(default, skip_serializing_if = "Option::is_none")]
78 pub code_verifier: Option<String>,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub success_url: Option<String>,
82 pub provider_id: String,
84 pub expires_at: i64,
86}
87
88impl OAuth2State {
89 pub fn encrypt(&self, key: &[u8; 32]) -> Result<String, crate::auth::error::AuthError> {
91 use aes_gcm::{
92 aead::{Aead, KeyInit},
93 Aes256Gcm, Nonce,
94 };
95 use rand::RngCore;
96
97 let cipher = Aes256Gcm::new(key.into());
98 let mut nonce_bytes = [0u8; 12];
99 rand::rng().fill_bytes(&mut nonce_bytes);
100 let nonce = Nonce::from(nonce_bytes);
101
102 let json = serde_json::to_vec(self).map_err(|e| {
103 crate::auth::error::AuthError::Token(format!("Failed to serialize state: {e}"))
104 })?;
105
106 let ciphertext = cipher
107 .encrypt(&nonce, json.as_slice())
108 .map_err(|e| crate::auth::error::AuthError::Token(format!("Encryption failed: {e}")))?;
109
110 let mut combined = Vec::with_capacity(nonce_bytes.len() + ciphertext.len());
111 combined.extend_from_slice(&nonce_bytes);
112 combined.extend_from_slice(&ciphertext);
113
114 Ok(base64::Engine::encode(
115 &base64::engine::general_purpose::STANDARD,
116 combined,
117 ))
118 }
119
120 pub fn decrypt(encoded: &str, key: &[u8; 32]) -> Result<Self, crate::auth::error::AuthError> {
122 use aes_gcm::{
123 aead::{Aead, KeyInit},
124 Aes256Gcm, Nonce,
125 };
126
127 let combined = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, encoded)
128 .map_err(|e| {
129 crate::auth::error::AuthError::Token(format!("Failed to decode base64 state: {e}"))
130 })?;
131
132 if combined.len() < 12 {
133 return Err(crate::auth::error::AuthError::Token(
134 "Invalid encrypted state".to_string(),
135 ));
136 }
137
138 let (nonce_bytes, ciphertext) = combined.split_at(12);
139 let nonce_arr: [u8; 12] = nonce_bytes.try_into().map_err(|_| {
140 crate::auth::error::AuthError::Token("Invalid nonce length".to_string())
141 })?;
142 let nonce = Nonce::from(nonce_arr);
143 let cipher = Aes256Gcm::new(key.into());
144
145 let decrypted = cipher
146 .decrypt(&nonce, ciphertext)
147 .map_err(|e| crate::auth::error::AuthError::Token(format!("Decryption failed: {e}")))?;
148
149 let state: Self = serde_json::from_slice(&decrypted).map_err(|e| {
150 crate::auth::error::AuthError::Token(format!("Failed to deserialize state: {e}"))
151 })?;
152
153 if chrono::Utc::now().timestamp() > state.expires_at {
154 return Err(crate::auth::error::AuthError::Token(
155 "State expired".to_string(),
156 ));
157 }
158
159 Ok(state)
160 }
161}