wasm_oidc_plugin/
session.rs1use aes_gcm::{
3 aead::{Aead, Generate, Nonce},
4 Aes256Gcm,
5};
6
7use base64::{engine::general_purpose::STANDARD_NO_PAD as base64engine, Engine as _};
9
10use log::debug;
12
13use std::convert::TryInto;
15use std::fmt::Debug;
16
17use serde::{Deserialize, Serialize};
19
20use crate::error::PluginError;
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct AuthorizationState {
27 pub access_token: String,
29 pub token_type: String,
31 pub expires_in: u32,
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub refresh_token: Option<String>,
36 pub id_token: String,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct Session {
44 pub issuer: String,
46 pub authorization_state: Option<AuthorizationState>,
48 pub original_path: String,
50 pub code_verifier: String,
52 pub state: String,
54}
55
56impl Session {
57 pub fn encrypt_and_encode(&self, cipher: Aes256Gcm) -> Result<(String, String), PluginError> {
64 let nonce = Nonce::<Aes256Gcm>::generate();
67 let encoded_nonce = base64engine.encode(nonce.as_slice());
68
69 let encrypted_cookie = cipher.encrypt(&nonce, serde_json::to_vec(&self)?.as_slice())?;
71 let encoded_cookie = base64engine.encode(encrypted_cookie.as_slice());
72
73 debug!("encrypted with nonce: {}", encoded_nonce);
74
75 Ok((encoded_cookie, encoded_nonce))
76 }
77
78 pub fn make_cookie_values(
85 encoded_cookie: &str,
86 encoded_nonce: &str,
87 cookie_name: &str,
88 cookie_duration_in_s: u64,
89 ) -> Vec<String> {
90 let cookie_parts = encoded_cookie
92 .as_bytes()
93 .chunks(4000)
94 .map(|chunk| std::str::from_utf8(chunk)
95 .expect("auth_cookie is base64 encoded, which means ASCII, which means one character = one byte, so this is valid"));
96
97 let mut cookie_values = vec![];
98
99 for (i, cookie_part) in cookie_parts.enumerate() {
101 let cookie_value = format!(
102 "{cookie_name}-{i}={cookie_part}; Path=/; Secure; HttpOnly; Max-Age={cookie_duration_in_s}; SameSite=Lax",
103 );
104 cookie_values.push(cookie_value);
105 }
106
107 let num_parts = cookie_values.len();
108 let num_parts_cookie_value = format!(
109 "{cookie_name}-parts={num_parts}; Path=/; Secure; HttpOnly; Max-Age={cookie_duration_in_s}; SameSite=Lax"
110 );
111 cookie_values.push(num_parts_cookie_value);
112
113 let nonce_cookie_value = format!(
115 "{cookie_name}-nonce={encoded_nonce}; Path=/; Secure; HttpOnly; Max-Age={cookie_duration_in_s}; SameSite=Lax",
116 );
117 cookie_values.push(nonce_cookie_value);
118
119 cookie_values
120 }
121
122 pub fn clear_cookie_values(cookie_name: &str, num_parts: u8) -> Vec<String> {
127 let num_parts = num_parts.max(1);
128 let mut cookie_values = Vec::with_capacity(num_parts as usize + 2);
129
130 for i in 0..num_parts {
131 cookie_values.push(format!(
132 "{cookie_name}-{i}=; Path=/; Secure; HttpOnly; Max-Age=0; SameSite=Lax"
133 ));
134 }
135 cookie_values.push(format!(
136 "{cookie_name}-parts=; Path=/; Secure; HttpOnly; Max-Age=0; SameSite=Lax"
137 ));
138 cookie_values.push(format!(
139 "{cookie_name}-nonce=; Path=/; Secure; HttpOnly; Max-Age=0; SameSite=Lax"
140 ));
141
142 cookie_values
143 }
144
145 pub fn make_set_cookie_headers(cookie_values: &[String]) -> Vec<(&'static str, &str)> {
148 let set_cookie_headers: Vec<(&str, &str)> = cookie_values
150 .iter()
151 .map(|v| ("Set-Cookie", v.as_str()))
152 .collect();
153
154 set_cookie_headers
156 }
157
158 pub fn decode_and_decrypt(
163 encoded_cookie: String,
164 cipher: Aes256Gcm,
165 encoded_nonce: String,
166 ) -> Result<Session, PluginError> {
167 debug!("decrypting with nonce: {}", encoded_nonce);
169 let decoded_nonce = base64engine.decode(encoded_nonce.as_bytes())?;
170 let nonce: Nonce<Aes256Gcm> = decoded_nonce
171 .as_slice()
172 .try_into()
173 .map_err(|_| PluginError::CookieValidationError("invalid nonce length".to_string()))?;
174
175 let decoded_cookie = base64engine.decode(encoded_cookie.as_bytes())?;
177
178 let decrypted_cookie = cipher.decrypt(&nonce, decoded_cookie.as_slice())?;
180
181 let state = serde_json::from_slice::<Session>(&decrypted_cookie)?;
183 debug!("state: {state:?}");
184 Ok(state)
185 }
186}