From d2195411a64592054e0cdaa3713fbd172f79a46d Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Mon, 20 Jun 2022 16:48:32 -0600 Subject: [PATCH] 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 Co-authored-by: Lucy Cifferello <12953208+elvece@users.noreply.github.com> Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> --- .gitignore | 4 +- backend/sqlx-data.json | 370 +++++++++--------- backend/src/auth.rs | 113 +++++- backend/src/control.rs | 2 +- backend/src/middleware/auth.rs | 22 +- backend/src/setup.rs | 28 +- .../src/app/pages/recover/recover.page.ts | 21 +- .../src/app/services/api/api.service.ts | 7 +- .../src/app/services/api/live-api.service.ts | 6 +- .../src/app/services/api/mock-api.service.ts | 9 +- .../src/app/services/state.service.ts | 7 +- .../ui/src/app/pages/login/login.page.ts | 3 +- .../ui/src/app/services/http.service.ts | 1 + libs/js_engine/src/lib.rs | 11 +- 14 files changed, 380 insertions(+), 224 deletions(-) diff --git a/.gitignore b/.gitignore index 1ef6d2d4b..830e820cf 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ .vscode/settings.json deploy_web.sh libs/js_engine/src/artifacts/ARM_JS_SNAPSHOT.bin -libs/js_engine/src/artifacts/JS_SNAPSHOT.bin \ No newline at end of file +libs/js_engine/src/artifacts/JS_SNAPSHOT.bin +deploy_web.sh +secrets.db diff --git a/backend/sqlx-data.json b/backend/sqlx-data.json index b129ec354..5f8cba317 100644 --- a/backend/sqlx-data.json +++ b/backend/sqlx-data.json @@ -1,47 +1,46 @@ { "db": "SQLite", "10350f5a16f1b2a6ce91672ae5dc6acc46691bd8f901861545ec83c326a8ccef": { - "query": "INSERT INTO ssh_keys (fingerprint, openssh_pubkey, created_at) VALUES (?, ?, ?)", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 3 - }, - "nullable": [] - } + } + }, + "query": "INSERT INTO ssh_keys (fingerprint, openssh_pubkey, created_at) VALUES (?, ?, ?)" }, "118d59de5cf930d5a3b5667b2220e9a3d593bd84276beb2b76c93b2694b0fd72": { - "query": "INSERT INTO session (id, user_agent, metadata) VALUES (?, ?, ?)", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 3 - }, - "nullable": [] - } + } + }, + "query": "INSERT INTO session (id, user_agent, metadata) VALUES (?, ?, ?)" }, "165daa7d6a60cb42122373b2c5ac7d39399bcc99992f0002ee7bfef50a8daceb": { - "query": "DELETE FROM certificates WHERE id = 0 OR id = 1;", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 0 - }, - "nullable": [] - } + } + }, + "query": "DELETE FROM certificates WHERE id = 0 OR id = 1;" }, "177c4b9cc7901a3b906e5969b86b1c11e6acbfb8e86e98f197d7333030b17964": { - "query": "DELETE FROM notifications WHERE id = ?", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 1 - }, - "nullable": [] - } + } + }, + "query": "DELETE FROM notifications WHERE id = ?" }, "1b2242afa55e730b37b00929b656d80940b457ec86c234ddd0de917bd8872611": { - "query": "INSERT INTO cifs_shares (hostname, path, username, password) VALUES (?, ?, ?, ?) RETURNING id AS \"id: u32\"", "describe": { "columns": [ { @@ -50,36 +49,36 @@ "type_info": "Int64" } ], - "parameters": { - "Right": 4 - }, "nullable": [ false - ] - } + ], + "parameters": { + "Right": 4 + } + }, + "query": "INSERT INTO cifs_shares (hostname, path, username, password) VALUES (?, ?, ?, ?) RETURNING id AS \"id: u32\"" }, "1eee1fdc793919c391008854407143d7a11b4668486c11a760b49af49992f9f8": { - "query": "REPLACE INTO tor (package, interface, key) VALUES (?, 'main', ?)", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 2 - }, - "nullable": [] - } + } + }, + "query": "REPLACE INTO tor (package, interface, key) VALUES (?, 'main', ?)" }, "2932aa02735b6422fca4ba889abfb3de8598178d4690076dc278898753d9df62": { - "query": "UPDATE session SET logged_out = CURRENT_TIMESTAMP WHERE id = ?", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 1 - }, - "nullable": [] - } + } + }, + "query": "UPDATE session SET logged_out = CURRENT_TIMESTAMP WHERE id = ?" }, "3502e58f2ab48fb4566d21c920c096f81acfa3ff0d02f970626a4dcd67bac71d": { - "query": "SELECT tor_key FROM account", "describe": { "columns": [ { @@ -88,16 +87,16 @@ "type_info": "Blob" } ], - "parameters": { - "Right": 0 - }, "nullable": [ false - ] - } + ], + "parameters": { + "Right": 0 + } + }, + "query": "SELECT tor_key FROM account" }, "3e57a0e52b69f33e9411c13b03a5d82c5856d63f0375eb4c23b255a09c54f8b1": { - "query": "SELECT key FROM tor WHERE package = ? AND interface = ?", "describe": { "columns": [ { @@ -106,16 +105,16 @@ "type_info": "Blob" } ], - "parameters": { - "Right": 2 - }, "nullable": [ false - ] - } + ], + "parameters": { + "Right": 2 + } + }, + "query": "SELECT key FROM tor WHERE package = ? AND interface = ?" }, "4691e3a2ce80b59009ac17124f54f925f61dc5ea371903e62cdffa5d7b67ca96": { - "query": "SELECT * FROM session WHERE logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP", "describe": { "columns": [ { @@ -149,9 +148,6 @@ "type_info": "Text" } ], - "parameters": { - "Right": 0 - }, "nullable": [ false, false, @@ -159,51 +155,54 @@ false, true, false - ] - } + ], + "parameters": { + "Right": 0 + } + }, + "query": "SELECT * FROM session WHERE logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP" }, "530192a2a530ee6b92e5b98e1eb1bf6d1426c7b0cb2578593a367cb0bf2c3ca8": { - "query": "UPDATE certificates SET priv_key_pem = ?, certificate_pem = ?, updated_at = datetime('now') WHERE lookup_string = ?", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 3 - }, - "nullable": [] - } + } + }, + "query": "UPDATE certificates SET priv_key_pem = ?, certificate_pem = ?, updated_at = datetime('now') WHERE lookup_string = ?" }, "56b986f2a2b7091d9c3acdd78f75d9842242de1f4da8f3672f2793d9fb256928": { - "query": "DELETE FROM tor WHERE package = ?", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 1 - }, - "nullable": [] - } + } + }, + "query": "DELETE FROM tor WHERE package = ?" }, "5b114c450073f77f466c980a2541293f30087b57301c379630326e5e5c2fb792": { - "query": "REPLACE INTO tor (package, interface, key) VALUES (?, ?, ?)", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 3 - }, - "nullable": [] - } + } + }, + "query": "REPLACE INTO tor (package, interface, key) VALUES (?, ?, ?)" }, "5c47da44b9c84468e95a13fc47301989900f130b3b5899d1ee6664df3ed812ac": { - "query": "INSERT INTO certificates (id, priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (0, ?, ?, NULL, datetime('now'), datetime('now'))", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 2 - }, - "nullable": [] - } + } + }, + "query": "INSERT INTO certificates (id, priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (0, ?, ?, NULL, datetime('now'), datetime('now'))" }, "629be61c3c341c131ddbbff0293a83dbc6afd07cae69d246987f62cf0cc35c2a": { - "query": "SELECT password FROM account", "describe": { "columns": [ { @@ -212,36 +211,36 @@ "type_info": "Text" } ], - "parameters": { - "Right": 0 - }, "nullable": [ false - ] - } + ], + "parameters": { + "Right": 0 + } + }, + "query": "SELECT password FROM account" }, "63785dc5f193ea31e6f641a910c75857ccd288a3f6e9c4f704331531e4f0689f": { - "query": "UPDATE session SET last_active = CURRENT_TIMESTAMP WHERE id = ? AND logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 1 - }, - "nullable": [] - } + } + }, + "query": "UPDATE session SET last_active = CURRENT_TIMESTAMP WHERE id = ? AND logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP" }, "6440354d73a67c041ea29508b43b5f309d45837a44f1a562051ad540d894c7d6": { - "query": "DELETE FROM ssh_keys WHERE fingerprint = ?", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 1 - }, - "nullable": [] - } + } + }, + "query": "DELETE FROM ssh_keys WHERE fingerprint = ?" }, "65e6c3fbb138da5cf385af096fdd3c062b6e826e12a8a4b23e16fcc773004c29": { - "query": "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications WHERE id < ? ORDER BY id DESC LIMIT ?", "describe": { "columns": [ { @@ -285,9 +284,6 @@ "type_info": "Text" } ], - "parameters": { - "Right": 2 - }, "nullable": [ false, true, @@ -297,11 +293,14 @@ false, false, true - ] - } + ], + "parameters": { + "Right": 2 + } + }, + "query": "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications WHERE id < ? ORDER BY id DESC LIMIT ?" }, "668f39c868f90cdbcc635858bac9e55ed73192ed2aec5c52dcfba9800a7a4a41": { - "query": "SELECT id AS \"id: u32\", hostname, path, username, password FROM cifs_shares", "describe": { "columns": [ { @@ -330,30 +329,30 @@ "type_info": "Text" } ], - "parameters": { - "Right": 0 - }, "nullable": [ false, false, false, false, true - ] - } + ], + "parameters": { + "Right": 0 + } + }, + "query": "SELECT id AS \"id: u32\", hostname, path, username, password FROM cifs_shares" }, "6b9abc9e079cff975f8a7f07ff70548c7877ecae3be0d0f2d3f439a6713326c0": { - "query": "DELETE FROM notifications WHERE id < ?", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 1 - }, - "nullable": [] - } + } + }, + "query": "DELETE FROM notifications WHERE id < ?" }, "6c96d76bffcc5f03290d8d8544a58521345ed2a843a509b17bbcd6257bb81821": { - "query": "SELECT priv_key_pem, certificate_pem FROM certificates WHERE id = 1;", "describe": { "columns": [ { @@ -367,27 +366,37 @@ "type_info": "Text" } ], - "parameters": { - "Right": 0 - }, "nullable": [ false, false - ] - } + ], + "parameters": { + "Right": 0 + } + }, + "query": "SELECT priv_key_pem, certificate_pem FROM certificates WHERE id = 1;" }, "7d548d2472fa3707bd17364b4800e229b9c2b1c0a22e245bf4e635b9b16b8c24": { - "query": "INSERT INTO certificates (priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (?, ?, ?, datetime('now'), datetime('now'))", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 3 - }, - "nullable": [] - } + } + }, + "query": "INSERT INTO certificates (priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (?, ?, ?, datetime('now'), datetime('now'))" + }, + "82a8fa7eae8a73b5345015c72af024b4f21489b1d9b42235398d7eb8977fb132": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Right": 1 + } + }, + "query": "UPDATE account SET password = ?" }, "8595651866e7db772260bd79e19d55b7271fd795b82a99821c935a9237c1aa16": { - "query": "SELECT interface, key FROM tor WHERE package = ?", "describe": { "columns": [ { @@ -401,17 +410,17 @@ "type_info": "Blob" } ], - "parameters": { - "Right": 1 - }, "nullable": [ false, false - ] - } + ], + "parameters": { + "Right": 1 + } + }, + "query": "SELECT interface, key FROM tor WHERE package = ?" }, "9496e17a73672ac3675e02efa7c4bf8bd479b866c0d31fa1e3a85ef159310a57": { - "query": "SELECT priv_key_pem, certificate_pem FROM certificates WHERE lookup_string = ?", "describe": { "columns": [ { @@ -425,47 +434,47 @@ "type_info": "Text" } ], - "parameters": { - "Right": 1 - }, "nullable": [ false, false - ] - } + ], + "parameters": { + "Right": 1 + } + }, + "query": "SELECT priv_key_pem, certificate_pem FROM certificates WHERE lookup_string = ?" }, "9fcedab1ba34daa2c6ae97c5953c09821b35b55be75b0c66045ab31a2cf4553e": { - "query": "REPLACE INTO account (id, password, tor_key) VALUES (?, ?, ?)", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 3 - }, - "nullable": [] - } + } + }, + "query": "REPLACE INTO account (id, password, tor_key) VALUES (?, ?, ?)" }, "a1cbaac36d8e14c8c3e7276237c4824bff18861f91b0b08aa5791704c492acb7": { - "query": "INSERT INTO certificates (id, priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (1, ?, ?, NULL, datetime('now'), datetime('now'))", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 2 - }, - "nullable": [] - } + } + }, + "query": "INSERT INTO certificates (id, priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (1, ?, ?, NULL, datetime('now'), datetime('now'))" }, "a4e7162322b28508310b9de7ebc891e619b881ff6d3ea09eba13da39626ab12f": { - "query": "UPDATE cifs_shares SET hostname = ?, path = ?, username = ?, password = ? WHERE id = ?", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 5 - }, - "nullable": [] - } + } + }, + "query": "UPDATE cifs_shares SET hostname = ?, path = ?, username = ?, password = ? WHERE id = ?" }, "a6b0c8909a3a5d6d9156aebfb359424e6b5a1d1402e028219e21726f1ebd282e": { - "query": "SELECT fingerprint, openssh_pubkey, created_at FROM ssh_keys", "describe": { "columns": [ { @@ -484,18 +493,18 @@ "type_info": "Text" } ], - "parameters": { - "Right": 0 - }, "nullable": [ false, false, false - ] - } + ], + "parameters": { + "Right": 0 + } + }, + "query": "SELECT fingerprint, openssh_pubkey, created_at FROM ssh_keys" }, "abfdeea8cd10343b85f647d7abc5dc3bd0b5891101b143485938192ee3b8c907": { - "query": "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications ORDER BY id DESC LIMIT ?", "describe": { "columns": [ { @@ -539,9 +548,6 @@ "type_info": "Text" } ], - "parameters": { - "Right": 1 - }, "nullable": [ false, true, @@ -551,21 +557,24 @@ false, false, true - ] - } + ], + "parameters": { + "Right": 1 + } + }, + "query": "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications ORDER BY id DESC LIMIT ?" }, "b376d9e77e0861a9af2d1081ca48d14e83abc5a1546213d15bb570972c403beb": { - "query": "-- Add migration script here\nCREATE TABLE IF NOT EXISTS tor\n(\n package TEXT NOT NULL,\n interface TEXT NOT NULL,\n key BLOB NOT NULL CHECK (length(key) = 64),\n PRIMARY KEY (package, interface)\n);\nCREATE TABLE IF NOT EXISTS session\n(\n id TEXT NOT NULL PRIMARY KEY,\n logged_in TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n logged_out TIMESTAMP,\n last_active TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n user_agent TEXT,\n metadata TEXT NOT NULL DEFAULT 'null'\n);\nCREATE TABLE IF NOT EXISTS account\n(\n id INTEGER PRIMARY KEY CHECK (id = 0),\n password TEXT NOT NULL,\n tor_key BLOB NOT NULL CHECK (length(tor_key) = 64)\n);\nCREATE TABLE IF NOT EXISTS ssh_keys\n(\n fingerprint TEXT NOT NULL,\n openssh_pubkey TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (fingerprint)\n);\nCREATE TABLE IF NOT EXISTS certificates\n(\n id INTEGER PRIMARY KEY, -- Root = 0, Int = 1, Other = 2..\n priv_key_pem TEXT NOT NULL,\n certificate_pem TEXT NOT NULL,\n lookup_string TEXT UNIQUE,\n created_at TEXT,\n updated_at TEXT\n);\nCREATE TABLE IF NOT EXISTS notifications\n(\n id INTEGER PRIMARY KEY,\n package_id TEXT,\n created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n code INTEGER NOT NULL,\n level TEXT NOT NULL,\n title TEXT NOT NULL,\n message TEXT NOT NULL,\n data TEXT\n);\nCREATE TABLE IF NOT EXISTS cifs_shares\n(\n id INTEGER PRIMARY KEY,\n hostname TEXT NOT NULL,\n path TEXT NOT NULL,\n username TEXT NOT NULL,\n password TEXT\n);", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 0 - }, - "nullable": [] - } + } + }, + "query": "-- Add migration script here\nCREATE TABLE IF NOT EXISTS tor\n(\n package TEXT NOT NULL,\n interface TEXT NOT NULL,\n key BLOB NOT NULL CHECK (length(key) = 64),\n PRIMARY KEY (package, interface)\n);\nCREATE TABLE IF NOT EXISTS session\n(\n id TEXT NOT NULL PRIMARY KEY,\n logged_in TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n logged_out TIMESTAMP,\n last_active TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n user_agent TEXT,\n metadata TEXT NOT NULL DEFAULT 'null'\n);\nCREATE TABLE IF NOT EXISTS account\n(\n id INTEGER PRIMARY KEY CHECK (id = 0),\n password TEXT NOT NULL,\n tor_key BLOB NOT NULL CHECK (length(tor_key) = 64)\n);\nCREATE TABLE IF NOT EXISTS ssh_keys\n(\n fingerprint TEXT NOT NULL,\n openssh_pubkey TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (fingerprint)\n);\nCREATE TABLE IF NOT EXISTS certificates\n(\n id INTEGER PRIMARY KEY, -- Root = 0, Int = 1, Other = 2..\n priv_key_pem TEXT NOT NULL,\n certificate_pem TEXT NOT NULL,\n lookup_string TEXT UNIQUE,\n created_at TEXT,\n updated_at TEXT\n);\nCREATE TABLE IF NOT EXISTS notifications\n(\n id INTEGER PRIMARY KEY,\n package_id TEXT,\n created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n code INTEGER NOT NULL,\n level TEXT NOT NULL,\n title TEXT NOT NULL,\n message TEXT NOT NULL,\n data TEXT\n);\nCREATE TABLE IF NOT EXISTS cifs_shares\n(\n id INTEGER PRIMARY KEY,\n hostname TEXT NOT NULL,\n path TEXT NOT NULL,\n username TEXT NOT NULL,\n password TEXT\n);" }, "cc33fe2958fe7caeac6999a217f918a68b45ad596664170b4d07671c6ea49566": { - "query": "SELECT hostname, path, username, password FROM cifs_shares WHERE id = ?", "describe": { "columns": [ { @@ -589,19 +598,19 @@ "type_info": "Text" } ], - "parameters": { - "Right": 1 - }, "nullable": [ false, false, false, true - ] - } + ], + "parameters": { + "Right": 1 + } + }, + "query": "SELECT hostname, path, username, password FROM cifs_shares WHERE id = ?" }, "d5117054072476377f3c4f040ea429d4c9b2cf534e76f35c80a2bf60e8599cca": { - "query": "SELECT openssh_pubkey FROM ssh_keys", "describe": { "columns": [ { @@ -610,36 +619,36 @@ "type_info": "Text" } ], - "parameters": { - "Right": 0 - }, "nullable": [ false - ] - } + ], + "parameters": { + "Right": 0 + } + }, + "query": "SELECT openssh_pubkey FROM ssh_keys" }, "d54bd5b53f8c760e1f8cde604aa8b1bdc66e4e025a636bc44ffbcd788b5168fd": { - "query": "INSERT INTO notifications (package_id, code, level, title, message, data) VALUES (?, ?, ?, ?, ?, ?)", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 6 - }, - "nullable": [] - } + } + }, + "query": "INSERT INTO notifications (package_id, code, level, title, message, data) VALUES (?, ?, ?, ?, ?, ?)" }, "d79d608ceb862c15b741a6040044c6dd54a837a3a0c5594d15a6041c7bc68ea8": { - "query": "INSERT OR IGNORE INTO tor (package, interface, key) VALUES (?, ?, ?)", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 3 - }, - "nullable": [] - } + } + }, + "query": "INSERT OR IGNORE INTO tor (package, interface, key) VALUES (?, ?, ?)" }, "de2a5e90798d606047ab8180c044baac05469c0cdf151316bd58ee8c7196fdef": { - "query": "SELECT * FROM ssh_keys WHERE fingerprint = ?", "describe": { "columns": [ { @@ -658,18 +667,18 @@ "type_info": "Text" } ], - "parameters": { - "Right": 1 - }, "nullable": [ false, false, false - ] - } + ], + "parameters": { + "Right": 1 + } + }, + "query": "SELECT * FROM ssh_keys WHERE fingerprint = ?" }, "ed848affa5bf92997cd441e3a50b3616b6724df3884bd9d199b3225e0bea8a54": { - "query": "SELECT priv_key_pem, certificate_pem FROM certificates WHERE id = 0;", "describe": { "columns": [ { @@ -683,23 +692,24 @@ "type_info": "Text" } ], - "parameters": { - "Right": 0 - }, "nullable": [ false, false - ] - } + ], + "parameters": { + "Right": 0 + } + }, + "query": "SELECT priv_key_pem, certificate_pem FROM certificates WHERE id = 0;" }, "f63c8c5a8754b34a49ef5d67802fa2b72aa409bbec92ecc6901492092974b71a": { - "query": "DELETE FROM cifs_shares WHERE id = ?", "describe": { "columns": [], + "nullable": [], "parameters": { "Right": 1 - }, - "nullable": [] - } + } + }, + "query": "DELETE FROM cifs_shares WHERE id = ?" } } \ No newline at end of file diff --git a/backend/src/auth.rs b/backend/src/auth.rs index 030589373..968c11c61 100644 --- a/backend/src/auth.rs +++ b/backend/src/auth.rs @@ -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, + new_password: Option, +) -> 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); +impl SetPasswordReceipt { + pub async fn new(db: &mut Db) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup( + locks: &mut Vec, + ) -> impl FnOnce(&patch_db::Verifier) -> Result { + 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: &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, + #[arg(rename = "new-password")] new_password: Option, +) -> 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(()) +} diff --git a/backend/src/control.rs b/backend/src/control.rs index 4235b03ca..72b20b041 100644 --- a/backend/src/control.rs +++ b/backend/src/control.rs @@ -25,7 +25,7 @@ pub struct StartReceipts { } impl StartReceipts { - pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result { + pub async fn new(db: &mut impl DbHandle, id: &PackageId) -> Result { let mut locks = Vec::new(); let setup = Self::setup(&mut locks, id); diff --git a/backend/src/middleware/auth.rs b/backend/src/middleware/auth.rs index 772695a94..3d0b177dd 100644 --- a/backend/src/middleware/auth.rs +++ b/backend/src/middleware/auth.rs @@ -34,33 +34,21 @@ impl HasLoggedOutSessions { logged_out_sessions: impl IntoIterator, ctx: &RpcContext, ) -> Result { - let sessions = logged_out_sessions - .into_iter() - .by_ref() - .map(|x| x.as_logout_session_id()) - .collect::>(); + 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(())) } } diff --git a/backend/src/setup.rs b/backend/src/setup.rs index 75565d88f..135188a30 100644 --- a/backend/src/setup.rs +++ b/backend/src/setup.rs @@ -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 { pub async fn attach( #[context] ctx: SetupContext, #[arg] guid: Arc, + #[arg(rename = "embassy-password")] password: Option, ) -> Result { 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()), diff --git a/frontend/projects/setup-wizard/src/app/pages/recover/recover.page.ts b/frontend/projects/setup-wizard/src/app/pages/recover/recover.page.ts index 9b2ba6563..75dd411c3 100644 --- a/frontend/projects/setup-wizard/src/app/pages/recover/recover.page.ts +++ b/frontend/projects/setup-wizard/src/app/pages/recover/recover.page.ts @@ -32,7 +32,7 @@ export class RecoverPage { private readonly loadingCtrl: LoadingController, private readonly errorToastService: ErrorToastService, public readonly stateService: StateService, - ) {} + ) { } async ngOnInit() { await this.getDrives() @@ -99,8 +99,7 @@ export class RecoverPage { const alert = await this.alertCtrl.create({ header: 'Embassy Data Drive Detected', message: new IonicSafeString( - `${importableDrive.vendor || 'Unknown Vendor'} - ${ - importableDrive.model || 'Unknown Model' + `${importableDrive.vendor || 'Unknown Vendor'} - ${importableDrive.model || 'Unknown Model' } contains Embassy data. To use this drive and its data as-is, click "Use Drive". This will complete the setup process.

Important. If you are trying to restore from backup or update from 0.2.x, DO NOT click "Use Drive". Instead, click "Cancel" and follow instructions.`, ), buttons: [ @@ -111,8 +110,16 @@ export class RecoverPage { { text: 'Use Drive', handler: async () => { - if (importableDrive.guid) - await this.importDrive(importableDrive.guid) + const modal = await this.modalController.create({ + component: PasswordPage, + componentProps: { storageDrive: importableDrive }, + }) + modal.onDidDismiss().then(res => { + if (res.data && res.data.password) { + this.importDrive(importableDrive.guid!, res.data.password) + } + }) + await modal.present() }, }, ], @@ -201,13 +208,13 @@ export class RecoverPage { } } - private async importDrive(guid: string) { + private async importDrive(guid: string, password: string) { const loader = await this.loadingCtrl.create({ message: 'Importing Drive', }) await loader.present() try { - await this.stateService.importDrive(guid) + await this.stateService.importDrive(guid, password) await this.navCtrl.navigateForward(`/success`) } catch (e: any) { this.errorToastService.present(e) diff --git a/frontend/projects/setup-wizard/src/app/services/api/api.service.ts b/frontend/projects/setup-wizard/src/app/services/api/api.service.ts index abda4fced..0e719ae4d 100644 --- a/frontend/projects/setup-wizard/src/app/services/api/api.service.ts +++ b/frontend/projects/setup-wizard/src/app/services/api/api.service.ts @@ -8,7 +8,7 @@ export abstract class ApiService { // encrypted abstract verifyCifs(cifs: CifsRecoverySource): Promise // setup.cifs.verify abstract verifyProductKey(): Promise // echo - throws error if invalid - abstract importDrive(guid: string): Promise // setup.execute + abstract importDrive(importInfo: ImportDriveReq): Promise // setup.attach abstract setupEmbassy(setupInfo: SetupEmbassyReq): Promise // setup.execute abstract setupComplete(): Promise // setup.complete } @@ -18,6 +18,11 @@ export interface GetStatusRes { migrating: boolean } +export interface ImportDriveReq { + guid: string + 'embassy-password': string +} + export interface SetupEmbassyReq { 'embassy-logicalname': string 'embassy-password': string diff --git a/frontend/projects/setup-wizard/src/app/services/api/live-api.service.ts b/frontend/projects/setup-wizard/src/app/services/api/live-api.service.ts index 4e83f6f0d..36c2f714d 100644 --- a/frontend/projects/setup-wizard/src/app/services/api/live-api.service.ts +++ b/frontend/projects/setup-wizard/src/app/services/api/live-api.service.ts @@ -2,11 +2,11 @@ import { Injectable } from '@angular/core' import { ApiService, CifsRecoverySource, - DiskInfo, DiskListResponse, DiskRecoverySource, EmbassyOSRecoveryInfo, GetStatusRes, + ImportDriveReq, RecoveryStatusRes, SetupEmbassyReq, SetupEmbassyRes, @@ -80,10 +80,10 @@ export class LiveApiService extends ApiService { }) } - async importDrive(guid: string) { + async importDrive(params: ImportDriveReq) { const res = await this.http.rpcRequest({ method: 'setup.attach', - params: { guid }, + params: params as any, }) return { diff --git a/frontend/projects/setup-wizard/src/app/services/api/mock-api.service.ts b/frontend/projects/setup-wizard/src/app/services/api/mock-api.service.ts index 4df65203b..0f7841cc5 100644 --- a/frontend/projects/setup-wizard/src/app/services/api/mock-api.service.ts +++ b/frontend/projects/setup-wizard/src/app/services/api/mock-api.service.ts @@ -1,6 +1,11 @@ import { Injectable } from '@angular/core' import { pauseFor } from '@start9labs/shared' -import { ApiService, CifsRecoverySource, SetupEmbassyReq } from './api.service' +import { + ApiService, + CifsRecoverySource, + ImportDriveReq, + SetupEmbassyReq, +} from './api.service' let tries = 0 @@ -84,7 +89,7 @@ export class MockApiService extends ApiService { return } - async importDrive(guid: string) { + async importDrive(params: ImportDriveReq) { await pauseFor(3000) return setupRes } diff --git a/frontend/projects/setup-wizard/src/app/services/state.service.ts b/frontend/projects/setup-wizard/src/app/services/state.service.ts index 7379443fe..8668191b6 100644 --- a/frontend/projects/setup-wizard/src/app/services/state.service.ts +++ b/frontend/projects/setup-wizard/src/app/services/state.service.ts @@ -69,8 +69,11 @@ export class StateService { setTimeout(() => this.pollDataTransferProgress(), 0) // prevent call stack from growing } - async importDrive(guid: string): Promise { - const ret = await this.apiService.importDrive(guid) + async importDrive(guid: string, password: string): Promise { + const ret = await this.apiService.importDrive({ + guid, + 'embassy-password': password, + }) this.torAddress = ret['tor-address'] this.lanAddress = ret['lan-address'] this.cert = ret['root-ca'] diff --git a/frontend/projects/ui/src/app/pages/login/login.page.ts b/frontend/projects/ui/src/app/pages/login/login.page.ts index cd7bf4bbf..354b8c2df 100644 --- a/frontend/projects/ui/src/app/pages/login/login.page.ts +++ b/frontend/projects/ui/src/app/pages/login/login.page.ts @@ -50,7 +50,8 @@ export class LoginPage { .setVerified() .then(() => this.router.navigate([''], { replaceUrl: true })) } catch (e: any) { - this.error = e.code === 34 ? 'Invalid Password' : e.message + // code 7 is for incorrect password + this.error = e.code === 7 ? 'Invalid Password' : e.message } finally { this.loader.dismiss() } diff --git a/frontend/projects/ui/src/app/services/http.service.ts b/frontend/projects/ui/src/app/services/http.service.ts index 20eff39f7..a4d4e53c8 100644 --- a/frontend/projects/ui/src/app/services/http.service.ts +++ b/frontend/projects/ui/src/app/services/http.service.ts @@ -35,6 +35,7 @@ export class HttpService { const res = await this.httpRequest>(httpOpts) if (isRpcError(res)) { + // code 34 is authorization error ie. invalid session if (res.error.code === 34) this.auth.setUnverified() throw new RpcError(res.error) } diff --git a/libs/js_engine/src/lib.rs b/libs/js_engine/src/lib.rs index d256e16c7..6cb490494 100644 --- a/libs/js_engine/src/lib.rs +++ b/libs/js_engine/src/lib.rs @@ -1,6 +1,8 @@ use std::path::{Path, PathBuf}; use std::pin::Pin; use std::sync::Arc; +use std::time::SystemTime; + use deno_core::anyhow::{anyhow, bail}; use deno_core::error::AnyError; use deno_core::{ @@ -11,7 +13,6 @@ use helpers::{script_dir, NonDetachingJoinHandle}; use models::{PackageId, ProcedureName, Version, VolumeId}; use serde::{Deserialize, Serialize}; use serde_json::Value; -use std::time::SystemTime; use tokio::io::AsyncReadExt; pub trait PathForVolumeId: Send + Sync { @@ -331,18 +332,18 @@ impl JsExecutionEnvironment { mod fns { use std::cell::RefCell; use std::convert::TryFrom; + use std::os::unix::fs::MetadataExt; use std::path::{Path, PathBuf}; use std::rc::Rc; + use deno_core::anyhow::{anyhow, bail}; use deno_core::error::AnyError; use deno_core::*; use models::VolumeId; use serde_json::Value; - use std::os::unix::fs::MetadataExt; - - use crate::{system_time_as_unix_ms, MetadataJs}; use super::{AnswerState, JsContext}; + use crate::{system_time_as_unix_ms, MetadataJs}; #[op] async fn read_file( @@ -633,4 +634,4 @@ fn system_time_as_unix_ms(system_time: &SystemTime) -> Option { .as_millis() .try_into() .ok() -} \ No newline at end of file +}