mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-29 16:30:55 +00:00
feat: add backup dialog
This commit is contained in:
parent
95294a80cb
commit
71a2290b8f
@ -1,13 +1,36 @@
|
|||||||
import { CancelIcon } from "@lume/icons";
|
import { ArrowRightIcon, CancelIcon } from "@lume/icons";
|
||||||
import * as Dialog from "@radix-ui/react-dialog";
|
import * as Dialog from "@radix-ui/react-dialog";
|
||||||
|
import { Link, useParams } from "@tanstack/react-router";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export function BackupDialog() {
|
export function BackupDialog() {
|
||||||
const [key, setKey] = useState("");
|
// @ts-ignore, magic!!!
|
||||||
|
const { account } = useParams({ strict: false });
|
||||||
|
|
||||||
|
const [key, setKey] = useState(null);
|
||||||
const [passphase, setPassphase] = useState("");
|
const [passphase, setPassphase] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const encryptKey = async () => {
|
const encryptKey = async () => {
|
||||||
console.log("****");
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const encrypted: string = await invoke("get_encrypted_key", {
|
||||||
|
npub: account,
|
||||||
|
password: passphase,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (encrypted) {
|
||||||
|
setKey(encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
} catch (e) {
|
||||||
|
setLoading(false);
|
||||||
|
toast.error(String(e));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -41,20 +64,7 @@ export function BackupDialog() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex flex-col gap-5">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<label htmlFor="nsec">
|
<label htmlFor="nsec">Set a passphase to secure your key</label>
|
||||||
Copy this key and keep it in safe place
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
name="nsec"
|
|
||||||
type="text"
|
|
||||||
className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<label htmlFor="nsec">
|
|
||||||
<span className="font-semibold">(Recommend)</span> Set a
|
|
||||||
passphase to secure your key
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
name="passphase"
|
name="passphase"
|
||||||
@ -63,19 +73,45 @@ export function BackupDialog() {
|
|||||||
onChange={(e) => setPassphase(e.target.value)}
|
onChange={(e) => setPassphase(e.target.value)}
|
||||||
className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900"
|
className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900"
|
||||||
/>
|
/>
|
||||||
{passphase.length ? (
|
|
||||||
<div className="absolute right-2 top-0 h-11 py-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={encryptKey}
|
|
||||||
className="inline-flex h-full items-center justify-center rounded-md bg-blue-500 px-3 text-sm font-medium text-white hover:bg-blue-600"
|
|
||||||
>
|
|
||||||
Update
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{key ? (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label htmlFor="nsec">
|
||||||
|
Copy this key and keep it in safe place
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="nsec"
|
||||||
|
type="text"
|
||||||
|
value={key}
|
||||||
|
readOnly
|
||||||
|
className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
{!key ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={encryptKey}
|
||||||
|
disabled={loading}
|
||||||
|
className="inline-flex h-11 w-full items-center justify-between gap-1.5 rounded-lg bg-blue-500 px-5 font-medium text-white hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
<div className="size-5" />
|
||||||
|
<div>Submit</div>
|
||||||
|
<ArrowRightIcon className="size-5" />
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
to="/$account/home"
|
||||||
|
params={{ account }}
|
||||||
|
search={{ guest: false }}
|
||||||
|
className="inline-flex h-11 w-full items-center justify-center gap-1.5 rounded-lg bg-blue-500 px-5 font-medium text-white hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
I've safely store my account key
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
|
@ -22,15 +22,17 @@ export class Ark {
|
|||||||
public async get_all_accounts() {
|
public async get_all_accounts() {
|
||||||
try {
|
try {
|
||||||
const accounts: Account[] = [];
|
const accounts: Account[] = [];
|
||||||
const cmd: string[] = await invoke("get_all_nsecs");
|
const cmd: string[] = await invoke("get_accounts");
|
||||||
|
|
||||||
for (const item of cmd) {
|
if (cmd) {
|
||||||
accounts.push({ npub: item.replace(".npub", "") });
|
for (const item of cmd) {
|
||||||
|
accounts.push({ npub: item.replace(".npub", "") });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.accounts = accounts;
|
||||||
|
return accounts;
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
this.accounts = accounts;
|
|
||||||
return accounts;
|
|
||||||
} catch (e) {
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,10 +49,10 @@ pub async fn show_in_folder(path: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_all_nsecs(app_handle: tauri::AppHandle) -> Result<Vec<String>, ()> {
|
pub fn get_accounts(app_handle: tauri::AppHandle) -> Result<Vec<String>, ()> {
|
||||||
let dir = app_handle.path().app_config_dir().unwrap();
|
let dir = app_handle.path().home_dir().unwrap();
|
||||||
|
|
||||||
if let Ok(paths) = std::fs::read_dir(dir) {
|
if let Ok(paths) = std::fs::read_dir(dir.join("Lume/")) {
|
||||||
let files = paths
|
let files = paths
|
||||||
.filter_map(|res| res.ok())
|
.filter_map(|res| res.ok())
|
||||||
.map(|dir_entry| dir_entry.path())
|
.map(|dir_entry| dir_entry.path())
|
||||||
|
@ -7,6 +7,8 @@ pub mod commands;
|
|||||||
pub mod nostr;
|
pub mod nostr;
|
||||||
pub mod tray;
|
pub mod tray;
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
use tauri_plugin_autostart::MacosLauncher;
|
use tauri_plugin_autostart::MacosLauncher;
|
||||||
@ -20,11 +22,15 @@ fn main() {
|
|||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
let _tray = tray::create_tray(app.handle()).unwrap();
|
let _tray = tray::create_tray(app.handle()).unwrap();
|
||||||
let handle = app.handle().clone();
|
let handle = app.handle().clone();
|
||||||
let config_dir = handle.path().app_config_dir().unwrap();
|
let resource_dir = handle.path().resource_dir().unwrap();
|
||||||
|
let home_dir = handle.path().home_dir().unwrap();
|
||||||
|
|
||||||
|
// create data folder if not exist
|
||||||
|
fs::create_dir_all(home_dir.join("Lume/")).unwrap();
|
||||||
|
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
// Create nostr database connection
|
// Create nostr database connection
|
||||||
let nostr_db = SQLiteDatabase::open(config_dir.join("nostr.db"))
|
let nostr_db = SQLiteDatabase::open(resource_dir.join("lume.db"))
|
||||||
.await
|
.await
|
||||||
.expect("Open database failed.");
|
.expect("Open database failed.");
|
||||||
|
|
||||||
@ -79,6 +85,7 @@ fn main() {
|
|||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
nostr::keys::create_keys,
|
nostr::keys::create_keys,
|
||||||
nostr::keys::save_key,
|
nostr::keys::save_key,
|
||||||
|
nostr::keys::get_encrypted_key,
|
||||||
nostr::keys::verify_signer,
|
nostr::keys::verify_signer,
|
||||||
nostr::keys::load_selected_account,
|
nostr::keys::load_selected_account,
|
||||||
nostr::keys::event_to_bech32,
|
nostr::keys::event_to_bech32,
|
||||||
@ -108,15 +115,21 @@ fn main() {
|
|||||||
nostr::event::upvote,
|
nostr::event::upvote,
|
||||||
nostr::event::downvote,
|
nostr::event::downvote,
|
||||||
commands::folder::show_in_folder,
|
commands::folder::show_in_folder,
|
||||||
commands::folder::get_all_nsecs,
|
commands::folder::get_accounts,
|
||||||
commands::opg::fetch_opg,
|
commands::opg::fetch_opg,
|
||||||
])
|
])
|
||||||
.build(tauri::generate_context!())
|
.build(tauri::generate_context!())
|
||||||
.expect("error while running tauri application")
|
.expect("error while running tauri application")
|
||||||
.run(|_app_handle, event| match event {
|
.run(|app, event| {
|
||||||
tauri::RunEvent::ExitRequested { api, .. } => {
|
if let tauri::RunEvent::Opened { urls } = event {
|
||||||
api.prevent_exit();
|
if let Some(w) = app.get_webview_window("main") {
|
||||||
|
let urls = urls
|
||||||
|
.iter()
|
||||||
|
.map(|u| u.as_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(",");
|
||||||
|
let _ = w.eval(&format!("window.onFileOpen(`{urls}`)"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -53,13 +53,14 @@ pub async fn save_key(
|
|||||||
let npub = nostr_keys.public_key().to_bech32().unwrap();
|
let npub = nostr_keys.public_key().to_bech32().unwrap();
|
||||||
let nsec = nostr_keys.secret_key().unwrap().to_bech32().unwrap();
|
let nsec = nostr_keys.secret_key().unwrap().to_bech32().unwrap();
|
||||||
|
|
||||||
if let Ok(config_dir) = app_handle.path().app_config_dir() {
|
let home_dir = app_handle.path().home_dir().unwrap();
|
||||||
let file_path = npub.clone() + ".npub";
|
let app_dir = home_dir.join("Lume/");
|
||||||
let _ = File::create(config_dir.join(file_path)).unwrap();
|
|
||||||
|
|
||||||
let keyring = Entry::new("Lume Secret Storage", &npub).unwrap();
|
let file_path = npub.clone() + ".npub";
|
||||||
let _ = keyring.set_password(&nsec);
|
let _ = File::create(app_dir.join(file_path)).unwrap();
|
||||||
}
|
|
||||||
|
let keyring = Entry::new("Lume Secret Storage", &npub).unwrap();
|
||||||
|
let _ = keyring.set_password(&nsec);
|
||||||
|
|
||||||
let signer = NostrSigner::Keys(nostr_keys);
|
let signer = NostrSigner::Keys(nostr_keys);
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
@ -84,6 +85,24 @@ pub async fn verify_signer(state: State<'_, Nostr>) -> Result<bool, ()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_encrypted_key(npub: &str, password: &str) -> Result<String, String> {
|
||||||
|
let keyring = Entry::new("Lume Secret Storage", npub).unwrap();
|
||||||
|
|
||||||
|
if let Ok(nsec) = keyring.get_password() {
|
||||||
|
let secret_key = SecretKey::from_bech32(nsec).expect("Get secret key failed");
|
||||||
|
let new_key = EncryptedSecretKey::new(&secret_key, password, 16, KeySecurity::Medium);
|
||||||
|
|
||||||
|
if let Ok(key) = new_key {
|
||||||
|
Ok(key.to_bech32().unwrap())
|
||||||
|
} else {
|
||||||
|
Err("Encrypt key failed".into())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err("Key not found".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn load_selected_account(npub: &str, state: State<'_, Nostr>) -> Result<bool, String> {
|
pub async fn load_selected_account(npub: &str, state: State<'_, Nostr>) -> Result<bool, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
|
Loading…
Reference in New Issue
Block a user