wip encryption issue to access doc
This commit is contained in:
+23
-17
@@ -1,7 +1,11 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { decryptSymmetricKey } from '@/docs/doc-collaboration/encryption';
|
||||
import assert from 'assert';
|
||||
|
||||
export type DocumentEncryptionError =
|
||||
| 'missing_symmetric_key'
|
||||
| 'decryption_failed'
|
||||
| null;
|
||||
|
||||
export function useDocumentEncryption(
|
||||
encryptionLoading: boolean,
|
||||
@@ -17,11 +21,13 @@ export function useDocumentEncryption(
|
||||
documentEncryptionSettings: {
|
||||
documentSymmetricKey: CryptoKey;
|
||||
} | null;
|
||||
documentEncryptionError: DocumentEncryptionError;
|
||||
} {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [settings, setSettings] = useState<{
|
||||
documentSymmetricKey: CryptoKey;
|
||||
} | null>(null);
|
||||
const [error, setError] = useState<DocumentEncryptionError>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -35,24 +41,27 @@ export function useDocumentEncryption(
|
||||
} else if (encryptionLoading || isDocumentEncrypted === undefined) {
|
||||
setLoading(true);
|
||||
setSettings(null);
|
||||
setError(null);
|
||||
return;
|
||||
} else if (isDocumentEncrypted === false) {
|
||||
setLoading(false);
|
||||
setSettings(null);
|
||||
setError(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// TODO: if encrypted but there is no encrypted symmetric key for this user, we should display an error
|
||||
// TODO: (maybe reuse the catch handler below?)
|
||||
// TODO:
|
||||
assert(
|
||||
userEncryptedSymmetricKey,
|
||||
'document encrypted symmetric key must exist',
|
||||
);
|
||||
if (!userEncryptedSymmetricKey) {
|
||||
if (!cancelled) {
|
||||
setError('missing_symmetric_key');
|
||||
setSettings(null);
|
||||
setLoading(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const userEncryptedSymmetricKeyArrayBuffer = Buffer.from(
|
||||
userEncryptedSymmetricKey,
|
||||
@@ -70,18 +79,14 @@ export function useDocumentEncryption(
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
//
|
||||
// TODO: this should display a global error since if encryption needed it should able
|
||||
// to retrieve information (except if onboarding needed, but still...)
|
||||
//
|
||||
// maybe this should be a return value so the parent knows where to set the CTA
|
||||
//
|
||||
|
||||
if (!cancelled) {
|
||||
setError('decryption_failed');
|
||||
setSettings(null);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
if (!cancelled) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,5 +100,6 @@ export function useDocumentEncryption(
|
||||
return {
|
||||
documentEncryptionLoading: loading,
|
||||
documentEncryptionSettings: settings,
|
||||
documentEncryptionError: error,
|
||||
};
|
||||
}
|
||||
|
||||
+28
-11
@@ -2,6 +2,11 @@ import { useEffect, useState } from 'react';
|
||||
|
||||
import { getEncryptionDB } from '../encryptionDB';
|
||||
|
||||
export type EncryptionError =
|
||||
| 'missing_private_key'
|
||||
| 'missing_public_key'
|
||||
| null;
|
||||
|
||||
export function useEncryption(userId?: string): {
|
||||
encryptionLoading: boolean;
|
||||
encryptionSettings: {
|
||||
@@ -9,6 +14,7 @@ export function useEncryption(userId?: string): {
|
||||
userPrivateKey: CryptoKey;
|
||||
userPublicKey: CryptoKey;
|
||||
} | null;
|
||||
encryptionError: EncryptionError;
|
||||
} {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [settings, setSettings] = useState<{
|
||||
@@ -16,6 +22,7 @@ export function useEncryption(userId?: string): {
|
||||
userPrivateKey: CryptoKey;
|
||||
userPublicKey: CryptoKey;
|
||||
} | null>(null);
|
||||
const [error, setError] = useState<EncryptionError>(null);
|
||||
|
||||
const enableEncryption: boolean = true; // TODO: this could be toggled for instances not needing encryption to save some requests
|
||||
|
||||
@@ -27,15 +34,18 @@ export function useEncryption(userId?: string): {
|
||||
if (!userId) {
|
||||
setLoading(true);
|
||||
setSettings(null);
|
||||
setError(null);
|
||||
return;
|
||||
} else if (enableEncryption === false) {
|
||||
setLoading(false);
|
||||
setSettings(null);
|
||||
setError(null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// We must first retrieve user keys locally
|
||||
const encryptionDatabase = await getEncryptionDB();
|
||||
@@ -46,7 +56,11 @@ export function useEncryption(userId?: string): {
|
||||
);
|
||||
|
||||
if (!userPrivateKey) {
|
||||
throw new Error('user has no local private key (needs onboarding)');
|
||||
if (!cancelled) {
|
||||
setError('missing_private_key');
|
||||
setSettings(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const userPublicKey = await encryptionDatabase.get(
|
||||
@@ -55,7 +69,11 @@ export function useEncryption(userId?: string): {
|
||||
);
|
||||
|
||||
if (!userPublicKey) {
|
||||
throw new Error('user is missing his public key');
|
||||
if (!cancelled) {
|
||||
setError('missing_public_key');
|
||||
setSettings(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cancelled) {
|
||||
@@ -68,18 +86,13 @@ export function useEncryption(userId?: string): {
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
//
|
||||
// TODO: this should display a global error since if encryption needed it should able
|
||||
// to retrieve information (except if onboarding needed, but still...)
|
||||
//
|
||||
// maybe this should be a return value so the parent knows where to set the CTA
|
||||
//
|
||||
|
||||
if (!cancelled) {
|
||||
setSettings(null);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
if (!cancelled) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,5 +103,9 @@ export function useEncryption(userId?: string): {
|
||||
};
|
||||
}, [userId, enableEncryption]);
|
||||
|
||||
return { encryptionLoading: loading, encryptionSettings: settings };
|
||||
return {
|
||||
encryptionLoading: loading,
|
||||
encryptionSettings: settings,
|
||||
encryptionError: error,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,7 +8,13 @@ export {
|
||||
encryptSymmetricKey,
|
||||
} from './encryption';
|
||||
export { getEncryptionDB } from './encryptionDB';
|
||||
export { useDocumentEncryption } from './hook/useDocumentEncryption';
|
||||
export { useEncryption } from './hook/useEncryption';
|
||||
export {
|
||||
useDocumentEncryption,
|
||||
type DocumentEncryptionError,
|
||||
} from './hook/useDocumentEncryption';
|
||||
export {
|
||||
useEncryption,
|
||||
type EncryptionError,
|
||||
} from './hook/useEncryption';
|
||||
export { useKeyFingerprint } from './hook/useKeyFingerprint';
|
||||
export { usePublicKeyRegistry } from './hook/usePublicKeyRegistry';
|
||||
|
||||
@@ -5,7 +5,9 @@ import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, Icon, Loading, TextErrors } from '@/components';
|
||||
import { Button } from '@gouvfr-lasuite/cunningham-react';
|
||||
|
||||
import { Box, Icon, Loading, StyledLink, Text, TextErrors } from '@/components';
|
||||
import { DEFAULT_QUERY_RETRY } from '@/core';
|
||||
import { DocEditor } from '@/docs/doc-editor';
|
||||
import {
|
||||
@@ -93,14 +95,18 @@ const DocPage = ({ id }: DocProps) => {
|
||||
|
||||
const { authenticated, user } = useAuth();
|
||||
const [doc, setDoc] = useState<Doc>();
|
||||
const { encryptionLoading, encryptionSettings } = useEncryption(user?.id);
|
||||
const { documentEncryptionLoading, documentEncryptionSettings } =
|
||||
useDocumentEncryption(
|
||||
encryptionLoading,
|
||||
encryptionSettings,
|
||||
doc?.is_encrypted,
|
||||
doc?.encrypted_document_symmetric_key_for_user,
|
||||
);
|
||||
const { encryptionLoading, encryptionSettings, encryptionError } =
|
||||
useEncryption(user?.id);
|
||||
const {
|
||||
documentEncryptionLoading,
|
||||
documentEncryptionSettings,
|
||||
documentEncryptionError,
|
||||
} = useDocumentEncryption(
|
||||
encryptionLoading,
|
||||
encryptionSettings,
|
||||
doc?.is_encrypted,
|
||||
doc?.encrypted_document_symmetric_key_for_user,
|
||||
);
|
||||
const { setCurrentDoc } = useDocStore();
|
||||
const { addTask } = useBroadcastStore();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -245,6 +251,98 @@ const DocPage = ({ id }: DocProps) => {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (doc.is_encrypted && (encryptionError || documentEncryptionError)) {
|
||||
return (
|
||||
<Box $align="center" $margin="auto" $gap="md" $padding="2rem">
|
||||
<Icon iconName="lock" $size="3rem" $theme="warning" />
|
||||
<Text as="h2" $textAlign="center" $margin="0">
|
||||
{t('Encryption keys unavailable')}
|
||||
</Text>
|
||||
<Box $maxWidth="500px" $gap="sm">
|
||||
<Text $variation="secondary" $textAlign="center">
|
||||
{t(
|
||||
'This is an encrypted document, but your current device does not have the required encryption keys to decrypt it.',
|
||||
)}
|
||||
</Text>
|
||||
|
||||
{(encryptionError === 'missing_private_key' ||
|
||||
encryptionError === 'missing_public_key') && (
|
||||
<Text $variation="secondary" $textAlign="center">
|
||||
{t(
|
||||
'This usually happens when you switch to a new device or browser without restoring your encryption backup.',
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{documentEncryptionError === 'missing_symmetric_key' && (
|
||||
<Text $variation="secondary" $textAlign="center">
|
||||
{t(
|
||||
'You do not have access to this encrypted document. Ask the document owner to share it with you again.',
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{documentEncryptionError === 'decryption_failed' && (
|
||||
<Text $variation="secondary" $textAlign="center">
|
||||
{t(
|
||||
'Your encryption keys could not decrypt this document. This may happen if your keys were recreated. Ask the document owner to share it with you again.',
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{(encryptionError === 'missing_private_key' ||
|
||||
encryptionError === 'missing_public_key') && (
|
||||
<Box $gap="xs" $maxWidth="500px">
|
||||
<Box
|
||||
$padding="sm"
|
||||
$background="#f0f7ff"
|
||||
$border="1px solid #c5ddf5"
|
||||
$radius="4px"
|
||||
$gap="xs"
|
||||
>
|
||||
<Text $weight="600" $size="sm">
|
||||
{t('Restore from backup (recommended)')}
|
||||
</Text>
|
||||
<Text $size="sm" $variation="secondary">
|
||||
{t(
|
||||
'If you have previously exported your encryption backup, you can restore it in your account settings to regain access to all your encrypted documents.',
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box
|
||||
$padding="sm"
|
||||
$background="#fff8f0"
|
||||
$border="1px solid #f5dfc5"
|
||||
$radius="4px"
|
||||
$gap="xs"
|
||||
>
|
||||
<Text $weight="600" $size="sm">
|
||||
{t('Recreate encryption keys (not recommended)')}
|
||||
</Text>
|
||||
<Text $size="sm" $variation="secondary">
|
||||
{t(
|
||||
'Creating new encryption keys means you will lose access to all previously encrypted documents. Document owners will need to share them with you again.',
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<StyledLink href="/">
|
||||
<Button
|
||||
color="neutral"
|
||||
icon={
|
||||
<Icon iconName="home" $withThemeInherited />
|
||||
}
|
||||
>
|
||||
{t('Back to home')}
|
||||
</Button>
|
||||
</StyledLink>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
|
||||
Reference in New Issue
Block a user