Export drops threads; a broken keyring stops with the secret recoverable
This commit is contained in:
parent
96174c4776
commit
caa580fce9
|
|
@ -40,7 +40,7 @@ fn scrub(v: &mut Value) {
|
||||||
pub fn export(b: &Bot) -> String {
|
pub fn export(b: &Bot) -> String {
|
||||||
let mut v = serde_json::to_value(b).unwrap_or(Value::Null);
|
let mut v = serde_json::to_value(b).unwrap_or(Value::Null);
|
||||||
if let Value::Object(m) = &mut v {
|
if let Value::Object(m) = &mut v {
|
||||||
for k in ["messages", "allow_rules", "unread", "last_turn", "workdir", "pinned", "id"] {
|
for k in ["messages", "threads", "allow_rules", "unread", "last_turn", "workdir", "pinned", "id"] {
|
||||||
m.remove(k);
|
m.remove(k);
|
||||||
}
|
}
|
||||||
if let Some(Value::Array(routines)) = m.get_mut("routines") {
|
if let Some(Value::Array(routines)) = m.get_mut("routines") {
|
||||||
|
|
|
||||||
15
src/vault.rs
15
src/vault.rs
|
|
@ -178,7 +178,10 @@ fn onepassword(host: &str) -> Option<Login> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let items: Value = serde_json::from_slice(&list.stdout).ok()?;
|
let items: Value = serde_json::from_slice(&list.stdout).ok()?;
|
||||||
let id = items.as_array()?.iter().find(|i| i["urls"].as_array().into_iter().flatten().any(|u| host_of(u["href"].as_str().unwrap_or("")).ends_with(host)))?["id"].as_str()?.to_string();
|
// Match the host exactly or as a subdomain (with the dot): "notgoogle.com" must not
|
||||||
|
// answer for "google.com" (a plain ends_with fills the wrong login on a lookalike domain).
|
||||||
|
let host_match = |h: &str, want: &str| h == want || h.ends_with(&format!(".{want}"));
|
||||||
|
let id = items.as_array()?.iter().find(|i| i["urls"].as_array().into_iter().flatten().any(|u| host_match(&host_of(u["href"].as_str().unwrap_or("")), host)))?["id"].as_str()?.to_string();
|
||||||
let got = Command::new("op").env("PATH", &path).args(["item", "get", &id, "--fields", "label=username,label=password", "--reveal", "--format", "json"]).output().ok()?;
|
let got = Command::new("op").env("PATH", &path).args(["item", "get", &id, "--fields", "label=username,label=password", "--reveal", "--format", "json"]).output().ok()?;
|
||||||
let fields: Value = serde_json::from_slice(&got.stdout).ok()?;
|
let fields: Value = serde_json::from_slice(&got.stdout).ok()?;
|
||||||
let arr = fields.as_array()?;
|
let arr = fields.as_array()?;
|
||||||
|
|
@ -412,9 +415,6 @@ pub fn fill_login(plan: &LoginPlan, submit: bool) -> Result<String, String> {
|
||||||
let (username, password, source) = match (&plan.item, &plan.other) {
|
let (username, password, source) = match (&plan.item, &plan.other) {
|
||||||
(Some(i), _) => {
|
(Some(i), _) => {
|
||||||
let (u, p) = crate::vault_store::login_secret(&i.id).ok_or("保險庫裡沒有這筆登入資訊")?;
|
let (u, p) = crate::vault_store::login_secret(&i.id).ok_or("保險庫裡沒有這筆登入資訊")?;
|
||||||
if !plan.matched {
|
|
||||||
crate::vault_store::add_site(&i.id, &plan.host)?;
|
|
||||||
}
|
|
||||||
(u, p, "保險庫")
|
(u, p, "保險庫")
|
||||||
}
|
}
|
||||||
(None, Some(l)) => (l.username.clone(), l.password.clone(), l.source),
|
(None, Some(l)) => (l.username.clone(), l.password.clone(), l.source),
|
||||||
|
|
@ -425,6 +425,13 @@ pub fn fill_login(plan: &LoginPlan, submit: bool) -> Result<String, String> {
|
||||||
if got["password"].as_bool() != Some(true) && got["user"].as_bool() != Some(true) {
|
if got["password"].as_bool() != Some(true) && got["user"].as_bool() != Some(true) {
|
||||||
return Err("這一頁找不到帳號或密碼欄位。先打開登入頁(有些網站要先按「登入」或先填帳號、按下一步才出現密碼欄)。".into());
|
return Err("這一頁找不到帳號或密碼欄位。先打開登入頁(有些網站要先按「登入」或先填帳號、按下一步才出現密碼欄)。".into());
|
||||||
}
|
}
|
||||||
|
// Remember the domain only after the fill worked: adding it on a failed (or lookalike)
|
||||||
|
// page made the next visit show matched:true and skip the anti-phishing warning.
|
||||||
|
if !plan.matched {
|
||||||
|
if let Some(i) = &plan.item {
|
||||||
|
crate::vault_store::add_site(&i.id, &plan.host)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"已在 {} 填入{}{}(來自{source},你看不到密碼)。{}",
|
"已在 {} 填入{}{}(來自{source},你看不到密碼)。{}",
|
||||||
plan.host,
|
plan.host,
|
||||||
|
|
|
||||||
|
|
@ -381,8 +381,12 @@ fn store_key(key: &[u8]) -> Result<(), String> {
|
||||||
fn key(create: bool) -> Result<aead::LessSafeKey, String> {
|
fn key(create: bool) -> Result<aead::LessSafeKey, String> {
|
||||||
let k = match read_key() {
|
let k = match read_key() {
|
||||||
Some(k) if k.len() == 32 => k,
|
Some(k) if k.len() == 32 => k,
|
||||||
_ if !create => return Err("找不到保險庫的金鑰(系統鑰匙圈裡的「AnyBot vault key」)".into()),
|
// An entry exists but is not a key: generating a fresh one (and -U overwriting the
|
||||||
_ => {
|
// entry) would destroy the only copy of the old key — every saved password would be
|
||||||
|
// undecryptable forever. Stop and tell the person instead.
|
||||||
|
Some(_) => return Err("保險庫金鑰損毀:鑰匙圈裡的「AnyBot vault key」內容不正確。已停止動作,避免覆蓋後舊密碼永遠解不開;若你確定不需要舊密碼,可手動刪除該鑰匙圈項目再試。".into()),
|
||||||
|
None if !create => return Err("找不到保險庫的金鑰(系統鑰匙圈裡的「AnyBot vault key」)".into()),
|
||||||
|
None => {
|
||||||
let mut k = vec![0u8; 32];
|
let mut k = vec![0u8; 32];
|
||||||
SystemRandom::new().fill(&mut k).map_err(|_| "產生金鑰失敗")?;
|
SystemRandom::new().fill(&mut k).map_err(|_| "產生金鑰失敗")?;
|
||||||
store_key(&k)?;
|
store_key(&k)?;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue