Microservice to bring 2FA to self hosted PDSes
1use anyhow::Result;
2use chrono::Utc;
3use sqlx::SqlitePool;
4use uuid::Uuid;
5
6#[derive(Debug, Clone, sqlx::FromRow)]
7pub struct AdminSessionRow {
8 pub session_id: String,
9 pub did: String,
10 pub handle: String,
11 pub oauth_session_id: String,
12 pub created_at: String,
13 pub expires_at: String,
14}
15
16/// Creates a new admin session and returns the generated session_id.
17pub async fn create_session(
18 pool: &SqlitePool,
19 did: &str,
20 handle: &str,
21 oauth_session_id: &str,
22 ttl_hours: u64,
23) -> Result<String> {
24 let session_id = Uuid::new_v4().to_string();
25 let now = Utc::now();
26 let created_at = now.to_rfc3339();
27 let expires_at = (now + chrono::Duration::hours(ttl_hours as i64)).to_rfc3339();
28
29 sqlx::query(
30 "INSERT INTO admin_sessions (session_id, did, handle, oauth_session_id, created_at, expires_at) VALUES (?, ?, ?, ?, ?, ?)",
31 )
32 .bind(&session_id)
33 .bind(did)
34 .bind(handle)
35 .bind(oauth_session_id)
36 .bind(&created_at)
37 .bind(&expires_at)
38 .execute(pool)
39 .await?;
40
41 Ok(session_id)
42}
43
44/// Looks up a session by ID. Returns None if the session does not exist or has expired.
45pub async fn get_session(pool: &SqlitePool, session_id: &str) -> Result<Option<AdminSessionRow>> {
46 let now = Utc::now().to_rfc3339();
47
48 let row = sqlx::query_as::<_, AdminSessionRow>(
49 "SELECT session_id, did, handle, oauth_session_id, created_at, expires_at FROM admin_sessions WHERE session_id = ? AND expires_at > ?",
50 )
51 .bind(session_id)
52 .bind(&now)
53 .fetch_optional(pool)
54 .await?;
55
56 Ok(row)
57}
58
59/// Deletes a session by ID.
60pub async fn delete_session(pool: &SqlitePool, session_id: &str) -> Result<()> {
61 sqlx::query("DELETE FROM admin_sessions WHERE session_id = ?")
62 .bind(session_id)
63 .execute(pool)
64 .await?;
65
66 Ok(())
67}
68
69/// Deletes all expired sessions and returns the number of rows removed.
70pub async fn cleanup_expired_sessions(pool: &SqlitePool) -> Result<u64> {
71 let now = Utc::now().to_rfc3339();
72
73 let result = sqlx::query("DELETE FROM admin_sessions WHERE expires_at <= ?")
74 .bind(&now)
75 .execute(pool)
76 .await?;
77
78 Ok(result.rows_affected())
79}