Reset password through setup wizard (#1490)

* closes FE portion of  #1470

* remove accidental commit of local script

* add reset password option (#1560)

* fix error code for incorrect password and clarify codes with comments

Co-authored-by: Matt Hill <matthill@Matt-M1.local>
Co-authored-by: Lucy Cifferello <12953208+elvece@users.noreply.github.com>
Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>
This commit is contained in:
Matt Hill
2022-06-20 16:48:32 -06:00
committed by GitHub
parent 1f5e6dbff6
commit d2195411a6
14 changed files with 380 additions and 224 deletions

View File

@@ -4,6 +4,7 @@ use std::marker::PhantomData;
use chrono::{DateTime, Utc};
use clap::ArgMatches;
use color_eyre::eyre::eyre;
use patch_db::{DbHandle, LockReceipt};
use rpc_toolkit::command;
use rpc_toolkit::command_helpers::prelude::{RequestParts, ResponseParts};
use rpc_toolkit::yajrc::RpcError;
@@ -18,7 +19,7 @@ use crate::util::display_none;
use crate::util::serde::{display_serializable, IoFormat};
use crate::{ensure_code, Error, ResultExt};
#[command(subcommands(login, logout, session))]
#[command(subcommands(login, logout, session, reset_password))]
pub fn auth() -> Result<(), Error> {
Ok(())
}
@@ -256,3 +257,113 @@ pub async fn kill(
HasLoggedOutSessions::new(ids.into_iter().map(KillSessionId), &ctx).await?;
Ok(())
}
#[instrument(skip(ctx, old_password, new_password))]
async fn cli_reset_password(
ctx: CliContext,
old_password: Option<String>,
new_password: Option<String>,
) -> Result<(), RpcError> {
let old_password = if let Some(old_password) = old_password {
old_password
} else {
rpassword::prompt_password_stdout("Current Password: ")?
};
let new_password = if let Some(new_password) = new_password {
new_password
} else {
let new_password = rpassword::prompt_password_stdout("New Password: ")?;
if new_password != rpassword::prompt_password_stdout("Confirm: ")? {
return Err(Error::new(
eyre!("Passwords do not match"),
crate::ErrorKind::IncorrectPassword,
)
.into());
}
new_password
};
rpc_toolkit::command_helpers::call_remote(
ctx,
"auth.reset-password",
serde_json::json!({ "old-password": old_password, "new-password": new_password }),
PhantomData::<()>,
)
.await?
.result?;
Ok(())
}
pub struct SetPasswordReceipt(LockReceipt<String, ()>);
impl SetPasswordReceipt {
pub async fn new<Db: DbHandle>(db: &mut Db) -> Result<Self, Error> {
let mut locks = Vec::new();
let setup = Self::setup(&mut locks);
Ok(setup(&db.lock_all(locks).await?)?)
}
pub fn setup(
locks: &mut Vec<patch_db::LockTargetId>,
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
let password_hash = crate::db::DatabaseModel::new()
.server_info()
.password_hash()
.make_locker(patch_db::LockType::Write)
.add_to_keys(locks);
move |skeleton_key| Ok(Self(password_hash.verify(skeleton_key)?))
}
}
pub async fn set_password<Db: DbHandle, Ex>(
db: &mut Db,
receipt: &SetPasswordReceipt,
secrets: &mut Ex,
password: &str,
) -> Result<(), Error>
where
for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>,
{
let password = argon2::hash_encoded(
password.as_bytes(),
&rand::random::<[u8; 16]>()[..],
&argon2::Config::default(),
)
.with_kind(crate::ErrorKind::PasswordHashGeneration)?;
sqlx::query!("UPDATE account SET password = ?", password,)
.execute(secrets)
.await?;
receipt.0.set(db, password).await?;
Ok(())
}
#[command(
rename = "reset-password",
custom_cli(cli_reset_password(async, context(CliContext))),
display(display_none)
)]
#[instrument(skip(ctx, old_password, new_password))]
pub async fn reset_password(
#[context] ctx: RpcContext,
#[arg(rename = "old-password")] old_password: Option<String>,
#[arg(rename = "new-password")] new_password: Option<String>,
) -> Result<(), Error> {
let old_password = old_password.unwrap_or_default();
let new_password = new_password.unwrap_or_default();
let mut secrets = ctx.secret_store.acquire().await?;
check_password_against_db(&mut secrets, &old_password).await?;
let mut db = ctx.db.handle();
let set_password_receipt = SetPasswordReceipt::new(&mut db).await?;
set_password(&mut db, &set_password_receipt, &mut secrets, &new_password).await?;
Ok(())
}

