port to owldb

This commit is contained in:
JMARyA 2025-04-28 15:41:23 +02:00
parent 1f77f4efdc
commit c74e159ce0
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
10 changed files with 844 additions and 365 deletions

View file

@ -1,10 +1,10 @@
use owl::{db::model::file::File, get, prelude::*, query, save, update};
use rocket::{Request, http::Status, outcome::Outcome, request::FromRequest};
use serde::{Deserialize, Serialize};
use serde_json::json;
use sqlx::FromRow;
use super::Sessions;
use crate::{get_pg, request::api::ToAPI};
use crate::request::api::ToAPI;
// TODO : 2FA
@ -20,16 +20,18 @@ use crate::{get_pg, request::api::ToAPI};
/// ...
/// }
/// ```
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
#[derive(Debug, Clone)]
#[model]
pub struct User {
/// The username chosen by the user
pub username: String,
pub id: Id,
/// The hashed password for the user
pub password: String,
/// The role of the user
pub user_role: UserRole,
#[sqlx(default)]
#[serde(skip)]
pub session: String,
pub profile_picture: Option<IdRef<File>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type)]
@ -43,52 +45,39 @@ pub enum UserRole {
impl User {
/// Find a user by their username
pub async fn find(username: &str) -> Option<Self> {
sqlx::query_as("SELECT * FROM users WHERE username = $1")
.bind(username)
.fetch_optional(get_pg!())
.await
.unwrap()
pub async fn find(username: &str) -> Option<Model<Self>> {
get!(username)
}
/// Create a new user with the given details
///
/// Returns an Option containing the created user, or None if a user already exists with the same username
pub async fn create(username: String, password: &str, role: UserRole) -> Option<Self> {
pub async fn create(username: String, password: &str, role: UserRole) -> Option<Model<Self>> {
// Check if a user already exists with the same username
if Self::find(&username).await.is_some() {
return None;
}
let u = Self {
username,
id: Id::String(username),
password: bcrypt::hash(password, bcrypt::DEFAULT_COST).unwrap(),
user_role: role,
profile_picture: None,
session: String::new(),
};
sqlx::query("INSERT INTO users (username, \"password\", user_role) VALUES ($1, $2, $3)")
.bind(&u.username)
.bind(&u.password)
.bind(&u.user_role)
.execute(get_pg!())
.await
.unwrap();
Some(u)
Some(save!(u))
}
/// Change the password of a User
///
/// Returns a Result indicating whether the password change was successful or not
pub async fn passwd(self, old: &str, new: &str) -> Result<(), ()> {
pub async fn passwd(&self, old: &str, new: &str) -> Result<(), ()> {
if self.verify_pw(old) {
sqlx::query("UPDATE users SET \"password\" = $1 WHERE username = $2;")
.bind(bcrypt::hash(new, bcrypt::DEFAULT_COST).unwrap())
.bind(&self.username)
.execute(get_pg!())
.await
.unwrap();
let mut target = vec![get!(self.id.clone()).unwrap()];
update!(&mut target, |u: &mut User| {
u.password = bcrypt::hash(new, bcrypt::DEFAULT_COST).unwrap();
});
return Ok(());
}
@ -98,11 +87,8 @@ impl User {
/// Find all users in the system
#[must_use]
pub async fn find_all() -> Vec<Self> {
sqlx::query_as("SELECT * FROM users")
.fetch_all(get_pg!())
.await
.unwrap()
pub async fn find_all() -> Vec<Model<Self>> {
query!(|_| true)
}
/// Check if the user is an admin
@ -123,14 +109,14 @@ impl User {
impl ToAPI for User {
async fn api(&self) -> serde_json::Value {
json!({
"username": self.username,
"username": self.id.to_string(),
"role": self.user_role
})
}
}
/// extracts a user from a request with `session` cookie
async fn extract_user(request: &Request<'_>) -> Option<User> {
async fn extract_user(request: &Request<'_>) -> Option<Model<User>> {
if let Some(session_id) = request.cookies().get("session") {
if let Some(user) = User::from_session(session_id.value().to_string()).await {
return Some(user);
@ -141,20 +127,22 @@ async fn extract_user(request: &Request<'_>) -> Option<User> {
None
}
pub struct UserAuth(pub Model<User>);
#[rocket::async_trait]
impl<'r> FromRequest<'r> for User {
impl<'r> FromRequest<'r> for UserAuth {
type Error = ();
async fn from_request(request: &'r Request<'_>) -> rocket::request::Outcome<Self, Self::Error> {
if let Some(user) = extract_user(request).await {
return Outcome::Success(user);
return Outcome::Success(UserAuth(user));
}
Outcome::Error((Status::Unauthorized, ()))
}
}
/// Struct which extracts a user with session from `Token` HTTP Header.
pub struct APIUser(pub User);
pub struct APIUser(pub Model<User>);
#[rocket::async_trait]
impl<'r> FromRequest<'r> for APIUser {
@ -191,7 +179,7 @@ impl<'r> FromRequest<'r> for APIUser {
/// }
/// ```
pub enum MaybeUser {
User(User),
User(Model<User>),
Anonymous,
}
@ -208,7 +196,7 @@ impl<'r> FromRequest<'r> for MaybeUser {
}
}
impl From<MaybeUser> for Option<User> {
impl From<MaybeUser> for Option<Model<User>> {
fn from(value: MaybeUser) -> Self {
value.take_user()
}
@ -216,7 +204,7 @@ impl From<MaybeUser> for Option<User> {
impl MaybeUser {
#[must_use]
pub const fn user(&self) -> Option<&User> {
pub const fn user(&self) -> Option<&Model<User>> {
match self {
MaybeUser::User(user) => Some(user),
MaybeUser::Anonymous => None,
@ -224,7 +212,7 @@ impl MaybeUser {
}
#[must_use]
pub fn take_user(self) -> Option<User> {
pub fn take_user(self) -> Option<Model<User>> {
match self {
MaybeUser::User(user) => Some(user),
MaybeUser::Anonymous => None,
@ -246,7 +234,7 @@ impl MaybeUser {
/// ...
/// }
/// ```
pub struct AdminUser(pub User);
pub struct AdminUser(pub Model<User>);
#[rocket::async_trait]
impl<'r> FromRequest<'r> for AdminUser {
@ -254,7 +242,7 @@ impl<'r> FromRequest<'r> for AdminUser {
async fn from_request(request: &'r Request<'_>) -> rocket::request::Outcome<Self, Self::Error> {
if let Some(user) = extract_user(request).await {
if user.is_admin() {
if user.read().is_admin() {
return Outcome::Success(AdminUser(user));
}
}