Merge user models into a single struct
This commit is contained in:
parent
960d0d4662
commit
256b999391
10
src/auth.rs
10
src/auth.rs
@ -1,17 +1,23 @@
|
|||||||
use leptos::*;
|
use leptos::*;
|
||||||
use crate::models::NewUser;
|
use crate::models::User;
|
||||||
|
|
||||||
/// Create a new user and log them in
|
/// Create a new user and log them in
|
||||||
/// Takes in a NewUser struct, with the password in plaintext
|
/// Takes in a NewUser struct, with the password in plaintext
|
||||||
/// Returns a Result with the error message if the user could not be created
|
/// Returns a Result with the error message if the user could not be created
|
||||||
#[server(endpoint = "signup")]
|
#[server(endpoint = "signup")]
|
||||||
pub async fn signup(new_user: NewUser) -> Result<(), ServerFnError> {
|
pub async fn signup(new_user: User) -> Result<(), ServerFnError> {
|
||||||
use crate::users::create_user;
|
use crate::users::create_user;
|
||||||
|
|
||||||
use leptos_actix::extract;
|
use leptos_actix::extract;
|
||||||
use actix_web::{HttpMessage, HttpRequest};
|
use actix_web::{HttpMessage, HttpRequest};
|
||||||
use actix_identity::Identity;
|
use actix_identity::Identity;
|
||||||
|
|
||||||
|
// Ensure the user has no id
|
||||||
|
let new_user = User {
|
||||||
|
id: None,
|
||||||
|
..new_user
|
||||||
|
};
|
||||||
|
|
||||||
create_user(&new_user).await
|
create_user(&new_user).await
|
||||||
.map_err(|e| ServerFnError::ServerError(format!("Error creating user: {}", e)))?;
|
.map_err(|e| ServerFnError::ServerError(format!("Error creating user: {}", e)))?;
|
||||||
|
|
||||||
|
@ -11,75 +11,26 @@ use diesel::prelude::*;
|
|||||||
// diesel-specific attributes to the models when compiling for the server
|
// diesel-specific attributes to the models when compiling for the server
|
||||||
|
|
||||||
/// Model for a "User", used for querying the database
|
/// Model for a "User", used for querying the database
|
||||||
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable))]
|
/// Various fields are wrapped in Options, because they are not always wanted for inserts/retrieval
|
||||||
#[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::users))]
|
/// Using deserialize_as makes Diesel use the specified type when deserializing from the database,
|
||||||
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
|
/// and then call .into() to convert it into the Option
|
||||||
#[derive(Serialize, Deserialize)]
|
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))]
|
||||||
pub struct User {
|
|
||||||
/// A unique id for the user
|
|
||||||
pub id: i32,
|
|
||||||
/// The user's username
|
|
||||||
pub username: String,
|
|
||||||
/// The user's email
|
|
||||||
pub email: String,
|
|
||||||
/// The user's password, stored as a hash
|
|
||||||
pub password: String,
|
|
||||||
/// The time the user was created
|
|
||||||
pub created_at: SystemTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Model for a "New User", used for inserting into the database
|
|
||||||
/// Note that this model does not have an id or created_at field, as those are automatically
|
|
||||||
/// generated by the database and we don't want to deal with them ourselves
|
|
||||||
#[cfg_attr(feature = "ssr", derive(Insertable))]
|
|
||||||
#[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::users))]
|
#[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::users))]
|
||||||
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
|
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct NewUser {
|
pub struct User {
|
||||||
|
/// A unique id for the user
|
||||||
|
#[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))]
|
||||||
|
// #[cfg_attr(feature = "ssr", diesel(skip_insertion))] // This feature is not yet released
|
||||||
|
pub id: Option<i32>,
|
||||||
/// The user's username
|
/// The user's username
|
||||||
pub username: String,
|
pub username: String,
|
||||||
/// The user's email
|
/// The user's email
|
||||||
pub email: String,
|
pub email: String,
|
||||||
/// The user's password, stored as a hash
|
/// The user's password, stored as a hash
|
||||||
pub password: String,
|
#[cfg_attr(feature = "ssr", diesel(deserialize_as = String))]
|
||||||
}
|
pub password: Option<String>,
|
||||||
|
|
||||||
/// Convert a User into a NewUser, omitting the id and created_at fields
|
|
||||||
impl From<User> for NewUser {
|
|
||||||
fn from(user: User) -> NewUser {
|
|
||||||
NewUser {
|
|
||||||
username: user.username,
|
|
||||||
email: user.email,
|
|
||||||
password: user.password,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Model for a "Public User", used for returning user data to the client
|
|
||||||
/// This model omits the password field, so that the hashed password is not sent to the client
|
|
||||||
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable))]
|
|
||||||
#[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::users))]
|
|
||||||
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct PublicUser {
|
|
||||||
/// A unique id for the user
|
|
||||||
pub id: i32,
|
|
||||||
/// The user's username
|
|
||||||
pub username: String,
|
|
||||||
/// The user's email
|
|
||||||
pub email: String,
|
|
||||||
/// The time the user was created
|
/// The time the user was created
|
||||||
pub created_at: SystemTime,
|
#[cfg_attr(feature = "ssr", diesel(deserialize_as = SystemTime))]
|
||||||
}
|
pub created_at: Option<SystemTime>,
|
||||||
|
|
||||||
/// Convert a User into a PublicUser, omitting the password field
|
|
||||||
impl From<User> for PublicUser {
|
|
||||||
fn from(user: User) -> PublicUser {
|
|
||||||
PublicUser {
|
|
||||||
id: user.id,
|
|
||||||
username: user.username,
|
|
||||||
email: user.email,
|
|
||||||
created_at: user.created_at,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
33
src/users.rs
33
src/users.rs
@ -14,7 +14,7 @@ cfg_if::cfg_if! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use crate::models::{NewUser, PublicUser, User};
|
use crate::models::User;
|
||||||
|
|
||||||
/// Get a user from the database by username or email
|
/// Get a user from the database by username or email
|
||||||
/// Returns a Result with the user if found, None if not found, or an error if there was a problem
|
/// Returns a Result with the user if found, None if not found, or an error if there was a problem
|
||||||
@ -34,17 +34,19 @@ pub async fn find_user(username_or_email: String) -> Result<Option<User>, Server
|
|||||||
/// Create a new user in the database
|
/// Create a new user in the database
|
||||||
/// Returns an empty Result if successful, or an error if there was a problem
|
/// Returns an empty Result if successful, or an error if there was a problem
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub async fn create_user(new_user: &NewUser) -> Result<(), ServerFnError> {
|
pub async fn create_user(new_user: &User) -> Result<(), ServerFnError> {
|
||||||
use crate::schema::users::dsl::*;
|
use crate::schema::users::dsl::*;
|
||||||
|
|
||||||
|
let new_password = new_user.password.clone()
|
||||||
|
.ok_or(ServerFnError::ServerError(format!("No password provided for user {}", new_user.username)))?;
|
||||||
|
|
||||||
let salt = SaltString::generate(&mut OsRng);
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
let password_hash = Pbkdf2.hash_password(new_user.password.as_bytes(), &salt)
|
let password_hash = Pbkdf2.hash_password(new_password.as_bytes(), &salt)
|
||||||
.map_err(|_| ServerFnError::ServerError("Error hashing password".to_string()))?.to_string();
|
.map_err(|_| ServerFnError::ServerError("Error hashing password".to_string()))?.to_string();
|
||||||
|
|
||||||
let new_user = NewUser {
|
let new_user = User {
|
||||||
username: new_user.username.clone(),
|
password: Some(password_hash),
|
||||||
email: new_user.email.clone(),
|
..new_user.clone()
|
||||||
password: password_hash,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let db_con = &mut get_db_conn();
|
let db_con = &mut get_db_conn();
|
||||||
@ -68,7 +70,10 @@ pub async fn validate_user(username_or_email: String, password: String) -> Resul
|
|||||||
None => return Ok(None)
|
None => return Ok(None)
|
||||||
};
|
};
|
||||||
|
|
||||||
let password_hash = PasswordHash::new(&db_user.password)
|
let db_password = db_user.password.clone()
|
||||||
|
.ok_or(ServerFnError::ServerError(format!("No password found for user {}", db_user.username)))?;
|
||||||
|
|
||||||
|
let password_hash = PasswordHash::new(&db_password)
|
||||||
.map_err(|e| ServerFnError::ServerError(format!("Error hashing supplied password: {}", e)))?;
|
.map_err(|e| ServerFnError::ServerError(format!("Error hashing supplied password: {}", e)))?;
|
||||||
|
|
||||||
match Pbkdf2.verify_password(password.as_bytes(), &password_hash) {
|
match Pbkdf2.verify_password(password.as_bytes(), &password_hash) {
|
||||||
@ -87,7 +92,13 @@ pub async fn validate_user(username_or_email: String, password: String) -> Resul
|
|||||||
/// Get a user from the database by username or email
|
/// Get a user from the database by username or email
|
||||||
/// Returns a Result with the user if found, None if not found, or an error if there was a problem
|
/// Returns a Result with the user if found, None if not found, or an error if there was a problem
|
||||||
#[server(endpoint = "get_user")]
|
#[server(endpoint = "get_user")]
|
||||||
pub async fn get_user(username_or_email: String) -> Result<Option<PublicUser>, ServerFnError> {
|
pub async fn get_user(username_or_email: String) -> Result<Option<User>, ServerFnError> {
|
||||||
let user = find_user(username_or_email).await?;
|
let mut user = find_user(username_or_email).await?;
|
||||||
Ok(user.map(|u| u.into()))
|
|
||||||
|
// Remove the password hash before returning the user
|
||||||
|
if let Some(user) = user.as_mut() {
|
||||||
|
user.password = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user