#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
use crate::session::{crypto::SessionCrypto, data::SessionData};
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
use std::time::Duration;
#[derive(Clone)]
pub(crate) struct SessionCodec {
crypto: Option<SessionCrypto>,
}
impl SessionCodec {
pub fn encrypted(crypto: SessionCrypto) -> Self {
Self {
crypto: Some(crypto),
}
}
pub fn plaintext() -> Self {
Self { crypto: None }
}
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
pub fn encode(&self, data: &SessionData) -> Result<String, SqlStoreError> {
let bytes = rmp_serde::to_vec_named(data)?;
let payload = match &self.crypto {
Some(crypto) => crypto.encrypt(&bytes)?,
None => bytes,
};
Ok(BASE64.encode(payload))
}
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
pub fn decode(&self, stored: &str) -> Result<SessionData, SqlStoreError> {
if self.crypto.is_none() && stored.starts_with('{') {
return Ok(serde_json::from_str(stored)?);
}
let payload = BASE64
.decode(stored)
.map_err(|_| SqlStoreError::Crypto(crate::session::crypto::CryptoError))?;
self.decode_bytes(&payload)
}
#[cfg(feature = "valkey")]
pub fn encode_bytes(&self, data: &SessionData) -> Result<Vec<u8>, SqlStoreError> {
let bytes = rmp_serde::to_vec_named(data)?;
Ok(match &self.crypto {
Some(crypto) => crypto.encrypt(&bytes)?,
None => bytes,
})
}
pub fn decode_bytes(&self, payload: &[u8]) -> Result<SessionData, SqlStoreError> {
let plaintext = match &self.crypto {
Some(crypto) => crypto.decrypt(payload)?,
None => payload.to_vec(),
};
match rmp_serde::from_slice(&plaintext) {
Ok(data) => Ok(data),
Err(_) => Ok(serde_json::from_slice(&plaintext)?),
}
}
}
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
pub(crate) fn expires_at(clock: &dyn axess_clock::Clock, ttl: Duration) -> i64 {
clock
.now()
.timestamp()
.saturating_add(ttl.as_secs().min(i64::MAX as u64) as i64)
}
#[derive(Debug, thiserror::Error)]
pub enum SqlStoreError {
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
#[error("database error: {0}")]
Db(#[from] sqlx::Error),
#[error("session JSON deserialisation failed: {0}")]
Serialization(#[from] serde_json::Error),
#[error("session MessagePack encoding failed: {0}")]
Encode(#[from] rmp_serde::encode::Error),
#[error("session MessagePack decoding failed: {0}")]
Decode(#[from] rmp_serde::decode::Error),
#[error("encryption/decryption error: {0}")]
Crypto(#[from] crate::session::crypto::CryptoError),
}