based/src/auth/session.rs
2025-01-09 22:36:30 +01:00

118 lines
3.6 KiB
Rust

use chrono::Utc;
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use crate::get_pg;
use super::{User, UserRole, gen_token};
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct Session {
/// The unique ID of the session token
pub id: uuid::Uuid,
/// The generated session token
pub token: String,
/// The username associated with the session token
pub user: String,
/// Session creation time
pub created: chrono::DateTime<Utc>,
/// Internal CSRF value
csrf: uuid::Uuid,
/// Named session value
pub name: Option<String>,
/// Kind of session
pub kind: SessionKind,
}
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type)]
#[sqlx(type_name = "session_kind", rename_all = "lowercase")]
pub enum SessionKind {
API,
USER,
}
pub trait Sessions {
fn from_session(session: String) -> impl std::future::Future<Output = Option<User>>;
fn login(
username: &str,
password: &str,
) -> impl std::future::Future<Output = Option<(Session, UserRole)>>;
fn api_key(&self, name: &str) -> impl std::future::Future<Output = Session>;
fn session(&self) -> impl std::future::Future<Output = Session>;
fn list_sessions(&self) -> impl std::future::Future<Output = Vec<Session>>;
fn end_session(&self, id: &uuid::Uuid) -> impl std::future::Future<Output = ()>;
}
impl Sessions for User {
/// Generate a new API Key session
async fn api_key(&self, name: &str) -> Session {
sqlx::query_as(
"INSERT INTO user_session (token, \"user\", kind, name) VALUES ($1, $2, $3, $4) RETURNING *",
)
.bind(gen_token(64))
.bind(&self.username)
.bind(SessionKind::API)
.bind(name)
.fetch_one(get_pg!())
.await
.unwrap()
}
/// End a user session
async fn end_session(&self, id: &uuid::Uuid) {
sqlx::query("DELETE FROM user_session WHERE id = $1 AND \"user\" = $2")
.bind(id)
.bind(&self.username)
.execute(get_pg!())
.await
.unwrap();
}
/// Get all sessions for a user
async fn list_sessions(&self) -> Vec<Session> {
sqlx::query_as("SELECT * FROM user_session WHERE \"user\" = $1")
.bind(&self.username)
.fetch_all(get_pg!())
.await
.unwrap()
}
// Get a user from session ID
async fn from_session(session: String) -> Option<User> {
let user: Option<Self> = sqlx::query_as("SELECT * FROM users WHERE username = (SELECT \"user\" FROM user_session WHERE token = $1)").bind(&session).fetch_optional(get_pg!()).await.unwrap();
if let Some(mut user) = user {
user.session = session;
return Some(user);
}
None
}
/// Login a user with the given username and password
async fn login(username: &str, password: &str) -> Option<(Session, UserRole)> {
let u = Self::find(username).await?;
if !u.verify_pw(password) {
return None;
}
Some((u.session().await, u.user_role))
}
/// Generate a new session token for the user
///
/// Returns a Session instance containing the generated token and associated user
async fn session(&self) -> Session {
sqlx::query_as(
"INSERT INTO user_session (token, \"user\", kind) VALUES ($1, $2, $3) RETURNING *",
)
.bind(gen_token(64))
.bind(&self.username)
.bind(SessionKind::USER)
.fetch_one(get_pg!())
.await
.unwrap()
}
}