Added ability to change email and password
This commit is contained in:
parent
8758491938
commit
fa843620ee
@ -9,10 +9,17 @@ pub struct User {
|
|||||||
pub user_id: uuid::Uuid,
|
pub user_id: uuid::Uuid,
|
||||||
#[validate(email)]
|
#[validate(email)]
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub password: String,
|
pub password: Option<String>,
|
||||||
pub admin: bool,
|
pub admin: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UpdateUser {
|
||||||
|
pub email: Option<String>,
|
||||||
|
pub password: Option<String>,
|
||||||
|
pub admin: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
pub async fn insert(
|
pub async fn insert(
|
||||||
transaction: &mut sqlx::Transaction<'_, Postgres>,
|
transaction: &mut sqlx::Transaction<'_, Postgres>,
|
||||||
@ -56,6 +63,41 @@ impl User {
|
|||||||
|
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_password(&self, pool: &PgPool) -> Result<String, sqlx::Error> {
|
||||||
|
let password = sqlx::query_scalar!(
|
||||||
|
"
|
||||||
|
SELECT password FROM users WHERE user_id = $1
|
||||||
|
",
|
||||||
|
self.user_id,
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(password)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update(
|
||||||
|
&self,
|
||||||
|
transaction: &mut sqlx::Transaction<'_, Postgres>,
|
||||||
|
update_user: UpdateUser,
|
||||||
|
) -> Result<(), sqlx::Error> {
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE users
|
||||||
|
SET email = coalesce($1, email),
|
||||||
|
password = coalesce($2, password)
|
||||||
|
WHERE user_id = $3;
|
||||||
|
",
|
||||||
|
update_user.email,
|
||||||
|
update_user.password,
|
||||||
|
self.user_id
|
||||||
|
)
|
||||||
|
.execute(&mut **transaction)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -23,6 +23,17 @@ impl From<DbUser> for User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<User> for DbUser {
|
||||||
|
fn from(user: User) -> Self {
|
||||||
|
Self {
|
||||||
|
user_id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
admin: user.admin,
|
||||||
|
password: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
pub async fn members(&self, pool: &PgPool) -> Result<Vec<Member>, sqlx::Error> {
|
pub async fn members(&self, pool: &PgPool) -> Result<Vec<Member>, sqlx::Error> {
|
||||||
let related_members = DbUserMember::get_members_from_user(pool, &self.id).await?;
|
let related_members = DbUserMember::get_members_from_user(pool, &self.id).await?;
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
|
use axum::debug_handler;
|
||||||
|
use axum::http::HeaderMap;
|
||||||
use axum::{extract::State, routing::post, Json, Router};
|
use axum::{extract::State, routing::post, Json, Router};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::auth::verify_password_hash;
|
use crate::auth::verify_password_hash;
|
||||||
|
use crate::auth::{get_user_from_header, AuthError};
|
||||||
|
use crate::database::model::user::UpdateUser;
|
||||||
use crate::database::model::Member as DbMember;
|
use crate::database::model::Member as DbMember;
|
||||||
use crate::database::model::Session as DbSession;
|
use crate::database::model::Session as DbSession;
|
||||||
use crate::database::model::User as DbUser;
|
use crate::database::model::User as DbUser;
|
||||||
@ -12,9 +17,11 @@ pub fn routes() -> Router<AppState> {
|
|||||||
Router::new()
|
Router::new()
|
||||||
.route("/auth/login", post(login))
|
.route("/auth/login", post(login))
|
||||||
.route("/auth/register", post(register))
|
.route("/auth/register", post(register))
|
||||||
|
.route("/auth/change_password", post(change_password))
|
||||||
|
.route("/auth/change_email", post(change_email))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct LoginRequest {
|
pub struct LoginRequest {
|
||||||
email: String,
|
email: String,
|
||||||
password: String,
|
password: String,
|
||||||
@ -26,10 +33,14 @@ pub async fn login<'a>(
|
|||||||
) -> Result<String, crate::Error> {
|
) -> Result<String, crate::Error> {
|
||||||
let db_user = DbUser::get_from_email(&state.pool, login_request.email).await?;
|
let db_user = DbUser::get_from_email(&state.pool, login_request.email).await?;
|
||||||
|
|
||||||
match verify_password_hash(&login_request.password, &db_user.password).await {
|
if let Some(pass) = db_user.password {
|
||||||
Ok(_) => (),
|
match verify_password_hash(&login_request.password, &pass).await {
|
||||||
Err(_err) => return Err(crate::Error::Auth(crate::auth::AuthError::InvalidPassword)),
|
Ok(_) => (),
|
||||||
};
|
Err(_err) => return Err(crate::Error::Auth(crate::auth::AuthError::InvalidPassword)),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return Err(AuthError::Unexpected.into());
|
||||||
|
}
|
||||||
|
|
||||||
// Create session
|
// Create session
|
||||||
let mut transaction = state.pool.begin().await?;
|
let mut transaction = state.pool.begin().await?;
|
||||||
@ -42,14 +53,14 @@ pub async fn login<'a>(
|
|||||||
Ok(db_session.token)
|
Ok(db_session.token)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct RegisterRequest {
|
pub struct RegisterRequest {
|
||||||
email: String,
|
email: String,
|
||||||
password: String,
|
password: String,
|
||||||
registration_tokens: Vec<String>,
|
registration_tokens: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn register<'a>(
|
pub async fn register(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Json(auth_request): Json<RegisterRequest>,
|
Json(auth_request): Json<RegisterRequest>,
|
||||||
) -> Result<String, crate::Error> {
|
) -> Result<String, crate::Error> {
|
||||||
@ -83,3 +94,88 @@ pub async fn register<'a>(
|
|||||||
|
|
||||||
Ok(db_session.token)
|
Ok(db_session.token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct ChangePasswordRequest {
|
||||||
|
pub old_password: String,
|
||||||
|
pub new_password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn change_password(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Json(request): Json<ChangePasswordRequest>,
|
||||||
|
) -> Result<(), crate::Error> {
|
||||||
|
let (_, user) = get_user_from_header(&state.pool, &headers).await?;
|
||||||
|
|
||||||
|
let password_hash = match generate_password_hash(request.new_password).await {
|
||||||
|
Ok(hash) => hash,
|
||||||
|
Err(_err) => return Err(crate::Error::Auth(crate::auth::AuthError::InvalidPassword)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let db_user: DbUser = user.into();
|
||||||
|
|
||||||
|
let old_password_hash = db_user.get_password(&state.pool).await?;
|
||||||
|
|
||||||
|
match verify_password_hash(&request.old_password, &old_password_hash).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_err) => return Err(crate::Error::Auth(crate::auth::AuthError::InvalidPassword)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut transaction = state.pool.begin().await?;
|
||||||
|
|
||||||
|
db_user
|
||||||
|
.update(
|
||||||
|
&mut transaction,
|
||||||
|
UpdateUser {
|
||||||
|
email: None,
|
||||||
|
password: Some(password_hash),
|
||||||
|
admin: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct ChangeEmailRequest {
|
||||||
|
pub password: String,
|
||||||
|
pub new_email: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn change_email(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Json(request): Json<ChangeEmailRequest>,
|
||||||
|
) -> Result<(), crate::Error> {
|
||||||
|
let (_, user) = get_user_from_header(&state.pool, &headers).await?;
|
||||||
|
|
||||||
|
let db_user: DbUser = user.into();
|
||||||
|
|
||||||
|
let password_hash = db_user.get_password(&state.pool).await?;
|
||||||
|
|
||||||
|
match verify_password_hash(&request.password, &password_hash).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_err) => return Err(crate::Error::Auth(crate::auth::AuthError::InvalidPassword)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut transaction = state.pool.begin().await?;
|
||||||
|
|
||||||
|
db_user
|
||||||
|
.update(
|
||||||
|
&mut transaction,
|
||||||
|
UpdateUser {
|
||||||
|
email: Some(request.new_email),
|
||||||
|
password: None,
|
||||||
|
admin: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -40,6 +40,12 @@ impl IntoResponse for Error {
|
|||||||
let (status_code, code) = match self {
|
let (status_code, code) = match self {
|
||||||
Self::Sqlx(ref err_kind) => match err_kind {
|
Self::Sqlx(ref err_kind) => match err_kind {
|
||||||
sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, "DATABASE_ROW_NOT_FOUND"),
|
sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, "DATABASE_ROW_NOT_FOUND"),
|
||||||
|
sqlx::Error::Database(db_err) => match db_err.kind() {
|
||||||
|
sqlx::error::ErrorKind::UniqueViolation => {
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, "DATABASE_DUPLICATE")
|
||||||
|
}
|
||||||
|
_ => (StatusCode::INTERNAL_SERVER_ERROR, "DATABASE_ERROR"),
|
||||||
|
},
|
||||||
_ => (StatusCode::INTERNAL_SERVER_ERROR, "DATABASE_ERROR"),
|
_ => (StatusCode::INTERNAL_SERVER_ERROR, "DATABASE_ERROR"),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user