Menu
Public repository / master
AkurAI-Notes / crates/notes-api/src/auth/session.rs
Latest change 21fd83f4a6eb769a0ee34aa30f721b5cdc6881af - Fix IDP OAuth: split public issuer vs internal loopback URL for token exchange by Claude Code
use akurai_json::Value;
use akurai_storage::BTree;
pub struct User {
pub sub: String,
pub email: String,
}
pub fn create(db: &mut BTree, sub: &str, email: &str) -> String {
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hasher};
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let rand = RandomState::new().build_hasher().finish();
let session_id = format!("s_{:x}_{}", rand, now);
let record = Value::Object(vec![
("sub".into(), Value::Str(sub.into())),
("email".into(), Value::Str(email.into())),
("createdAt".into(), Value::Int(now as i64)),
]);
let key = format!("_session:{}", session_id);
let _ = db.insert(key.as_bytes(), record.to_json().as_bytes());
let _ = db.commit();
session_id
}
pub fn extract_session_id(req: &akurai_http::Request) -> Option<String> {
if let Some(auth) = req.header("Authorization") {
if let Some(token) = auth.strip_prefix("Bearer ") {
let t = token.trim();
if !t.is_empty() {
return Some(t.to_string());
}
}
}
if let Some(cookies) = req.header("Cookie") {
for part in cookies.split(';') {
let part = part.trim();
if let Some(value) = part.strip_prefix("notes_session=") {
let v = value.trim();
if !v.is_empty() {
return Some(v.to_string());
}
}
}
}
None
}
pub fn get_user(db: &mut BTree, session_id: &str) -> Option<User> {
let key = format!("_session:{}", session_id);
match db.get(key.as_bytes()) {
Ok(Some(bytes)) => {
let s = String::from_utf8_lossy(&bytes);
if let Ok(val) = akurai_json::parse(&s) {
let sub = val
.get("sub")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let email = val
.get("email")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
Some(User { sub, email })
} else {
None
}
}
_ => None,
}
}