1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
// aes_gcm
use aes_gcm::{
aead::{AeadMut, OsRng},
AeadCore, Aes256Gcm,
};
// base64
use base64::{engine::general_purpose::STANDARD_NO_PAD as base64engine, Engine as _};
// log
use log::debug;
// std
use std::fmt::Debug;
// serde
use serde::{Deserialize, Serialize};
use crate::error::PluginError;
/// Struct parse the cookie from the request into a struct in order to access the fields and
/// also to save the cookie on the client side
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthorizationState {
/// Access token to be used for requests to the API
pub access_token: String,
/// Type of the access token
pub token_type: String,
/// Time in seconds until the access token expires
pub expires_in: u32,
/// Refresh token to be used to refresh the access token
#[serde(skip_serializing_if = "Option::is_none")]
pub refresh_token: Option<String>,
/// ID token in JWT format
pub id_token: String,
}
/// Struct that holds all information about the current session including the authorization state,
/// the original path, the PKCE code verifier and the state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Session {
/// Authorization state
pub authorization_state: Option<AuthorizationState>,
/// Original Path to which the user should be redirected after login
pub original_path: String,
/// PKCE Code Verifier used to generate the PKCE Code Challenge
pub code_verifier: String,
/// State used to prevent CSRF attacks
pub state: String,
}
impl Session {
/// Create a new session, encrypt it and encode it by using the given cipher
/// * `cipher` - Cipher used to encrypt the cookie
///
/// Returns:
/// * the base64 encoded encrypted session data
/// * the base64 encoded nonce needed to decrypt it
pub fn encrypt_and_encode(
&self,
mut cipher: Aes256Gcm,
) -> Result<(String, String), PluginError> {
// Generate nonce and encode it
// We generate the nonce here to make sure we never encrypt with the same nonce twice
let nonce = Aes256Gcm::generate_nonce(OsRng);
let encoded_nonce = base64engine.encode(nonce.as_slice());
// Encrypt and encode cookie
let encrypted_cookie = cipher.encrypt(&nonce, serde_json::to_vec(&self)?.as_slice())?;
let encoded_cookie = base64engine.encode(encrypted_cookie.as_slice());
debug!("encrypted with nonce: {}", &encoded_nonce);
Ok((encoded_cookie, encoded_nonce))
}
/// Make the cookie values from the encoded cookie by splitting it into chunks of 4000 bytes and
/// then building the values to be set in the Set-Cookie headers
/// * `encoded_cookie` - Encoded cookie to be split into chunks of 4000 bytes
/// * `encoded_nonce` - Base64 encoded nonce needed to decrypt the cookie
/// * `cookie_name` - Name of the cookie
/// * `cookie_duration` - Duration of the cookie in seconds
/// * `number_current_cookies` - Number of cookies that are currently set (important because otherwise decryption will fail if older and expired cookies are still present)
pub fn make_cookie_values(
encoded_cookie: &str,
encoded_nonce: &str,
cookie_name: &str,
cookie_duration: u64,
number_current_cookies: u64,
) -> Vec<String> {
// Split every 4000 bytes
let cookie_parts = encoded_cookie
.as_bytes()
.chunks(4000)
.map(|chunk| std::str::from_utf8(chunk)
.expect("auth_cookie is base64 encoded, which means ASCII, which means one character = one byte, so this is valid"));
let mut cookie_values = vec![];
// Build the cookie values
for (i, cookie_part) in cookie_parts.enumerate() {
let cookie_value = format!(
"{}-{}={}; Path=/; HttpOnly; Secure; Max-Age={}",
cookie_name, i, cookie_part, cookie_duration
);
cookie_values.push(cookie_value);
}
// Build nonce cookie value
let nonce_cookie_value = format!(
"{}-nonce={}; Path=/; HttpOnly; Secure; Max-Age={}; ",
cookie_name, &encoded_nonce, cookie_duration
);
cookie_values.push(nonce_cookie_value);
// Overwrite the old cookies because decryption will fail if older and expired cookies are
// still present.
for i in cookie_values.len()..number_current_cookies as usize {
cookie_values.push(format!(
"{}-{}=; Path=/; HttpOnly; Secure; Max-Age=0",
cookie_name, i
));
}
cookie_values
}
/// Make the Set-Cookie headers from the cookie values
/// * `cookie_values` - Cookie values to be set in the Set-Cookie headers
pub fn make_set_cookie_headers(cookie_values: &[String]) -> Vec<(&'static str, &str)> {
// Build the cookie headers
let set_cookie_headers: Vec<(&str, &str)> = cookie_values
.iter()
.map(|v| ("Set-Cookie", v.as_str()))
.collect();
// Return the cookie headers
set_cookie_headers
}
/// Decode cookie, parse into a struct in order to access the fields
/// * `encoded_cookie` - Encoded cookie to be decoded and parsed into a struct
/// * `cipher` - Cipher used to decrypt the cookie
/// * `encoded_nonce` - Nonce used to decrypt the cookie
pub fn decode_and_decrypt(
encoded_cookie: String,
mut cipher: Aes256Gcm,
encoded_nonce: String,
) -> Result<Session, PluginError> {
// Decode nonce using base64
debug!("decrypting with nonce: {}", encoded_nonce);
let decoded_nonce = base64engine.decode(encoded_nonce.as_bytes())?;
let nonce = aes_gcm::Nonce::from_slice(decoded_nonce.as_slice());
// Decode cookie using base64
let decoded_cookie = base64engine.decode(encoded_cookie.as_bytes())?;
// Decrypt with cipher
let decrypted_cookie = cipher.decrypt(nonce, decoded_cookie.as_slice())?;
// Parse cookie into a struct
let state = serde_json::from_slice::<Session>(&decrypted_cookie)?;
debug!("state: {:?}", state);
Ok(state)
}
}