From cba94ab99e16e0fac71553e3c909626bebd47f24 Mon Sep 17 00:00:00 2001 From: Ren Amamiya <123083837+reyamir@users.noreply.github.com> Date: Sun, 9 Jul 2023 16:55:25 +0700 Subject: [PATCH] secure privkey --- src-tauri/Cargo.lock | 14 +- src-tauri/Cargo.toml | 4 +- src-tauri/src/main.rs | 10 +- src-tauri/tauri.conf.json | 3 + src/app.tsx | 6 + src/app/auth/create/step-1.tsx | 25 ++- src/app/auth/create/step-2.tsx | 202 ++++++++++----------- src/app/auth/create/step-3.tsx | 199 ++++++++++++-------- src/app/auth/create/step-4.tsx | 262 ++++++--------------------- src/app/auth/create/step-5.tsx | 224 +++++++++++++++++++++++ src/app/auth/import/step-1.tsx | 53 ++++-- src/app/auth/import/step-2.tsx | 152 +++++++++++----- src/app/auth/import/step-3.tsx | 88 +++++++++ src/app/auth/unlock.tsx | 154 ++++++++++++++++ src/libs/storage.tsx | 3 +- src/shared/protected.tsx | 7 + src/stores/onboarding.tsx | 42 +++-- src/stores/stronghold.tsx | 13 ++ src/utils/hooks/usePublish.tsx | 6 +- src/utils/hooks/useSecureStorage.tsx | 43 +++++ 20 files changed, 1011 insertions(+), 499 deletions(-) create mode 100644 src/app/auth/create/step-5.tsx create mode 100644 src/app/auth/import/step-3.tsx create mode 100644 src/app/auth/unlock.tsx create mode 100644 src/stores/stronghold.tsx create mode 100644 src/utils/hooks/useSecureStorage.tsx diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 72decf18..8b447454 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2452,6 +2452,7 @@ version = "1.0.1" dependencies = [ "cocoa", "objc", + "rand 0.8.5", "rust-argon2", "serde", "serde_json", @@ -2461,7 +2462,6 @@ dependencies = [ "tauri-plugin-autostart", "tauri-plugin-single-instance", "tauri-plugin-sql", - "tauri-plugin-store", "tauri-plugin-stronghold", ] @@ -4847,18 +4847,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tauri-plugin-store" -version = "0.0.0" -source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#0d0ed7b9075ee21f37d787217fba3ef0784b2449" -dependencies = [ - "log", - "serde", - "serde_json", - "tauri", - "thiserror", -] - [[package]] name = "tauri-plugin-stronghold" version = "0.0.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index cf841cef..a9363a2c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -16,13 +16,13 @@ tauri-build = { version = "1.2", features = [] } [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -tauri = { version = "1.2", features = [ "clipboard-read-text", "clipboard-write-text", "dialog-open", "fs-read-dir", "fs-read-file", "http-all", "http-multipart", "notification-all", "os-all", "process-relaunch", "shell-open", "system-tray", "updater", "window-close", "window-start-dragging"] } +tauri = { version = "1.2", features = [ "path-all", "fs-read-dir", "fs-read-file", "clipboard-read-text", "clipboard-write-text", "dialog-open", "http-all", "http-multipart", "notification-all", "os-all", "process-relaunch", "shell-open", "system-tray", "updater", "window-close", "window-start-dragging"] } tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } -tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } tauri-plugin-stronghold = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } sqlx-cli = {version = "0.7.0", default-features = false, features = ["sqlite"] } rust-argon2 = "1.0" +rand = "0.8.5" [dependencies.tauri-plugin-sql] git = "https://github.com/tauri-apps/plugins-workspace" diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index f0dc875a..f8a53515 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -7,6 +7,7 @@ #[macro_use] extern crate objc; +// use rand::distributions::{Alphanumeric, DistString}; use tauri::{Manager, WindowEvent}; use tauri_plugin_autostart::MacosLauncher; use tauri_plugin_sql::{Migration, MigrationKind}; @@ -121,8 +122,13 @@ fn main() { ..Default::default() }; - let key = argon2::hash_raw(password.as_ref(), b"SALT_TODO", &config) - .expect("failed to hash password"); + // let salt = Alphanumeric.sample_string(&mut rand::thread_rng(), 12); + let key = argon2::hash_raw( + password.as_ref(), + b"LUME_NEED_RUST_DEVELOPER_HELP_MAKE_SALT_RANDOM", + &config, + ) + .expect("failed to hash password"); key.to_vec() }) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index d5143697..82f04ba3 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -41,6 +41,9 @@ "$VIDEO/*" ] }, + "path": { + "all": true + }, "shell": { "all": false, "open": true diff --git a/src/app.tsx b/src/app.tsx index 40543fe1..e644b4b8 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -5,10 +5,13 @@ import { CreateStep1Screen } from '@app/auth/create/step-1'; import { CreateStep2Screen } from '@app/auth/create/step-2'; import { CreateStep3Screen } from '@app/auth/create/step-3'; import { CreateStep4Screen } from '@app/auth/create/step-4'; +import { CreateStep5Screen } from '@app/auth/create/step-5'; import { AuthImportScreen } from '@app/auth/import'; import { ImportStep1Screen } from '@app/auth/import/step-1'; import { ImportStep2Screen } from '@app/auth/import/step-2'; +import { ImportStep3Screen } from '@app/auth/import/step-3'; import { OnboardingScreen } from '@app/auth/onboarding'; +import { UnlockScreen } from '@app/auth/unlock'; import { WelcomeScreen } from '@app/auth/welcome'; import { ChannelScreen } from '@app/channel'; import { ChatScreen } from '@app/chat'; @@ -51,6 +54,7 @@ const router = createBrowserRouter([ children: [ { path: '', element: }, { path: 'step-2', element: }, + { path: 'step-3', element: }, ], }, { @@ -61,8 +65,10 @@ const router = createBrowserRouter([ { path: 'step-2', element: }, { path: 'step-3', element: }, { path: 'step-4', element: }, + { path: 'step-5', element: }, ], }, + { path: 'unlock', element: }, ], }, { diff --git a/src/app/auth/create/step-1.tsx b/src/app/auth/create/step-1.tsx index de1dd4ef..8341f324 100644 --- a/src/app/auth/create/step-1.tsx +++ b/src/app/auth/create/step-1.tsx @@ -8,11 +8,17 @@ import { createAccount } from '@libs/storage'; import { Button } from '@shared/button'; import { EyeOffIcon, EyeOnIcon, LoaderIcon } from '@shared/icons'; +import { useOnboarding } from '@stores/onboarding'; + export function CreateStep1Screen() { const navigate = useNavigate(); const queryClient = useQueryClient(); - const [type, setType] = useState('password'); + const [setPubkey, setPrivkey] = useOnboarding((state) => [ + state.setPubkey, + state.setPrivkey, + ]); + const [privkeyInput, setPrivkeyInput] = useState('password'); const [loading, setLoading] = useState(false); const privkey = useMemo(() => generatePrivateKey(), []); @@ -22,10 +28,10 @@ export function CreateStep1Screen() { // toggle private key const showPrivateKey = () => { - if (type === 'password') { - setType('text'); + if (privkeyInput === 'password') { + setPrivkeyInput('text'); } else { - setType('password'); + setPrivkeyInput('password'); } }; @@ -33,11 +39,10 @@ export function CreateStep1Screen() { mutationFn: (data: { npub: string; pubkey: string; - privkey: string; follows: null | string[][]; is_active: number; }) => { - return createAccount(data.npub, data.pubkey, data.privkey, null, 1); + return createAccount(data.npub, data.pubkey, null, 1); }, onSuccess: (data) => { queryClient.setQueryData(['currentAccount'], data); @@ -47,10 +52,12 @@ export function CreateStep1Screen() { const submit = () => { setLoading(true); + setPubkey(pubkey); + setPrivkey(privkey); + account.mutate({ npub, pubkey, - privkey, follows: null, is_active: 1, }); @@ -80,7 +87,7 @@ export function CreateStep1Screen() {
@@ -89,7 +96,7 @@ export function CreateStep1Screen() { onClick={() => showPrivateKey()} className="group absolute right-2 top-1/2 -translate-y-1/2 transform rounded p-1 hover:bg-zinc-700" > - {type === 'password' ? ( + {privkeyInput === 'password' ? ( = async (values) => { + return { + values: values.password ? values : {}, + errors: !values.password + ? { + password: { + type: 'required', + message: 'This is required.', + }, + } + : {}, + }; +}; export function CreateStep2Screen() { const navigate = useNavigate(); - const createProfile = useOnboarding((state: any) => state.createProfile); + const setPassword = useStronghold((state) => state.setPassword); - const [picture, setPicture] = useState(DEFAULT_AVATAR); - const [banner, setBanner] = useState(''); + const [pubkey, privkey] = useOnboarding((state) => [state.pubkey, state.privkey]); + const [passwordInput, setPasswordInput] = useState('password'); const [loading, setLoading] = useState(false); + const { save } = useSecureStorage(); + + // toggle private key + const showPassword = () => { + if (passwordInput === 'password') { + setPasswordInput('text'); + } else { + setPasswordInput('password'); + } + }; + const { register, + setError, handleSubmit, - formState: { isDirty, isValid }, - } = useForm(); + formState: { errors, isDirty, isValid }, + } = useForm({ resolver }); - const onSubmit = (data: any) => { + const onSubmit = async (data: { [x: string]: string }) => { setLoading(true); - try { - const profile = { - ...data, - username: data.name, - display_name: data.name, - bio: data.about, - }; - createProfile(profile); + if (data.password.length > 3) { + // add password to local state + setPassword(data.password); + + // save privkey to secure storage + await save(pubkey, privkey, data.password); + // redirect to next step - setTimeout(() => navigate('/auth/create/step-3', { replace: true }), 1200); - } catch { - console.log('error'); + navigate('/auth/create/step-3', { replace: true }); + } else { + setLoading(false); + setError('password', { + type: 'custom', + message: 'Password is required and must be greater than 3', + }); } }; return (
-

Create your profile

+

+ Set password to secure your key +

-
-
- - -
-
- user's banner + +
+
+ -
- -
+
-
-
- user's avatar -
- -
-
+
+

+ Password is use to secure your key store in local machine, when you move + to other clients, you just need to copy your private key as nsec or + hexstring +

+ + {errors.password &&

{errors.password.message}

} +
-
-
- - -
-
- -