118 lines
3.6 KiB
Rust
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()
|
|
}
|
|
}
|