aboutsummaryrefslogtreecommitdiff
path: root/logic/src/session.rs
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-04-28 18:27:03 +0200
committermetamuffin <metamuffin@disroot.org>2025-04-28 18:27:03 +0200
commit51761cbdefa39107b9e1f931f1aa8df6aebb2a94 (patch)
tree957ca180786ece777e6e1153ada91da741d845ec /logic/src/session.rs
parent80d28b764c95891551e28c395783f5ff9d065743 (diff)
downloadjellything-51761cbdefa39107b9e1f931f1aa8df6aebb2a94.tar
jellything-51761cbdefa39107b9e1f931f1aa8df6aebb2a94.tar.bz2
jellything-51761cbdefa39107b9e1f931f1aa8df6aebb2a94.tar.zst
many much more generic refactor
Diffstat (limited to 'logic/src/session.rs')
-rw-r--r--logic/src/session.rs112
1 files changed, 112 insertions, 0 deletions
diff --git a/logic/src/session.rs b/logic/src/session.rs
new file mode 100644
index 0000000..bc7f137
--- /dev/null
+++ b/logic/src/session.rs
@@ -0,0 +1,112 @@
+/*
+ This file is part of jellything (https://codeberg.org/metamuffin/jellything)
+ which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
+ Copyright (C) 2025 metamuffin <metamuffin.org>
+*/
+use aes_gcm_siv::{
+ KeyInit,
+ aead::{Aead, generic_array::GenericArray},
+};
+use anyhow::anyhow;
+use base64::Engine;
+use jellybase::SECRETS;
+use jellycommon::{
+ chrono::{DateTime, Utc},
+ user::{PermissionSet, User},
+};
+use log::warn;
+use serde::{Deserialize, Serialize};
+use std::{sync::LazyLock, time::Duration};
+
+pub struct Session {
+ pub user: User,
+}
+
+pub struct AdminSession(pub Session);
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct SessionData {
+ username: String,
+ expire: DateTime<Utc>,
+ permissions: PermissionSet,
+}
+
+static SESSION_KEY: LazyLock<[u8; 32]> = LazyLock::new(|| {
+ if let Some(sk) = &SECRETS.session_key {
+ let r = base64::engine::general_purpose::STANDARD
+ .decode(sk)
+ .expect("key invalid; should be valid base64");
+ r.try_into()
+ .expect("key has the wrong length; should be 32 bytes")
+ } else {
+ warn!("session_key not configured; generating a random one.");
+ [(); 32].map(|_| rand::random())
+ }
+});
+
+pub fn create(username: String, permissions: PermissionSet, expire: Duration) -> String {
+ let session_data = SessionData {
+ expire: Utc::now() + expire,
+ username: username.to_owned(),
+ permissions,
+ };
+ 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());
+ let mut ciphertext = cipher
+ .encrypt(&GenericArray::from(nonce), plaintext.as_slice())
+ .unwrap();
+ ciphertext.extend(nonce);
+
+ base64::engine::general_purpose::URL_SAFE.encode(&ciphertext)
+}
+
+pub fn validate(token: &str) -> anyhow::Result<String> {
+ let ciphertext = base64::engine::general_purpose::URL_SAFE.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() {
+ jellybase::use_test_config();
+ let tok = create(
+ "blub".to_string(),
+ jellycommon::user::PermissionSet::default(),
+ Duration::from_days(1),
+ );
+ validate(&tok).unwrap();
+}
+
+#[test]
+fn test_crypto() {
+ jellybase::use_test_config();
+ 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());
+}