mirror of
https://github.com/ellie/atuin.git
synced 2024-09-11 21:18:22 +03: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::{
|
||||
api::{
|
||||
AddHistoryRequest, CountResponse, DeleteHistoryRequest, ErrorResponse, IndexResponse,
|
||||
LoginRequest, LoginResponse, RegisterResponse, StatusResponse, SyncHistoryResponse,
|
||||
LoginRequest, LoginResponse, RegisterResponse, StatusResponse, SyncHistoryResponse, ChangePasswordRequest,
|
||||
},
|
||||
record::RecordStatus,
|
||||
};
|
||||
@ -355,4 +355,26 @@ impl<'a> Client<'a> {
|
||||
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)]
|
||||
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)]
|
||||
pub struct LoginRequest {
|
||||
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 add_user(&self, user: &NewUser) -> DbResult<i64>;
|
||||
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 count_history(&self, user: &User) -> DbResult<i64>;
|
||||
|
@ -289,6 +289,22 @@ impl Database for Postgres {
|
||||
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)]
|
||||
async fn add_user(&self, user: &NewUser) -> DbResult<i64> {
|
||||
let email: &str = &user.email;
|
||||
|
@ -169,6 +169,33 @@ pub async fn delete<DB: Database>(
|
||||
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()))]
|
||||
pub async fn login<DB: Database>(
|
||||
state: State<AppState<DB>>,
|
||||
|
@ -5,7 +5,7 @@ use axum::{
|
||||
http::Request,
|
||||
middleware::Next,
|
||||
response::{IntoResponse, Response},
|
||||
routing::{delete, get, post},
|
||||
routing::{delete, get, post, patch},
|
||||
Router,
|
||||
};
|
||||
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("/user/:username", get(handlers::user::get))
|
||||
.route("/account", delete(handlers::user::delete))
|
||||
.route("/account/password", patch(handlers::user::change_password))
|
||||
.route("/register", post(handlers::user::register))
|
||||
.route("/login", post(handlers::user::login))
|
||||
.route("/record", post(handlers::record::post::<DB>))
|
||||
|
@ -7,6 +7,7 @@ pub mod delete;
|
||||
pub mod login;
|
||||
pub mod logout;
|
||||
pub mod register;
|
||||
pub mod change_password;
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct Cmd {
|
||||
@ -27,6 +28,8 @@ pub enum Commands {
|
||||
|
||||
// Delete your account, and all synced data
|
||||
Delete,
|
||||
|
||||
ChangePassword(change_password::Cmd)
|
||||
}
|
||||
|
||||
impl Cmd {
|
||||
@ -36,6 +39,7 @@ impl Cmd {
|
||||
Commands::Register(r) => r.run(&settings).await,
|
||||
Commands::Logout => logout::run(&settings),
|
||||
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