diff --git a/src/app/(app)/(layout)/settings/page.tsx b/src/app/(app)/(layout)/settings/page.tsx new file mode 100644 index 00000000..c96bfe1f --- /dev/null +++ b/src/app/(app)/(layout)/settings/page.tsx @@ -0,0 +1,5 @@ +import { Settings } from '@/views/settings/Settings'; + +export default function Page() { + return ; +} diff --git a/src/components/layout/AppLayout.tsx b/src/components/layout/AppLayout.tsx index a2c4dd48..d1892ded 100644 --- a/src/components/layout/AppLayout.tsx +++ b/src/components/layout/AppLayout.tsx @@ -5,6 +5,7 @@ import { Menu, MessageCircle, Settings, + Settings2, Vault, } from 'lucide-react'; import Link from 'next/link'; @@ -79,6 +80,24 @@ export const AppLayout: FC<{ children: ReactNode }> = ({ children }) => {
+ + + + + + Change your password + + This will change the Master Password used to protect your funds + and messages. + + + +
+ + ( + + Current Master Password + + + + + + )} + /> + + ( + +
+ New Master Password + +
+ +
+ + + {clickedGenerate ? ( + + ) : null} +
+
+ + + + Important: + Your account cannot be recovered if you forget it! + +
+ )} + /> + + ( + + Confirm New Master Password + + + + + + )} + /> + + ( + + Master Password Hint + + + + + + Important: + The password hint will be stored in clear text. + + + )} + /> + + ( + + + + +
+ + I understand that if I forget the password + my account cannot be recovered. + + +
+
+ )} + /> + + + + + + + + + +
+ + + ); +}; diff --git a/src/views/settings/Section.tsx b/src/views/settings/Section.tsx new file mode 100644 index 00000000..fa52db44 --- /dev/null +++ b/src/views/settings/Section.tsx @@ -0,0 +1,20 @@ +import { FC, ReactNode } from 'react'; + +export const Section: FC<{ + title: string | ReactNode; + description: string | ReactNode; + children: ReactNode; +}> = ({ title, description, children }) => { + return ( +
+
+

{title}

+

+ {description} +

+
+ +
{children}
+
+ ); +}; diff --git a/src/views/settings/Settings.tsx b/src/views/settings/Settings.tsx new file mode 100644 index 00000000..88b0a5e1 --- /dev/null +++ b/src/views/settings/Settings.tsx @@ -0,0 +1,17 @@ +import { useTranslations } from 'next-intl'; + +import { ChangePassword } from './ChangePassword'; + +export const Settings = () => { + const t = useTranslations('Index'); + + return ( +
+

{t('settings')}

+ +
+ +
+
+ ); +}; diff --git a/src/views/wallet/Settings.tsx b/src/views/wallet/Settings.tsx index 48f9d151..5f2f84cb 100644 --- a/src/views/wallet/Settings.tsx +++ b/src/views/wallet/Settings.tsx @@ -1,7 +1,7 @@ 'use client'; import { Copy, CopyCheck, Loader2 } from 'lucide-react'; -import { FC, ReactNode, useEffect, useRef, useState } from 'react'; +import { FC, useEffect, useRef, useState } from 'react'; import { useCopyToClipboard } from 'usehooks-ts'; import { VaultButton } from '@/components/button/VaultButton'; @@ -18,24 +18,7 @@ import { CryptoWorkerResponse, } from '@/workers/crypto/types'; -const Section: FC<{ - title: string | ReactNode; - description: string | ReactNode; - children: ReactNode; -}> = ({ title, description, children }) => { - return ( -
-
-

{title}

-

- {description} -

-
- -
{children}
-
- ); -}; +import { Section } from '../settings/Section'; const WalletName: FC<{ walletId: string }> = ({ walletId }) => { const { toast } = useToast(); diff --git a/src/workers/account/account.ts b/src/workers/account/account.ts index ba801a54..0613a482 100644 --- a/src/workers/account/account.ts +++ b/src/workers/account/account.ts @@ -1,5 +1,7 @@ import { + changeProtectedSymmetricKey, createProtectedSymmetricKey, + decryptSymmetricKey, generateMasterKeyAndHash, secp256k1GenerateProtectedKeyPair, } from '@/utils/crypto'; @@ -78,7 +80,56 @@ self.onmessage = async e => { const response: WorkerResponse = { type: 'generateMaster', - payload: { ...result, protectedSymmetricKey }, + payload: { + ...result, + protectedSymmetricKey, + }, + }; + + self.postMessage(response); + + break; + } + + case 'changePassword': { + const { + payload: { + email, + currentPassword, + newPassword, + newPasswordHint, + currentProtectedSymmetricKey, + }, + } = message; + + const current = await generateMasterKeyAndHash({ + email, + password: currentPassword, + }); + + const symmetricKey = decryptSymmetricKey({ + protectedSymmetricKey: currentProtectedSymmetricKey, + masterKey: current.masterKey, + }); + + const result = await generateMasterKeyAndHash({ + email, + password: newPassword, + }); + + const newProtectedSymmetricKey = changeProtectedSymmetricKey({ + symmetricKey, + newMasterKey: result.masterKey, + }); + + const response: WorkerResponse = { + type: 'changePassword', + payload: { + currentMasterKeyHash: current.masterPasswordHash, + newMasterKeyHash: result.masterPasswordHash, + newProtectedSymmetricKey, + newPasswordHint, + }, }; self.postMessage(response); diff --git a/src/workers/account/types.ts b/src/workers/account/types.ts index 9e48ce03..14765a02 100644 --- a/src/workers/account/types.ts +++ b/src/workers/account/types.ts @@ -19,6 +19,16 @@ export type WorkerMessage = password: string; protectedSymmetricKey: string; }; + } + | { + type: 'changePassword'; + payload: { + email: string; + currentPassword: string; + newPassword: string; + newPasswordHint?: string; + currentProtectedSymmetricKey: string; + }; }; export type CreateAccountResult = { @@ -47,5 +57,14 @@ export type WorkerResponse = protectedSymmetricKey: string; }; } + | { + type: 'changePassword'; + payload: { + currentMasterKeyHash: string; + newMasterKeyHash: string; + newProtectedSymmetricKey: string; + newPasswordHint?: string; + }; + } | { type: 'loaded' } | { type: 'error'; msg: string };