aboutsummaryrefslogtreecommitdiff
path: root/server/src/routes/ui/account/session/token.rs
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/routes/ui/account/session/token.rs')
-rw-r--r--server/src/routes/ui/account/session/token.rs72
1 files changed, 72 insertions, 0 deletions
diff --git a/server/src/routes/ui/account/session/token.rs b/server/src/routes/ui/account/session/token.rs
new file mode 100644
index 0000000..d4546aa
--- /dev/null
+++ b/server/src/routes/ui/account/session/token.rs
@@ -0,0 +1,72 @@
+use super::SessionData;
+use aes_gcm_siv::{
+ aead::{generic_array::GenericArray, Aead},
+ KeyInit,
+};
+use anyhow::anyhow;
+use base64::Engine;
+use chrono::{Duration, Utc};
+use std::sync::LazyLock;
+
+static SESSION_KEY: LazyLock<[u8; 32]> = LazyLock::new(|| [(); 32].map(|_| rand::random()));
+
+pub fn create(username: String, expire: Duration) -> String {
+ let session_data = SessionData {
+ expire: Utc::now() + expire,
+ username,
+ };
+ let mut plaintext =
+ bincode::serde::encode_to_vec(&session_data, bincode::config::standard()).unwrap();
+
+ while plaintext.len() % 16 == 0 {
+ plaintext.push(0);
+ }
+
+ let cipher = aes_gcm_siv::Aes256GcmSiv::new_from_slice(&*SESSION_KEY).unwrap();
+ let nonce = [(); 12].map(|_| rand::random());
+ eprintln!("SESSION_KEY={SESSION_KEY:?}");
+ let mut ciphertext = cipher
+ .encrypt(&GenericArray::from(nonce), plaintext.as_slice())
+ .unwrap();
+ ciphertext.extend(nonce);
+
+ base64::engine::general_purpose::STANDARD.encode(&ciphertext)
+}
+
+pub fn validate(token: &str) -> anyhow::Result<String> {
+ let ciphertext = base64::engine::general_purpose::STANDARD.decode(token)?;
+ let cipher = aes_gcm_siv::Aes256GcmSiv::new_from_slice(&*SESSION_KEY).unwrap();
+ let (ciphertext, nonce) = ciphertext.split_at(ciphertext.len() - 12);
+ let plaintext = cipher
+ .decrypt(nonce.into(), ciphertext)
+ .map_err(|e| anyhow!("decryption failed: {e:?}"))?;
+
+ let (session_data, _): (SessionData, _) =
+ bincode::serde::decode_from_slice(&plaintext, bincode::config::standard())?;
+
+ if session_data.expire < Utc::now() {
+ Err(anyhow!("session expired"))?
+ }
+
+ Ok(session_data.username)
+}
+
+#[test]
+fn test() {
+ let tok = create("blub".to_string(), Duration::days(1));
+ validate(&tok).unwrap();
+}
+
+#[test]
+fn test_crypto() {
+ let nonce = [(); 12].map(|_| rand::random());
+ let cipher = aes_gcm_siv::Aes256GcmSiv::new_from_slice(&*SESSION_KEY).unwrap();
+ let plaintext = b"testing stuff---";
+ let ciphertext = cipher
+ .encrypt(&GenericArray::from(nonce), plaintext.as_slice())
+ .unwrap();
+ let plaintext2 = cipher
+ .decrypt((&nonce).into(), ciphertext.as_slice())
+ .unwrap();
+ assert_eq!(plaintext, plaintext2.as_slice());
+}