View File

@@ -25,7 +25,7 @@ pub struct StartReceipts {
}
impl StartReceipts {
pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result<Self, Error> {
pub async fn new(db: &mut impl DbHandle, id: &PackageId) -> Result<Self, Error> {
let mut locks = Vec::new();
let setup = Self::setup(&mut locks, id);

View File

@@ -34,33 +34,21 @@ impl HasLoggedOutSessions {
logged_out_sessions: impl IntoIterator<Item = impl AsLogoutSessionId>,
ctx: &RpcContext,
) -> Result<Self, Error> {
let sessions = logged_out_sessions
.into_iter()
.by_ref()
.map(|x| x.as_logout_session_id())
.collect::<Vec<_>>();
let mut open_authed_websockets = ctx.open_authed_websockets.lock().await;
let mut sqlx_conn = ctx.secret_store.acquire().await?;
for session in &sessions {
for session in logged_out_sessions {
let session = session.as_logout_session_id();
sqlx::query!(
"UPDATE session SET logged_out = CURRENT_TIMESTAMP WHERE id = ?",
session
)
.execute(&mut sqlx_conn)
.await?;
}
drop(sqlx_conn);
for session in sessions {
for socket in ctx
.open_authed_websockets
.lock()
.await
.remove(&session)
.unwrap_or_default()
{
for socket in open_authed_websockets.remove(&session).unwrap_or_default() {
let _ = socket.send(());
}
}
Ok(Self(()))
Ok(HasLoggedOutSessions(()))
}
}

View File

@@ -12,12 +12,12 @@ use futures::future::BoxFuture;
use futures::{FutureExt, TryFutureExt, TryStreamExt};
use nix::unistd::{Gid, Uid};
use openssl::x509::X509;
use patch_db::LockType;
use patch_db::{DbHandle, LockType};
use rpc_toolkit::command;
use rpc_toolkit::yajrc::RpcError;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use sqlx::{Executor, Sqlite};
use sqlx::{Connection, Executor, Sqlite};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
@@ -96,6 +96,7 @@ pub async fn list_disks() -> Result<DiskListResponse, Error> {
pub async fn attach(
#[context] ctx: SetupContext,
#[arg] guid: Arc<String>,
#[arg(rename = "embassy-password")] password: Option<String>,
) -> Result<SetupResult, Error> {
let requires_reboot = crate::disk::main::import(
&*guid,
@@ -148,7 +149,28 @@ pub async fn attach(
)
.await?;
let secrets = ctx.secret_store().await?;
let tor_key = crate::net::tor::os_key(&mut secrets.acquire().await?).await?;
let db = ctx.db(&secrets).await?;
let mut secrets_handle = secrets.acquire().await?;
let mut db_handle = db.handle();
let mut secrets_tx = secrets_handle.begin().await?;
let mut db_tx = db_handle.begin().await?;
if let Some(password) = password {
let set_password_receipt = crate::auth::SetPasswordReceipt::new(&mut db_tx).await?;
crate::auth::set_password(
&mut db_tx,
&set_password_receipt,
&mut secrets_tx,
&password,
)
.await?;
}
let tor_key = crate::net::tor::os_key(&mut secrets_tx).await?;
db_tx.commit(None).await?;
secrets_tx.commit().await?;
let (_, root_ca) = SslManager::init(secrets).await?.export_root_ca().await?;
let setup_result = SetupResult {
tor_address: format!("http://{}", tor_key.public().get_onion_address()),