update stronghold

This commit is contained in:
Ren Amamiya
2023-07-20 08:57:58 +07:00
parent a80477b40e
commit bbfdb139c6
16 changed files with 74 additions and 127 deletions

View File

@@ -28,7 +28,7 @@
"cheerio": "1.0.0-rc.12", "cheerio": "1.0.0-rc.12",
"dayjs": "^1.11.9", "dayjs": "^1.11.9",
"destr": "^1.2.2", "destr": "^1.2.2",
"framer-motion": "^10.12.22", "framer-motion": "^10.13.0",
"get-urls": "^11.0.0", "get-urls": "^11.0.0",
"immer": "^10.0.2", "immer": "^10.0.2",
"light-bolt11-decoder": "^3.0.0", "light-bolt11-decoder": "^3.0.0",

28
pnpm-lock.yaml generated
View File

@@ -38,8 +38,8 @@ dependencies:
specifier: ^1.2.2 specifier: ^1.2.2
version: 1.2.2 version: 1.2.2
framer-motion: framer-motion:
specifier: ^10.12.22 specifier: ^10.13.0
version: 10.12.22(react-dom@18.2.0)(react@18.2.0) version: 10.13.0(react-dom@18.2.0)(react@18.2.0)
get-urls: get-urls:
specifier: ^11.0.0 specifier: ^11.0.0
version: 11.0.0 version: 11.0.0
@@ -2210,7 +2210,7 @@ packages:
postcss: ^8.1.0 postcss: ^8.1.0
dependencies: dependencies:
browserslist: 4.21.9 browserslist: 4.21.9
caniuse-lite: 1.0.30001516 caniuse-lite: 1.0.30001517
fraction.js: 4.2.0 fraction.js: 4.2.0
normalize-range: 0.1.2 normalize-range: 0.1.2
picocolors: 1.0.0 picocolors: 1.0.0
@@ -2272,8 +2272,8 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true hasBin: true
dependencies: dependencies:
caniuse-lite: 1.0.30001516 caniuse-lite: 1.0.30001517
electron-to-chromium: 1.4.464 electron-to-chromium: 1.4.465
node-releases: 2.0.13 node-releases: 2.0.13
update-browserslist-db: 1.0.11(browserslist@4.21.9) update-browserslist-db: 1.0.11(browserslist@4.21.9)
dev: true dev: true
@@ -2333,8 +2333,8 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: false dev: false
/caniuse-lite@1.0.30001516: /caniuse-lite@1.0.30001517:
resolution: {integrity: sha512-Wmec9pCBY8CWbmI4HsjBeQLqDTqV91nFVR83DnZpYyRnPI1wePDsTg0bGLPC5VU/3OIZV1fmxEea1b+tFKe86g==} resolution: {integrity: sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==}
dev: true dev: true
/ccount@2.0.1: /ccount@2.0.1:
@@ -2735,8 +2735,8 @@ packages:
/eastasianwidth@0.2.0: /eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
/electron-to-chromium@1.4.464: /electron-to-chromium@1.4.465:
resolution: {integrity: sha512-guZ84yoou4+ILNdj0XEbmGs6DEWj6zpVOWYpY09GU66yEb0DSYvP/biBPzHn0GuW/3RC/pnaYNUWlQE1fJYtgA==} resolution: {integrity: sha512-XQcuHvEJRMU97UJ75e170mgcITZoz0lIyiaVjk6R+NMTJ8KBIvUHYd1779swgOppUlzxR+JsLpq59PumaXS1jQ==}
dev: true dev: true
/emoji-regex@8.0.0: /emoji-regex@8.0.0:
@@ -3327,8 +3327,8 @@ packages:
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
dev: true dev: true
/framer-motion@10.12.22(react-dom@18.2.0)(react@18.2.0): /framer-motion@10.13.0(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-bBGYPOxvxcfzS7/py9MEqDucmXBkVl2g42HNlXXPieSTSGGkr8L7+MilCnrU6uX3HrNk/tcB++1SkWE8BosHFw==} resolution: {integrity: sha512-xKhw9VCizmwEHbopOfluaoVunGHSZyMztGbTvsgOYqCjaKu6qtlwWY1J+6GhL41NY1P157JgEikjDm67XCFnvQ==}
peerDependencies: peerDependencies:
react: ^18.0.0 react: ^18.0.0
react-dom: ^18.0.0 react-dom: ^18.0.0
@@ -6025,8 +6025,8 @@ packages:
inline-style-parser: 0.1.1 inline-style-parser: 0.1.1
dev: false dev: false
/sucrase@3.33.0: /sucrase@3.34.0:
resolution: {integrity: sha512-ARGC7vbufOHfpvyGcZZXFaXCMZ9A4fffOGC5ucOW7+WHDGlAe8LJdf3Jts1sWhDeiI1RSWrKy5Hodl+JWGdW2A==} resolution: {integrity: sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==}
engines: {node: '>=8'} engines: {node: '>=8'}
hasBin: true hasBin: true
dependencies: dependencies:
@@ -6100,7 +6100,7 @@ packages:
postcss-nested: 6.0.1(postcss@8.4.26) postcss-nested: 6.0.1(postcss@8.4.26)
postcss-selector-parser: 6.0.13 postcss-selector-parser: 6.0.13
resolve: 1.22.2 resolve: 1.22.2
sucrase: 3.33.0 sucrase: 3.34.0
transitivePeerDependencies: transitivePeerDependencies:
- ts-node - ts-node
dev: true dev: true

