diff --git a/package.json b/package.json index 1171d66..f2ffac5 100755 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@tanstack/react-query": "^4.12.0", "axios": "^1.1.2", "axios-case-converter": "^0.11.1", + "camelcase-keys": "^8.0.2", "crypto-js": "^4.1.1", "date-fns": "^2.29.3", "formik": "^2.2.9", diff --git a/src/AuthContext/AuthContext.ts b/src/AuthContext/AuthContext.ts index 5e897f1..61f0201 100644 --- a/src/AuthContext/AuthContext.ts +++ b/src/AuthContext/AuthContext.ts @@ -5,10 +5,11 @@ import {ServerUser, User} from "~/server-types" interface AuthContextTypeBase { user: ServerUser | User | null isAuthenticated: boolean - login: (user: ServerUser) => Promise + login: (user: ServerUser | User) => void logout: () => void - _decryptContent: (content: string) => string - _encryptContent: (content: string) => string + _decryptUsingMasterPassword: (content: string) => string + _encryptUsingMasterPassword: (content: string) => string + _decryptUsingPrivateKey: (message: string) => Promise _setDecryptionPassword: (decryptionPassword: string) => void _updateUser: (user: ServerUser | User) => void } @@ -36,12 +37,15 @@ const AuthContext = createContext({ logout: () => { throw new Error("logout() not implemented") }, - _decryptContent: () => { + _decryptUsingMasterPassword: () => { throw new Error("_decryptContent() not implemented") }, - _encryptContent: () => { + _encryptUsingMasterPassword: () => { throw new Error("_encryptContent() not implemented") }, + _decryptUsingPrivateKey: () => { + throw new Error("_decryptUsingPrivateKey() not implemented") + }, _setDecryptionPassword: () => { throw new Error("_setMasterDecryptionPassword() not implemented") }, diff --git a/src/AuthContext/AuthContextProvider.tsx b/src/AuthContext/AuthContextProvider.tsx index 5e2aca6..ca1cfae 100644 --- a/src/AuthContext/AuthContextProvider.tsx +++ b/src/AuthContext/AuthContextProvider.tsx @@ -1,6 +1,7 @@ import {ReactElement, ReactNode, useCallback, useEffect, useMemo} from "react" import {useLocalStorage} from "react-use" import {AxiosError} from "axios" +import {decrypt, readMessage, readPrivateKey} from "openpgp" import {useMutation} from "@tanstack/react-query" @@ -23,6 +24,14 @@ export interface AuthContextProviderProps { export default function AuthContextProvider({ children, }: AuthContextProviderProps): ReactElement { + const {mutateAsync: refresh} = useMutation< + RefreshTokenResult, + AxiosError, + void + >(refreshToken, { + onError: () => logout(false), + }) + const [decryptionPassword, setDecryptionPassword] = useLocalStorage< string | null >("_global-context-auth-decryption-password", null) @@ -30,6 +39,7 @@ export default function AuthContextProvider({ "_global-context-auth-user", null, ) + const masterPassword = useMemo(() => { if (decryptionPassword === null || !user?.encryptedPassword) { return null @@ -46,7 +56,7 @@ export default function AuthContextProvider({ } }, []) - const encryptContent = useCallback( + const encryptUsingMasterPassword = useCallback( (content: string) => { if (!masterPassword) { throw new Error("Master password not set.") @@ -57,7 +67,7 @@ export default function AuthContextProvider({ [masterPassword], ) - const decryptContent = useCallback( + const decryptUsingMasterPassword = useCallback( (content: string) => { if (!masterPassword) { throw new Error("Master password not set.") @@ -68,75 +78,68 @@ export default function AuthContextProvider({ [masterPassword], ) - const tryDecryptUserNote = useCallback( - (newUser?: ServerUser): void => { - const userData: ServerUser = newUser ?? user - - if (userData?.encryptedNotes && masterPassword) { - const note = JSON.parse( - decryptContent(userData.encryptedNotes!), - ) - - // @ts-ignore - const newUser: User = { - ...user, - notes: note, - isDecrypted: true, - } - - setUser(newUser) + const decryptUsingPrivateKey = useCallback( + async (message: string): Promise => { + if (!user) { + throw new Error("User not set.") } + + if (!user.isDecrypted) { + throw new Error("User is not decrypted.") + } + + return ( + await decrypt({ + message: await readMessage({ + armoredMessage: message, + }), + decryptionKeys: await readPrivateKey({ + armoredKey: user.notes.privateKey, + }), + }) + ).data.toString() }, - [user, decryptContent, masterPassword], + [user], ) - const updateUser = useCallback( - (newUser: ServerUser | User) => { - setUser(newUser) - - tryDecryptUserNote() - }, - [user, tryDecryptUserNote], - ) - - const updateDecryptionPassword = useCallback( - (password: string) => { - setDecryptionPassword(password) - - tryDecryptUserNote() - }, - [tryDecryptUserNote], - ) - - const {mutateAsync: refresh} = useMutation< - RefreshTokenResult, - AxiosError, - void - >(refreshToken, { - onError: () => logout(false), - }) - const value = useMemo( () => ({ user: user ?? null, - login: updateUser, + login: setUser, logout, isAuthenticated: user !== null, - _encryptContent: encryptContent, - _decryptContent: decryptContent, - _setDecryptionPassword: updateDecryptionPassword, - _updateUser: updateUser, + _encryptUsingMasterPassword: encryptUsingMasterPassword, + _decryptUsingMasterPassword: decryptUsingMasterPassword, + _decryptUsingPrivateKey: decryptUsingPrivateKey, + _setDecryptionPassword: setDecryptionPassword, + _updateUser: setUser, }), - [ - user, - logout, - encryptContent, - decryptContent, - updateDecryptionPassword, - updateUser, - ], + [user, logout, encryptUsingMasterPassword, decryptUsingMasterPassword], ) + // Decrypt user notes + useEffect(() => { + if ( + user && + !user.isDecrypted && + user.encryptedPassword && + masterPassword + ) { + const note = JSON.parse( + decryptUsingMasterPassword(user.encryptedNotes!), + ) + + const newUser: User = { + ...user, + notes: note, + isDecrypted: true, + } + + setUser(newUser) + } + }, [user, decryptUsingMasterPassword]) + + // Refresh token and logout user if needed useEffect(() => { const interceptor = client.interceptors.response.use( response => response, diff --git a/src/route-widgets/SettingsRoute/DecryptedReport.tsx b/src/route-widgets/SettingsRoute/DecryptedReport.tsx index bbab298..608dcdd 100644 --- a/src/route-widgets/SettingsRoute/DecryptedReport.tsx +++ b/src/route-widgets/SettingsRoute/DecryptedReport.tsx @@ -1,37 +1,28 @@ -import {ReactElement} from "react" +import {ReactElement, useContext} from "react" import {useAsync} from "react-use" -import {useUser} from "~/hooks" -import {decrypt, readMessage, readPrivateKey} from "openpgp" +import camelcaseKeys from "camelcase-keys" + +import AuthContext from "~/AuthContext/AuthContext" export interface DecryptedReportProps { - encryptedNotes: string + encryptedContent: string } export default function DecryptedReport({ - encryptedNotes, + encryptedContent, }: DecryptedReportProps): ReactElement { - const user = useUser() + const {_decryptUsingPrivateKey} = useContext(AuthContext) const {value} = useAsync(async () => { - if (user.isDecrypted) { - // @ts-ignore - const key = await readPrivateKey({ - armoredKey: user.notes.privateKey, - }) - const message = await readMessage({ - armoredMessage: encryptedNotes, - }) - - return await decrypt({ - message: message, - decryptionKeys: key, - }) - } - }, [encryptedNotes]) + const message = await _decryptUsingPrivateKey(encryptedContent) + return camelcaseKeys(JSON.parse(message)) + }, [encryptedContent]) if (!value) { return <> } - return
{value}
+ console.log(value) + + return <> } diff --git a/src/routes/AuthenticatedRoute.tsx b/src/routes/AuthenticatedRoute.tsx index e81068f..0441b84 100644 --- a/src/routes/AuthenticatedRoute.tsx +++ b/src/routes/AuthenticatedRoute.tsx @@ -56,7 +56,7 @@ export default function AuthenticatedRoute(): ReactElement { - + diff --git a/src/routes/LoginRoute.tsx b/src/routes/LoginRoute.tsx index 0077d22..1ea3c07 100644 --- a/src/routes/LoginRoute.tsx +++ b/src/routes/LoginRoute.tsx @@ -25,7 +25,10 @@ export default function LoginRoute(): ReactElement { />, login(user, () => navigate("/"))} + onConfirm={user => { + login(user) + navigate("/") + }} email={email} sameRequestToken={sameRequestToken} />, diff --git a/src/routes/ReportsRoute.tsx b/src/routes/ReportsRoute.tsx index 33cf9e1..f52b895 100644 --- a/src/routes/ReportsRoute.tsx +++ b/src/routes/ReportsRoute.tsx @@ -19,7 +19,7 @@ export default function ReportsRoute(): ReactElement { {reports.map(report => ( ))} diff --git a/src/server-types.ts b/src/server-types.ts index a7e26d0..55748b6 100644 --- a/src/server-types.ts +++ b/src/server-types.ts @@ -91,7 +91,7 @@ export interface Alias { export interface Report { id: string - encryptedNotes: string + encryptedContent: string } export interface UserNote {