Skip to content
This repository has been archived by the owner on Jan 17, 2024. It is now read-only.

Commit

Permalink
feat(api): change password endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
lennartkloock committed Oct 21, 2023
1 parent 34c8d0f commit a6ce34b
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 2 deletions.
4 changes: 2 additions & 2 deletions platform/api/src/api/v1/gql/mutations/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ impl AuthMutation {
.bind(user.id)
.bind(!user.totp_enabled)
.bind(expires_at)
.fetch_one(&mut *tx)
.fetch_one(tx.as_mut())
.await?;

sqlx::query("UPDATE users SET last_login_at = NOW() WHERE id = $1")
.bind(user.id)
.execute(&mut *tx)
.execute(tx.as_mut())
.await?;

tx.commit().await?;
Expand Down
54 changes: 54 additions & 0 deletions platform/api/src/api/v1/gql/mutations/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use bytes::Bytes;
use prost::Message;

use crate::api::middleware::auth::AuthError;
use crate::api::v1::gql::validators::PasswordValidator;
use crate::{
api::v1::gql::{
error::{GqlError, Result, ResultExt},
Expand Down Expand Up @@ -148,6 +149,59 @@ impl UserMutation {
Ok(user.into())
}

async fn password<'ctx>(
&self,
ctx: &Context<'_>,
#[graphql(desc = "Current password")] current_password: String,
#[graphql(desc = "New password", validator(custom = "PasswordValidator"))]
new_password: String,
) -> Result<User> {
let global = ctx.get_global();
let request_context = ctx.get_req_context();

let auth = request_context
.auth()
.await?
.ok_or(GqlError::Auth(AuthError::NotLoggedIn))?;

let user = global
.user_by_id_loader
.load(auth.session.user_id.0)
.await
.map_err_gql("failed to fetch user")?
.map_err_gql(GqlError::NotFound("user"))?;

if !user.verify_password(&current_password) {
return Err(GqlError::InvalidInput {
fields: vec!["password"],
message: "wrong password",
}
.into());
}

let mut tx = global.db.begin().await?;

let user: database::User =
sqlx::query_as("UPDATE users SET password_hash = $1 WHERE id = $2 RETURNING *")
.bind(database::User::hash_password(&new_password))
.bind(user.id)
.fetch_one(tx.as_mut())
.await?;

// Delete all sessions except current
sqlx::query("DELETE FROM user_sessions WHERE user_id = $1 AND id != $2")
.bind(user.id)
.bind(auth.session.id)
.execute(tx.as_mut())
.await?;

// TODO: Logout active connections

tx.commit().await?;

Ok(user.into())
}

/// Follow or unfollow a user.
async fn follow<'ctx>(
&self,
Expand Down
10 changes: 10 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,16 @@ type UserMutation {
"""
follow: Boolean!
): Boolean!
password(
"""
Current password
"""
currentPassword: String!
"""
New password
"""
newPassword: String!
): User!
twoFa: TwoFaMutation!
}

Expand Down

0 comments on commit a6ce34b

Please sign in to comment.