View File

@@ -121,8 +121,8 @@ fn main() {
tauri_plugin_stronghold::Builder::new(|password| { tauri_plugin_stronghold::Builder::new(|password| {
let config = argon2::Config { let config = argon2::Config {
lanes: 2, lanes: 2,
mem_cost: 50_000, mem_cost: 10_000,
time_cost: 30, time_cost: 10,
thread_mode: argon2::ThreadMode::from_threads(2), thread_mode: argon2::ThreadMode::from_threads(2),
variant: argon2::Variant::Argon2id, variant: argon2::Variant::Argon2id,
..Default::default() ..Default::default()

View File

@@ -32,12 +32,9 @@ export function CreateStep2Screen() {
const [passwordInput, setPasswordInput] = useState('password'); const [passwordInput, setPasswordInput] = useState('password');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [privkey, setPassword] = useStronghold((state) => [
state.privkey,
state.setPassword,
]);
const pubkey = useOnboarding((state) => state.privkey); const privkey = useStronghold((state) => state.privkey);
const pubkey = useOnboarding((state) => state.pubkey);
const { save } = useSecureStorage(); const { save } = useSecureStorage();
@@ -60,9 +57,6 @@ export function CreateStep2Screen() {
const onSubmit = async (data: { [x: string]: string }) => { const onSubmit = async (data: { [x: string]: string }) => {
setLoading(true); setLoading(true);
if (data.password.length > 3) { if (data.password.length > 3) {
// add password to local state
setPassword(data.password);
// save privkey to secure storage // save privkey to secure storage
await save(pubkey, privkey, data.password); await save(pubkey, privkey, data.password);

View File

@@ -137,7 +137,7 @@ export function CreateStep5Screen() {
}; };
const update = useMutation({ const update = useMutation({
mutationFn: (follows: any) => { mutationFn: (follows: string[]) => {
return updateAccount('follows', follows, account.pubkey); return updateAccount('follows', follows, account.pubkey);
}, },
onSuccess: () => { onSuccess: () => {

View File

@@ -32,11 +32,8 @@ export function ImportStep2Screen() {
const [passwordInput, setPasswordInput] = useState('password'); const [passwordInput, setPasswordInput] = useState('password');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [privkey, setPassword] = useStronghold((state) => [
state.privkey,
state.setPassword,
]);
const privkey = useStronghold((state) => state.privkey);
const pubkey = useOnboarding((state) => state.pubkey); const pubkey = useOnboarding((state) => state.pubkey);
const { save } = useSecureStorage(); const { save } = useSecureStorage();
@@ -60,9 +57,6 @@ export function ImportStep2Screen() {
const onSubmit = async (data: { [x: string]: string }) => { const onSubmit = async (data: { [x: string]: string }) => {
setLoading(true); setLoading(true);
if (data.password.length > 3) { if (data.password.length > 3) {
// add password to local state
setPassword(data.password);
// save privkey to secure storage // save privkey to secure storage
await save(pubkey, privkey, data.password); await save(pubkey, privkey, data.password);
@@ -115,9 +109,9 @@ export function ImportStep2Screen() {
</div> </div>
<div className="text-sm text-zinc-500"> <div className="text-sm text-zinc-500">
<p> <p>
Password is use to secure your key store in local machine, when you move Password is use to unlock app and secure your key store in local machine.
to other clients, you just need to copy your private key as nsec or When you move to other clients, you just need to copy your private key as
hexstring nsec or hexstring
</p> </p>
</div> </div>
<span className="text-sm text-red-400"> <span className="text-sm text-red-400">

View File

@@ -33,13 +33,10 @@ const resolver: Resolver<FormValues> = async (values) => {
export function MigrateScreen() { export function MigrateScreen() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const navigate = useNavigate(); const navigate = useNavigate();
const setPrivkey = useStronghold((state) => state.setPrivkey);
const [passwordInput, setPasswordInput] = useState('password'); const [passwordInput, setPasswordInput] = useState('password');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [setPassword, setPrivkey] = useStronghold((state) => [
state.setPassword,
state.setPrivkey,
]);
const { account } = useAccount(); const { account } = useAccount();
const { save } = useSecureStorage(); const { save } = useSecureStorage();
@@ -63,9 +60,6 @@ export function MigrateScreen() {
const onSubmit = async (data: { [x: string]: string }) => { const onSubmit = async (data: { [x: string]: string }) => {
setLoading(true); setLoading(true);
if (data.password.length > 3) { if (data.password.length > 3) {
// add password to local state
setPassword(data.password);
// load private in secure storage // load private in secure storage
try { try {
// save privkey to secure storage // save privkey to secure storage

View File

@@ -21,8 +21,7 @@ export function OnboardingScreen() {
// publish event // publish event
publish({ publish({
content: content: 'Running Lume, join with me: https://lume.nu',
'Running Lume, fighting for better future, join us here: https://lume.nu',
kind: 1, kind: 1,
tags: [], tags: [],
}); });
@@ -55,9 +54,7 @@ export function OnboardingScreen() {
<User pubkey={account.pubkey} time={Math.floor(Date.now() / 1000)} /> <User pubkey={account.pubkey} time={Math.floor(Date.now() / 1000)} />
)} )}
<div className="-mt-6 select-text whitespace-pre-line break-words pl-[49px] text-base text-zinc-100"> <div className="-mt-6 select-text whitespace-pre-line break-words pl-[49px] text-base text-zinc-100">
<p>Running Lume, fighting for better future</p> <p>Running Lume, join with me</p>
<p>
join us here:{' '}
<a <a
href="https://lume.nu" href="https://lume.nu"
className="font-normal text-fuchsia-500 no-underline hover:text-fuchsia-600" className="font-normal text-fuchsia-500 no-underline hover:text-fuchsia-600"
@@ -66,7 +63,6 @@ export function OnboardingScreen() {
> >
https://lume.nu https://lume.nu
</a> </a>
</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -29,13 +29,10 @@ const resolver: Resolver<FormValues> = async (values) => {
export function UnlockScreen() { export function UnlockScreen() {
const navigate = useNavigate(); const navigate = useNavigate();
const setPrivkey = useStronghold((state) => state.setPrivkey);
const [passwordInput, setPasswordInput] = useState('password'); const [passwordInput, setPasswordInput] = useState('password');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [setPrivkey, setPassword] = useStronghold((state) => [
state.setPrivkey,
state.setPassword,
]);
const { account } = useAccount(); const { account } = useAccount();
const { load } = useSecureStorage(); const { load } = useSecureStorage();
@@ -59,9 +56,6 @@ export function UnlockScreen() {
const onSubmit = async (data: { [x: string]: string }) => { const onSubmit = async (data: { [x: string]: string }) => {
setLoading(true); setLoading(true);
if (data.password.length > 3) { if (data.password.length > 3) {
// add password to local state
setPassword(data.password);
// load private in secure storage // load private in secure storage
try { try {
const privkey = await load(account.pubkey, data.password); const privkey = await load(account.pubkey, data.password);

View File

@@ -13,7 +13,7 @@ import {
updateLastLogin, updateLastLogin,
} from '@libs/storage'; } from '@libs/storage';
import { LoaderIcon, LumeIcon } from '@shared/icons'; import { LoaderIcon } from '@shared/icons';
import { nHoursAgo } from '@utils/date'; import { nHoursAgo } from '@utils/date';
import { useAccount } from '@utils/hooks/useAccount'; import { useAccount } from '@utils/hooks/useAccount';
@@ -175,27 +175,24 @@ export function Root() {
}, [status]); }, [status]);
return ( return (
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-black dark:text-zinc-100"> <div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-zinc-950 dark:text-zinc-100">
<div className="relative h-full overflow-hidden"> <div className="flex h-screen w-full flex-col">
<div <div
data-tauri-drag-region data-tauri-drag-region
className="absolute left-0 top-0 z-20 h-16 w-full bg-transparent" className="relative h-11 shrink-0 border border-zinc-100 bg-white dark:border-zinc-900 dark:bg-black"
/> />
<div className="relative flex h-full flex-col items-center justify-center"> <div className="relative flex min-h-0 w-full flex-1 items-center justify-center">
<div className="flex flex-col items-center gap-2"> <div className="flex flex-col items-center justify-center gap-4">
<LumeIcon className="h-16 w-16 text-black dark:text-zinc-100" /> <LoaderIcon className="h-6 w-6 animate-spin text-zinc-100" />
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold leading-tight text-zinc-900 dark:text-zinc-100"> <h3 className="text-lg font-semibold leading-tight text-zinc-100">
Here&apos;s an interesting fact: Prefetching data...
</h3> </h3>
<p className="font-medium text-zinc-300 dark:text-zinc-600"> <p className="text-zinc-600">
Bitcoin and Nostr can be used by anyone, and no one can stop you! This may take a few seconds, please don&apos;t close app.
</p> </p>
</div> </div>
</div> </div>
<div className="absolute bottom-16 left-1/2 -translate-x-1/2 transform">
<LoaderIcon className="h-5 w-5 animate-spin text-black dark:text-zinc-100" />
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -19,7 +19,7 @@ export function useNewsfeed() {
useEffect(() => { useEffect(() => {
if (status === 'success' && account) { if (status === 'success' && account) {
const follows = account ? JSON.parse(account.follows) : []; const follows = account ? JSON.parse(account.follows as string) : [];
const filter: NDKFilter = { const filter: NDKFilter = {
kinds: [1, 6], kinds: [1, 6],
@@ -30,7 +30,6 @@ export function useNewsfeed() {
sub.current = ndk.subscribe(filter, { closeOnEose: false }); sub.current = ndk.subscribe(filter, { closeOnEose: false });
sub.current.addListener('event', (event: NDKEvent) => { sub.current.addListener('event', (event: NDKEvent) => {
console.log('new note: ', event);
// add to db // add to db
createNote( createNote(
event.id, event.id,
@@ -46,7 +45,9 @@ export function useNewsfeed() {
} }
return () => { return () => {
if (sub.current) {
sub.current.stop(); sub.current.stop();
}
}; };
}, [status]); }, [status]);
} }

View File

@@ -6,7 +6,7 @@ import { useStronghold } from '@stores/stronghold';
import { useAccount } from '@utils/hooks/useAccount'; import { useAccount } from '@utils/hooks/useAccount';
export function Protected({ children }: { children: ReactNode }) { export function Protected({ children }: { children: ReactNode }) {
const password = useStronghold((state) => state.password); const privkey = useStronghold((state) => state.privkey);
const { status, account } = useAccount(); const { status, account } = useAccount();
if (status === 'success' && !account) { if (status === 'success' && !account) {
@@ -17,7 +17,7 @@ export function Protected({ children }: { children: ReactNode }) {
return <Navigate to="/auth/migrate" replace />; return <Navigate to="/auth/migrate" replace />;
} }
if (status === 'success' && account && !password) { if (status === 'success' && account && !privkey) {
return <Navigate to="/auth/unlock" replace />; return <Navigate to="/auth/unlock" replace />;
} }

View File

@@ -3,11 +3,8 @@ import { create } from 'zustand';
interface OnboardingState { interface OnboardingState {
profile: { [x: string]: string }; profile: { [x: string]: string };
pubkey: string; pubkey: string;
privkey: string;
createProfile: (data: { [x: string]: string }) => void; createProfile: (data: { [x: string]: string }) => void;
setPubkey: (pubkey: string) => void; setPubkey: (pubkey: string) => void;
setPrivkey: (privkey: string) => void;
clearPrivkey: (privkey: string) => void;
} }
export const useOnboarding = create<OnboardingState>((set) => ({ export const useOnboarding = create<OnboardingState>((set) => ({
@@ -20,10 +17,4 @@ export const useOnboarding = create<OnboardingState>((set) => ({
setPubkey: (pubkey: string) => { setPubkey: (pubkey: string) => {
set({ pubkey: pubkey }); set({ pubkey: pubkey });
}, },
setPrivkey: (privkey: string) => {
set({ privkey: privkey });
},
clearPrivkey: () => {
set({ privkey: '' });
},
})); }));

View File

@@ -1,19 +1,22 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { createJSONStorage, persist } from 'zustand/middleware';
interface StrongholdState { interface StrongholdState {
password: null | string;
privkey: null | string; privkey: null | string;
setPassword: (password: string) => void;
setPrivkey: (privkey: string) => void; setPrivkey: (privkey: string) => void;
} }
export const useStronghold = create<StrongholdState>((set) => ({ export const useStronghold = create<StrongholdState>()(
password: null, persist(
(set) => ({
privkey: null, privkey: null,
setPassword: (password: string) => {
set({ password: password });
},
setPrivkey: (privkey: string) => { setPrivkey: (privkey: string) => {
set({ privkey: privkey }); set({ privkey: privkey });
}, },
})); }),
{
name: 'stronghold',
storage: createJSONStorage(() => sessionStorage),
}
)
);

View File

@@ -5,14 +5,12 @@ import { useNDK } from '@libs/ndk/provider';
import { useStronghold } from '@stores/stronghold'; import { useStronghold } from '@stores/stronghold';
import { useAccount } from '@utils/hooks/useAccount'; import { useAccount } from '@utils/hooks/useAccount';
import { useSecureStorage } from '@utils/hooks/useSecureStorage';
export function usePublish() { export function usePublish() {
const { ndk } = useNDK(); const { ndk } = useNDK();
const { account } = useAccount(); const { account } = useAccount();
const { load } = useSecureStorage();
const cachePrivkey = useStronghold((state) => state.privkey); const privkey = useStronghold((state) => state.privkey);
const publish = async ({ const publish = async ({
content, content,
@@ -23,12 +21,7 @@ export function usePublish() {
kind: NDKKind | number; kind: NDKKind | number;
tags: string[][]; tags: string[][];
}): Promise<NDKEvent> => { }): Promise<NDKEvent> => {
let privkey: string; if (!privkey) throw new Error('Private key not found');
if (cachePrivkey) {
privkey = cachePrivkey;
} else {
privkey = await load(account.pubkey);
}
const event = new NDKEvent(ndk); const event = new NDKEvent(ndk);
const signer = new NDKPrivateKeySigner(privkey); const signer = new NDKPrivateKeySigner(privkey);

View File

@@ -1,13 +1,9 @@
import { appConfigDir } from '@tauri-apps/api/path'; import { appConfigDir } from '@tauri-apps/api/path';
import { Stronghold } from 'tauri-plugin-stronghold-api'; import { Stronghold } from 'tauri-plugin-stronghold-api';
import { useStronghold } from '@stores/stronghold';
const dir = await appConfigDir(); const dir = await appConfigDir();
export function useSecureStorage() { export function useSecureStorage() {
const password = useStronghold((state) => state.password);
async function getClient(stronghold: Stronghold) { async function getClient(stronghold: Stronghold) {
try { try {
return await stronghold.loadClient('lume'); return await stronghold.loadClient('lume');
@@ -16,22 +12,16 @@ export function useSecureStorage() {
} }
} }
const save = async (key: string, value: string, userpass?: string) => { const save = async (key: string, value: string, password: string) => {
const stronghold = await Stronghold.load( const stronghold = await Stronghold.load(`${dir}lume.stronghold`, password);
`${dir}lume.stronghold`,
userpass ? userpass : password
);
const client = await getClient(stronghold); const client = await getClient(stronghold);
const store = client.getStore(); const store = client.getStore();
await store.insert(key, Array.from(new TextEncoder().encode(value))); await store.insert(key, Array.from(new TextEncoder().encode(value)));
return await stronghold.save(); return await stronghold.save();
}; };
const load = async (key: string, userpass?: string) => { const load = async (key: string, password: string) => {
const stronghold = await Stronghold.load( const stronghold = await Stronghold.load(`${dir}lume.stronghold`, password);
`${dir}lume.stronghold`,
userpass ? userpass : password
);
const client = await getClient(stronghold); const client = await getClient(stronghold);
const store = client.getStore(); const store = client.getStore();
const value = await store.get(key); const value = await store.get(key);