use crate::database::Database;
use rocket::serde::{Deserialize, Serialize};
use rocket::State;
use sqlx::query_as;
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
pub struct User {
pub id: i32,
pub name: String,
pub email: String,
#[serde(skip_deserializing)]
pub password: String,
}
impl User {
pub async fn create(
db: &Database,
name: String,
password: String,
email: String,
) -> Result<Self, sqlx::Error> {
let result = query_as!( User,
"INSERT INTO users (name, email, password) VALUES ($1, $2, crypt($3, gen_salt('bf'))) RETURNING *",
&name,
&email.to_lowercase(),
&password
)
.fetch_one(db)
.await?;
Ok(result)
}
pub async fn find(db: &Database, id: i32) -> Result<Self, sqlx::Error> {
query_as!(User, "SELECT * FROM users WHERE id = $1", id)
.fetch_one(db)
.await
}
pub async fn authenticate(
db: &Database,
name: String,
password: String,
) -> Result<Option<User>, sqlx::Error> {
let result = query_as!(
User,
"SELECT * FROM users WHERE name = $1 AND password = crypt($2, password)",
name,
password
)
.fetch_optional(db)
.await?;
Ok(result)
}
}
static COOKIE_USER_ID_KEY: &str = "user_id";
use rocket::http::{CookieJar, Status};
use rocket::request::{FromRequest, Outcome};
use rocket::Request;
#[rocket::async_trait]
impl<'r> FromRequest<'r> for User {
type Error = ();
async fn from_request(request: &'r Request<'_>) -> Outcome<User, ()> {
let db: &State<Database> = match request.guard().await {
Outcome::Success(db) => db,
_ => return Outcome::Failure((Status::Unauthorized, ())),
};
if let Some(user) = user_from_basic_auth(db, &*request).await {
return Outcome::Success(user);
}
let cookies = request.cookies();
let some_id = match get_user_id_from_cookie(cookies) {
Some(id) => id,
None => return Outcome::Forward(()),
};
match User::find(db, some_id).await {
Ok(u) => Outcome::Success(u),
Err(_e) => Outcome::Forward(()), }
}
}
async fn user_from_basic_auth(db: &State<Database>, req: &Request<'_>) -> Option<User> {
use rocket_basicauth::BasicAuth;
info!("trying basic auth");
let credentials = match BasicAuth::from_request(req).await {
Outcome::Success(ba) => ba,
_ => return None,
};
info!("extracted basic auth");
match User::authenticate(db, credentials.username, credentials.password).await {
Ok(some) => some,
_ => None,
}
}
fn get_user_id_from_cookie(jar: &CookieJar) -> Option<i32> {
match jar.get_private(COOKIE_USER_ID_KEY) {
Some(id) => Some(id.value().parse::<i32>().ok()?),
None => None,
}
}
impl PartialEq for User {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for User {}