pub mod middleware;
pub mod password;
pub mod routes;
use std::sync::{Arc, OnceLock};
use koan_core::auth::{Claims, Role};
use koan_core::db::pool::Pool;
use koan_core::db::queries::auth as auth_queries;
pub(crate) type Keypair = (Arc<Vec<u8>>, Arc<Vec<u8>>);
static SIGNING: OnceLock<Keypair> = OnceLock::new();
pub(crate) fn set_signing_keys(private: Arc<Vec<u8>>, public: Arc<Vec<u8>>) {
let _ = SIGNING.set((private, public));
}
pub(crate) fn signing_keys() -> Result<&'static Keypair, koan_core::auth::AuthError> {
if let Some(keys) = SIGNING.get() {
return Ok(keys);
}
let (private, public) = koan_core::auth::load_or_generate_keypair()?;
Ok(SIGNING.get_or_init(|| (Arc::new(private), Arc::new(public))))
}
#[derive(Debug, Clone)]
pub struct AuthUser {
pub user_id: i64,
pub username: String,
pub role: Role,
}
pub(crate) async fn current_user(pool: &Arc<Pool>, claims: Claims) -> Option<AuthUser> {
let pool = pool.clone();
tokio::task::spawn_blocking(move || {
let db = pool.get().ok()?;
let user = auth_queries::get_user_by_id(&db.conn, claims.sub).ok()??;
(user.username == claims.username).then_some(AuthUser {
user_id: user.id,
username: user.username,
role: user.role,
})
})
.await
.ok()
.flatten()
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Lease {
pub user_id: i64,
pub mark: u64,
pub expires: Option<u64>,
}
impl Lease {
pub(crate) async fn ended(self) {
let lapsed = async {
match self.expires {
Some(at) => {
let left = at.saturating_sub(koan_core::auth::now_unix());
tokio::time::sleep(std::time::Duration::from_secs(left)).await;
}
None => std::future::pending().await,
}
};
tokio::select! {
_ = koan_core::auth::account_changed_since(self.user_id, self.mark) => {}
_ = lapsed => {}
}
}
}
impl AuthUser {
pub fn anonymous_admin() -> Self {
Self {
user_id: 0,
username: koan_core::auth::ANONYMOUS.into(),
role: Role::Admin,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[tokio::test]
async fn a_lease_ends_when_its_account_changes_and_no_other() {
let lease = Lease {
user_id: 9001,
mark: koan_core::auth::account_mark(),
expires: None,
};
koan_core::auth::account_changed(9002);
assert!(
tokio::time::timeout(Duration::from_millis(50), lease.ended())
.await
.is_err()
);
koan_core::auth::account_changed(9001);
tokio::time::timeout(Duration::from_secs(1), lease.ended())
.await
.unwrap();
}
#[tokio::test]
async fn a_lease_ends_when_its_token_lapses() {
let lease = Lease {
user_id: 9003,
mark: koan_core::auth::account_mark(),
expires: Some(koan_core::auth::now_unix()),
};
tokio::time::timeout(Duration::from_secs(1), lease.ended())
.await
.unwrap();
}
}