Skip to content

Commit

Permalink
fix: correct encryption key (#25)
Browse files Browse the repository at this point in the history
* fix: correct encryption key

* refactor: params
  • Loading branch information
apotdevin authored Jul 20, 2024
1 parent cedd563 commit 190d0e6
Show file tree
Hide file tree
Showing 21 changed files with 594 additions and 330 deletions.
100 changes: 100 additions & 0 deletions src/app/(app)/(layout)/temp/recover/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
'use client';

import { hexToBytes } from '@noble/hashes/utils';
import { nip44 } from 'nostr-tools';
import { useCallback, useEffect, useState } from 'react';

import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { useToast } from '@/components/ui/use-toast';
import { useUserQuery } from '@/graphql/queries/__generated__/user.generated';
import { generateMasterKeyAndHash } from '@/utils/crypto';

export default function Page() {
const { data } = useUserQuery();

const { toast } = useToast();

const [email, setEmail] = useState<string>('');
const [password, setPassword] = useState<string>('');
const [protectedMnemonic, setProtectedMnemonic] = useState<string>('');
const [mnemonic, setMnemonic] = useState<string>('');
const [loading, setLoading] = useState<boolean>(false);

useEffect(() => {
if (!!email) return;
if (!data?.user.email) return;
setEmail(data.user.email);
}, [data, email]);

const decrypt = useCallback(async () => {
if (loading) return;

setLoading(true);

try {
const { masterKey } = await generateMasterKeyAndHash({ email, password });

const decrypted = nip44.v2.decrypt(
protectedMnemonic,
hexToBytes(masterKey)
);

setMnemonic(decrypted);
} catch (error) {
toast({
variant: 'destructive',
title: 'Error decrypting.',
description:
'Check your password and email and make sure you are copying the complete encrypted mnemonic.',
});
setMnemonic('');
}

setLoading(false);
}, [email, loading, password, protectedMnemonic, toast]);

return (
<div className="my-4 flex flex-col gap-4">
<div>
<Label>Email</Label>
<Input
value={email}
onChange={value => setEmail(value.target.value)}
placeholder="Type your email"
/>
</div>
<div>
<Label>Password</Label>
<Input
type="password"
autoComplete="false"
value={password}
onChange={value => setPassword(value.target.value)}
placeholder="Type your password"
/>
</div>
<div>
<Label>Encrypted Mnemonic</Label>
<Input
value={protectedMnemonic}
onChange={value => setProtectedMnemonic(value.target.value)}
placeholder="Type your encrypted mnemonic"
/>
</div>
<Button
disabled={!email || !password || !protectedMnemonic || loading}
onClick={() => decrypt()}
>
{!loading ? 'Decrypt' : 'Decrypting...'}
</Button>
{!!mnemonic ? (
<div>
<Label>Mnemonic</Label>
<Input readOnly contentEditable={'false'} value={mnemonic} />
</div>
) : null}
</div>
);
}
91 changes: 56 additions & 35 deletions src/components/button/VaultButton.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
'use client';

import { ApolloError, useApolloClient } from '@apollo/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { Loader2, Lock, Unlock } from 'lucide-react';
import { FC, useEffect, useRef, useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';

import { useCheckPasswordMutation } from '@/graphql/mutations/__generated__/checkPassword.generated';
import {
CheckPasswordDocument,
CheckPasswordMutation,
CheckPasswordMutationVariables,
} from '@/graphql/mutations/__generated__/checkPassword.generated';
import { useUserQuery } from '@/graphql/queries/__generated__/user.generated';
import { useKeyStore } from '@/stores/keys';
import { toWithError } from '@/utils/async';
import { cn } from '@/utils/cn';
import { handleApolloError } from '@/utils/error';
import { WorkerMessage, WorkerResponse } from '@/workers/account/types';
Expand Down Expand Up @@ -42,6 +48,8 @@ const formSchema = z.object({
const UnlockDialogContent: FC<{ callback: () => void }> = ({ callback }) => {
const workerRef = useRef<Worker>();

const client = useApolloClient();

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
Expand All @@ -51,35 +59,24 @@ const UnlockDialogContent: FC<{ callback: () => void }> = ({ callback }) => {

const { toast } = useToast();

const setMasterKey = useKeyStore(s => s.setMasterKey);
const setKeys = useKeyStore(s => s.setKeys);

const [loading, setLoading] = useState(true);
const [tempMasterKey, setTempMasterKey] = useState('');

const { data } = useUserQuery({ errorPolicy: 'ignore' });

const [checkPassword] = useCheckPasswordMutation({
onCompleted: () => {
setMasterKey(tempMasterKey);
callback();
},
onError: error => {
const messages = handleApolloError(error);

toast({
variant: 'destructive',
title: 'Unable to unlock.',
description: messages.join(', '),
});

setTempMasterKey('');
form.reset();
},
});
const { data, loading: userLoading, error } = useUserQuery();

const handleSubmit = (values: z.infer<typeof formSchema>) => {
setLoading(true);
if (loading || !data?.user.email || !workerRef.current) {

if (
loading ||
userLoading ||
error ||
!data?.user.email ||
!data?.user.protected_symmetric_key ||
!workerRef.current
) {
setLoading(false);
return;
}

Expand All @@ -88,6 +85,7 @@ const UnlockDialogContent: FC<{ callback: () => void }> = ({ callback }) => {
payload: {
email: data.user.email,
password: values.password,
protectedSymmetricKey: data.user.protected_symmetric_key,
},
};

Expand All @@ -99,18 +97,41 @@ const UnlockDialogContent: FC<{ callback: () => void }> = ({ callback }) => {
new URL('../../workers/account/account.ts', import.meta.url)
);

workerRef.current.onmessage = event => {
workerRef.current.onmessage = async event => {
const message: WorkerResponse = event.data;

switch (message.type) {
case 'generateMaster':
setTempMasterKey(message.payload.masterKey);

checkPassword({
variables: { password: message.payload.masterPasswordHash },
});
case 'generateMaster': {
const [, error] = await toWithError(
client.mutate<
CheckPasswordMutation,
CheckPasswordMutationVariables
>({
mutation: CheckPasswordDocument,
variables: { password: message.payload.masterPasswordHash },
})
);

if (error) {
const messages = handleApolloError(error as ApolloError);

toast({
variant: 'destructive',
title: 'Unable to unlock.',
description: messages.join(', '),
});

form.reset();
} else {
setKeys({
masterKey: message.payload.masterKey,
protectedSymmetricKey: message.payload.protectedSymmetricKey,
});
callback();
}

break;
}

case 'loaded':
setLoading(false);
Expand All @@ -128,7 +149,7 @@ const UnlockDialogContent: FC<{ callback: () => void }> = ({ callback }) => {
return () => {
if (workerRef.current) workerRef.current.terminate();
};
}, [checkPassword]);
}, [client, toast, callback, form, setKeys]);

return (
<>
Expand Down Expand Up @@ -189,7 +210,7 @@ export const VaultButton: FC<{
className?: string;
size?: 'sm';
}> = ({ lockedTitle = 'Locked', className, size }) => {
const masterKey = useKeyStore(s => s.masterKey);
const keys = useKeyStore(s => s.keys);

const clearKeys = useKeyStore(s => s.clear);

Expand All @@ -203,7 +224,7 @@ export const VaultButton: FC<{
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
{!!masterKey ? (
{!!keys ? (
<Button
type="button"
variant="outline"
Expand All @@ -226,7 +247,7 @@ export const VaultButton: FC<{
)}
</DialogTrigger>
<DialogContent className="sm:max-w-md">
{!!masterKey ? (
{!!keys ? (
<>
<DialogHeader>
<DialogTitle>Lock your vault</DialogTitle>
Expand Down
8 changes: 5 additions & 3 deletions src/components/form/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { useLoginMutation } from '@/graphql/mutations/__generated__/login.generated';
import { argon2Hash } from '@/utils/crypto';
import { generateMasterKeyAndHash } from '@/utils/crypto';
import { handleApolloError } from '@/utils/error';
import { ROUTES } from '@/utils/routes';

Expand Down Expand Up @@ -72,8 +72,10 @@ export function LoginForm() {

setLoading(true);
try {
const masterKey = await argon2Hash(data.password, data.email);
const masterPasswordHash = await argon2Hash(masterKey, data.password);
const { masterPasswordHash } = await generateMasterKeyAndHash({
email: data.email,
password: data.password,
});

login({
variables: {
Expand Down
8 changes: 4 additions & 4 deletions src/components/wallet/SendAddressForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const SendAddressForm: FC<{

const [stateLoading, setStateLoading] = useState(false);

const masterKey = useKeyStore(s => s.masterKey);
const keys = useKeyStore(s => s.keys);

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
Expand Down Expand Up @@ -290,7 +290,7 @@ export const SendAddressForm: FC<{
return;
}

if (!result.data?.pay.liquid_address || !protected_mnemonic || !masterKey) {
if (!result.data?.pay.liquid_address || !protected_mnemonic || !keys) {
setStateLoading(false);
return;
}
Expand All @@ -307,7 +307,7 @@ export const SendAddressForm: FC<{
mnemonic: protected_mnemonic,
descriptor: result.data.pay.liquid_address.wallet_account.descriptor,
pset: result.data.pay.liquid_address.base_64,
masterKey,
keys,
},
};

Expand Down Expand Up @@ -454,7 +454,7 @@ export const SendAddressForm: FC<{
) : null}

<div className="flex items-center justify-center pt-2">
{masterKey ? (
{!!keys ? (
<Button type="submit" disabled={loading} className="w-full">
{loading ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Expand Down
8 changes: 4 additions & 4 deletions src/components/wallet/SendInvoiceForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export const SendInvoiceForm: FC<{

const [loading, setLoading] = useState(true);

const masterKey = useKeyStore(s => s.masterKey);
const keys = useKeyStore(s => s.keys);

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
Expand Down Expand Up @@ -231,7 +231,7 @@ export const SendInvoiceForm: FC<{
if (
!result.data?.pay.lightning_invoice ||
!walletInfo.protected_mnemonic ||
!masterKey
!keys
) {
setLoading(false);
return;
Expand All @@ -249,7 +249,7 @@ export const SendInvoiceForm: FC<{
mnemonic: walletInfo.protected_mnemonic,
descriptor: result.data.pay.lightning_invoice.wallet_account.descriptor,
pset: result.data.pay.lightning_invoice.base_64,
masterKey,
keys,
},
};

Expand Down Expand Up @@ -285,7 +285,7 @@ export const SendInvoiceForm: FC<{
<Decoded invoice={watchInvoice[0]} />

<div className="flex items-center justify-center">
{masterKey ? (
{!!keys ? (
<Button type="submit" disabled={loading} className="w-full">
{loading ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Expand Down
2 changes: 2 additions & 0 deletions src/graphql/queries/__generated__/user.generated.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/graphql/queries/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const User = gql`
user {
id
email
protected_symmetric_key
default_wallet_id
}
}
Expand Down
Loading

0 comments on commit 190d0e6

Please sign in to comment.