mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
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:
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()),
|
||||
|
||||
Reference in New Issue
Block a user