1pub mod middleware;
12pub mod password;
13pub mod routes;
14
15use std::sync::Arc;
16
17use parking_lot::RwLock;
18
19use koan_core::auth::{Claims, Role};
20use koan_core::db::pool::Pool;
21use koan_core::db::queries::auth as auth_queries;
22
23pub(crate) type Keypair = (Arc<Vec<u8>>, Arc<Vec<u8>>);
25
26static SIGNING: RwLock<Option<Keypair>> = RwLock::new(None);
27
28pub(crate) fn set_signing_keys(private: Arc<Vec<u8>>, public: Arc<Vec<u8>>) {
32 *SIGNING.write() = Some((private, public));
33}
34
35pub(crate) fn signing_keys() -> Result<Keypair, koan_core::auth::AuthError> {
38 if let Some(keys) = SIGNING.read().clone() {
39 return Ok(keys);
40 }
41 let (private, public) = koan_core::auth::load_or_generate_keypair()?;
42 Ok(SIGNING
43 .write()
44 .get_or_insert_with(|| (Arc::new(private), Arc::new(public)))
45 .clone())
46}
47
48#[derive(Debug, Clone)]
50pub struct AuthUser {
51 pub user_id: i64,
52 pub username: String,
53 pub role: Role,
54}
55
56pub(crate) async fn current_user(pool: &Arc<Pool>, claims: Claims) -> Option<AuthUser> {
63 let pool = pool.clone();
64 tokio::task::spawn_blocking(move || {
65 let db = pool.get().ok()?;
66 let user = auth_queries::get_user_by_id(&db.conn, claims.sub).ok()??;
67 (user.username == claims.username).then_some(AuthUser {
68 user_id: user.id,
69 username: user.username,
70 role: user.role,
71 })
72 })
73 .await
74 .ok()
75 .flatten()
76}
77
78pub(crate) async fn key_user(pool: &Arc<Pool>, key: String) -> Option<AuthUser> {
81 let pool = pool.clone();
82 tokio::task::spawn_blocking(move || {
83 let db = pool.get().ok()?;
84 let user =
85 koan_core::db::queries::api_keys::authenticate_api_key(&db.conn, &key).ok()??;
86 Some(AuthUser {
87 user_id: user.id,
88 username: user.username,
89 role: user.role,
90 })
91 })
92 .await
93 .ok()
94 .flatten()
95}
96
97#[derive(Debug, Clone, Copy)]
101pub(crate) struct Lease {
102 pub user_id: i64,
103 pub mark: u64,
104 pub expires: Option<u64>,
106}
107
108impl Lease {
109 pub(crate) async fn ended(self) {
113 let lapsed = async {
114 match self.expires {
115 Some(at) => {
116 let left = at.saturating_sub(koan_core::clock::unix_secs() as u64);
117 tokio::time::sleep(std::time::Duration::from_secs(left)).await;
118 }
119 None => std::future::pending().await,
120 }
121 };
122 tokio::select! {
123 _ = koan_core::auth::account_changed_since(self.user_id, self.mark) => {}
124 _ = lapsed => {}
125 }
126 }
127}
128
129impl AuthUser {
130 pub fn anonymous_admin() -> Self {
132 Self {
133 user_id: 0,
134 username: koan_core::auth::ANONYMOUS.into(),
135 role: Role::Admin,
136 }
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use std::time::Duration;
144
145 #[tokio::test]
146 async fn a_lease_ends_when_its_account_changes_and_no_other() {
147 let lease = Lease {
148 user_id: 9001,
149 mark: koan_core::auth::account_mark(),
150 expires: None,
151 };
152 koan_core::auth::account_changed(9002);
153 assert!(
154 tokio::time::timeout(Duration::from_millis(50), lease.ended())
155 .await
156 .is_err()
157 );
158 koan_core::auth::account_changed(9001);
159 tokio::time::timeout(Duration::from_secs(1), lease.ended())
160 .await
161 .unwrap();
162 }
163
164 #[tokio::test]
165 async fn a_lease_ends_when_its_token_lapses() {
166 let lease = Lease {
167 user_id: 9003,
168 mark: koan_core::auth::account_mark(),
169 expires: Some(koan_core::clock::unix_secs() as u64),
170 };
171 tokio::time::timeout(Duration::from_secs(1), lease.ended())
172 .await
173 .unwrap();
174 }
175}