2024-12-27 03:56:27 +01:00
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 ,
}
2024-12-29 20:39:10 +01:00
#[ allow(clippy::upper_case_acronyms) ]
2024-12-27 03:56:27 +01:00
#[ derive(Debug, Clone, Serialize, Deserialize, sqlx::Type) ]
#[ sqlx(type_name = " session_kind " , rename_all = " lowercase " ) ]
pub enum SessionKind {
API ,
USER ,
}
pub trait Sessions {
2024-12-29 21:35:48 +01:00
fn from_session ( session : String ) -> impl std ::future ::Future < Output = Option < User > > ;
2024-12-27 03:56:27 +01:00
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 > > ;
2025-01-09 14:28:53 +01:00
fn end_session ( & self , id : & str ) -> impl std ::future ::Future < Output = ( ) > ;
2024-12-27 03:56:27 +01:00
}
impl Sessions for User {
/// Generate a new API Key session
async fn api_key ( & self , name : & str ) -> Session {
sqlx ::query_as (
2025-01-08 20:52:38 +01:00
" INSERT INTO user_session (token, \" user \" , kind, name) VALUES ($1, $2, $3, $4) RETURNING * " ,
2024-12-27 03:56:27 +01:00
)
. bind ( gen_token ( 64 ) )
. bind ( & self . username )
. bind ( SessionKind ::API )
. bind ( name )
. fetch_one ( get_pg! ( ) )
. await
. unwrap ( )
}
/// End a user session
2025-01-09 14:28:53 +01:00
async fn end_session ( & self , id : & str ) {
sqlx ::query ( " DELETE FROM user_session WHERE id = $1 " )
. bind ( id )
2024-12-27 03:56:27 +01:00
. execute ( get_pg! ( ) )
. await
. unwrap ( ) ;
}
/// Get all sessions for a user
async fn list_sessions ( & self ) -> Vec < Session > {
2025-01-08 20:54:53 +01:00
sqlx ::query_as ( " SELECT * FROM user_session WHERE \" user \" = $1 " )
2024-12-27 03:56:27 +01:00
. bind ( & self . username )
. fetch_all ( get_pg! ( ) )
. await
. unwrap ( )
}
// Get a user from session ID
2024-12-29 21:35:48 +01:00
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 ( ) ;
2024-12-27 03:56:27 +01:00
if let Some ( mut user ) = user {
2024-12-29 21:35:48 +01:00
user . session = session ;
2024-12-27 03:56:27 +01:00
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 ( )
}
}