mirror of
https://github.com/atuinsh/atuin.git
synced 2024-11-25 01:34:13 +01:00
Add change-password command & support on server
This commit is contained in:
parent
d84f5b2d33
commit
214f2491ce
@ -11,7 +11,7 @@ use reqwest::{
|
|||||||
use atuin_common::{
|
use atuin_common::{
|
||||||
api::{
|
api::{
|
||||||
AddHistoryRequest, CountResponse, DeleteHistoryRequest, ErrorResponse, IndexResponse,
|
AddHistoryRequest, CountResponse, DeleteHistoryRequest, ErrorResponse, IndexResponse,
|
||||||
LoginRequest, LoginResponse, RegisterResponse, StatusResponse, SyncHistoryResponse,
|
LoginRequest, LoginResponse, RegisterResponse, StatusResponse, SyncHistoryResponse, ChangePasswordRequest,
|
||||||
},
|
},
|
||||||
record::RecordStatus,
|
record::RecordStatus,
|
||||||
};
|
};
|
||||||
@ -355,4 +355,26 @@ impl<'a> Client<'a> {
|
|||||||
bail!("Unknown error");
|
bail!("Unknown error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn change_password(&self, current_password: String, new_password: String) -> Result<()> {
|
||||||
|
let url = format!("{}/account/password", self.sync_addr);
|
||||||
|
let url = Url::parse(url.as_str())?;
|
||||||
|
|
||||||
|
let resp = self.client.patch(url).json(&ChangePasswordRequest {
|
||||||
|
current_password,
|
||||||
|
new_password
|
||||||
|
}).send().await?;
|
||||||
|
|
||||||
|
dbg!(&resp);
|
||||||
|
|
||||||
|
if resp.status() == 401 {
|
||||||
|
bail!("current password is incorrect")
|
||||||
|
} else if resp.status() == 403 {
|
||||||
|
bail!("invalid login details");
|
||||||
|
} else if resp.status() == 200 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
bail!("Unknown error");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,15 @@ pub struct RegisterResponse {
|
|||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct DeleteUserResponse {}
|
pub struct DeleteUserResponse {}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct ChangePasswordRequest {
|
||||||
|
pub current_password: String,
|
||||||
|
pub new_password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct ChangePasswordResponse {}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct LoginRequest {
|
pub struct LoginRequest {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
@ -54,6 +54,7 @@ pub trait Database: Sized + Clone + Send + Sync + 'static {
|
|||||||
async fn get_user_session(&self, u: &User) -> DbResult<Session>;
|
async fn get_user_session(&self, u: &User) -> DbResult<Session>;
|
||||||
async fn add_user(&self, user: &NewUser) -> DbResult<i64>;
|
async fn add_user(&self, user: &NewUser) -> DbResult<i64>;
|
||||||
async fn delete_user(&self, u: &User) -> DbResult<()>;
|
async fn delete_user(&self, u: &User) -> DbResult<()>;
|
||||||
|
async fn update_user_password(&self, u: &User) -> DbResult<()>;
|
||||||
|
|
||||||
async fn total_history(&self) -> DbResult<i64>;
|
async fn total_history(&self) -> DbResult<i64>;
|
||||||
async fn count_history(&self, user: &User) -> DbResult<i64>;
|
async fn count_history(&self, user: &User) -> DbResult<i64>;
|
||||||
|
@ -289,6 +289,22 @@ impl Database for Postgres {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
async fn update_user_password(&self, user: &User) -> DbResult<()> {
|
||||||
|
sqlx::query(
|
||||||
|
"update users
|
||||||
|
set password = $1
|
||||||
|
where id = $2",
|
||||||
|
)
|
||||||
|
.bind(&user.password)
|
||||||
|
.bind(user.id)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(fix_error)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn add_user(&self, user: &NewUser) -> DbResult<i64> {
|
async fn add_user(&self, user: &NewUser) -> DbResult<i64> {
|
||||||
let email: &str = &user.email;
|
let email: &str = &user.email;
|
||||||
|
@ -169,6 +169,33 @@ pub async fn delete<DB: Database>(
|
|||||||
Ok(Json(DeleteUserResponse {}))
|
Ok(Json(DeleteUserResponse {}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip_all, fields(user.id = user.id, change_password))]
|
||||||
|
pub async fn change_password<DB: Database>(
|
||||||
|
UserAuth(mut user): UserAuth,
|
||||||
|
state: State<AppState<DB>>,
|
||||||
|
Json(change_password): Json<ChangePasswordRequest>,
|
||||||
|
) -> Result<Json<ChangePasswordResponse>, ErrorResponseStatus<'static>> {
|
||||||
|
let db = &state.0.database;
|
||||||
|
|
||||||
|
let verified = verify_str(user.password.as_str(), change_password.current_password.borrow());
|
||||||
|
if !verified {
|
||||||
|
return Err(
|
||||||
|
ErrorResponse::reply("password is not correct").with_status(StatusCode::UNAUTHORIZED)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let hashed = hash_secret(&change_password.new_password);
|
||||||
|
user.password = hashed;
|
||||||
|
|
||||||
|
if let Err(e) = db.update_user_password(&user).await {
|
||||||
|
error!("failed to change user password: {}", e);
|
||||||
|
|
||||||
|
return Err(ErrorResponse::reply("failed to change user password")
|
||||||
|
.with_status(StatusCode::INTERNAL_SERVER_ERROR));
|
||||||
|
};
|
||||||
|
Ok(Json(ChangePasswordResponse {}))
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip_all, fields(user.username = login.username.as_str()))]
|
#[instrument(skip_all, fields(user.username = login.username.as_str()))]
|
||||||
pub async fn login<DB: Database>(
|
pub async fn login<DB: Database>(
|
||||||
state: State<AppState<DB>>,
|
state: State<AppState<DB>>,
|
||||||
|
@ -5,7 +5,7 @@ use axum::{
|
|||||||
http::Request,
|
http::Request,
|
||||||
middleware::Next,
|
middleware::Next,
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
routing::{delete, get, post},
|
routing::{delete, get, post, patch},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use eyre::Result;
|
use eyre::Result;
|
||||||
@ -120,6 +120,7 @@ pub fn router<DB: Database>(database: DB, settings: Settings<DB::Settings>) -> R
|
|||||||
.route("/history", delete(handlers::history::delete))
|
.route("/history", delete(handlers::history::delete))
|
||||||
.route("/user/:username", get(handlers::user::get))
|
.route("/user/:username", get(handlers::user::get))
|
||||||
.route("/account", delete(handlers::user::delete))
|
.route("/account", delete(handlers::user::delete))
|
||||||
|
.route("/account/password", patch(handlers::user::change_password))
|
||||||
.route("/register", post(handlers::user::register))
|
.route("/register", post(handlers::user::register))
|
||||||
.route("/login", post(handlers::user::login))
|
.route("/login", post(handlers::user::login))
|
||||||
.route("/record", post(handlers::record::post::<DB>))
|
.route("/record", post(handlers::record::post::<DB>))
|
||||||
|
@ -7,6 +7,7 @@ pub mod delete;
|
|||||||
pub mod login;
|
pub mod login;
|
||||||
pub mod logout;
|
pub mod logout;
|
||||||
pub mod register;
|
pub mod register;
|
||||||
|
pub mod change_password;
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
pub struct Cmd {
|
pub struct Cmd {
|
||||||
@ -27,6 +28,8 @@ pub enum Commands {
|
|||||||
|
|
||||||
// Delete your account, and all synced data
|
// Delete your account, and all synced data
|
||||||
Delete,
|
Delete,
|
||||||
|
|
||||||
|
ChangePassword(change_password::Cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cmd {
|
impl Cmd {
|
||||||
@ -36,6 +39,7 @@ impl Cmd {
|
|||||||
Commands::Register(r) => r.run(&settings).await,
|
Commands::Register(r) => r.run(&settings).await,
|
||||||
Commands::Logout => logout::run(&settings),
|
Commands::Logout => logout::run(&settings),
|
||||||
Commands::Delete => delete::run(&settings).await,
|
Commands::Delete => delete::run(&settings).await,
|
||||||
|
Commands::ChangePassword(c) => c.run(&settings).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
55
atuin/src/command/client/account/change_password.rs
Normal file
55
atuin/src/command/client/account/change_password.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
use eyre::{bail, Result};
|
||||||
|
|
||||||
|
use atuin_client::{api_client, settings::Settings};
|
||||||
|
use rpassword::prompt_password;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
pub struct Cmd {
|
||||||
|
#[clap(long, short)]
|
||||||
|
pub current_password: Option<String>,
|
||||||
|
|
||||||
|
#[clap(long, short)]
|
||||||
|
pub new_password: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cmd {
|
||||||
|
pub async fn run(self, settings: &Settings) -> Result<()> {
|
||||||
|
run(settings, &self.current_password, &self.new_password).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(
|
||||||
|
settings: &Settings,
|
||||||
|
current_password: &Option<String>,
|
||||||
|
new_password: &Option<String>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let client = api_client::Client::new(
|
||||||
|
&settings.sync_address,
|
||||||
|
&settings.session_token,
|
||||||
|
settings.network_connect_timeout,
|
||||||
|
settings.network_timeout,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let current_password = current_password
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| prompt_password("Please enter the current password: ").expect("Failed to read from input"));
|
||||||
|
|
||||||
|
if current_password.is_empty() {
|
||||||
|
bail!("please provide the current password");
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_password = new_password
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| prompt_password("Please enter the new password: ").expect("Failed to read from input"));
|
||||||
|
|
||||||
|
if new_password.is_empty() {
|
||||||
|
bail!("please provide a new password");
|
||||||
|
}
|
||||||
|
|
||||||
|
client.change_password(current_password, new_password).await?;
|
||||||
|
|
||||||
|
println!("Account password successfully changed!");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user