mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-20 08:15:26 +02:00
improved AuthContext
This commit is contained in:
parent
388fa9488c
commit
62faf096bb
@ -18,6 +18,7 @@
|
|||||||
"@tanstack/react-query": "^4.12.0",
|
"@tanstack/react-query": "^4.12.0",
|
||||||
"axios": "^1.1.2",
|
"axios": "^1.1.2",
|
||||||
"axios-case-converter": "^0.11.1",
|
"axios-case-converter": "^0.11.1",
|
||||||
|
"camelcase-keys": "^8.0.2",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
|
@ -5,10 +5,11 @@ import {ServerUser, User} from "~/server-types"
|
|||||||
interface AuthContextTypeBase {
|
interface AuthContextTypeBase {
|
||||||
user: ServerUser | User | null
|
user: ServerUser | User | null
|
||||||
isAuthenticated: boolean
|
isAuthenticated: boolean
|
||||||
login: (user: ServerUser) => Promise<void>
|
login: (user: ServerUser | User) => void
|
||||||
logout: () => void
|
logout: () => void
|
||||||
_decryptContent: (content: string) => string
|
_decryptUsingMasterPassword: (content: string) => string
|
||||||
_encryptContent: (content: string) => string
|
_encryptUsingMasterPassword: (content: string) => string
|
||||||
|
_decryptUsingPrivateKey: (message: string) => Promise<string>
|
||||||
_setDecryptionPassword: (decryptionPassword: string) => void
|
_setDecryptionPassword: (decryptionPassword: string) => void
|
||||||
_updateUser: (user: ServerUser | User) => void
|
_updateUser: (user: ServerUser | User) => void
|
||||||
}
|
}
|
||||||
@ -36,12 +37,15 @@ const AuthContext = createContext<AuthContextType>({
|
|||||||
logout: () => {
|
logout: () => {
|
||||||
throw new Error("logout() not implemented")
|
throw new Error("logout() not implemented")
|
||||||
},
|
},
|
||||||
_decryptContent: () => {
|
_decryptUsingMasterPassword: () => {
|
||||||
throw new Error("_decryptContent() not implemented")
|
throw new Error("_decryptContent() not implemented")
|
||||||
},
|
},
|
||||||
_encryptContent: () => {
|
_encryptUsingMasterPassword: () => {
|
||||||
throw new Error("_encryptContent() not implemented")
|
throw new Error("_encryptContent() not implemented")
|
||||||
},
|
},
|
||||||
|
_decryptUsingPrivateKey: () => {
|
||||||
|
throw new Error("_decryptUsingPrivateKey() not implemented")
|
||||||
|
},
|
||||||
_setDecryptionPassword: () => {
|
_setDecryptionPassword: () => {
|
||||||
throw new Error("_setMasterDecryptionPassword() not implemented")
|
throw new Error("_setMasterDecryptionPassword() not implemented")
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {ReactElement, ReactNode, useCallback, useEffect, useMemo} from "react"
|
import {ReactElement, ReactNode, useCallback, useEffect, useMemo} from "react"
|
||||||
import {useLocalStorage} from "react-use"
|
import {useLocalStorage} from "react-use"
|
||||||
import {AxiosError} from "axios"
|
import {AxiosError} from "axios"
|
||||||
|
import {decrypt, readMessage, readPrivateKey} from "openpgp"
|
||||||
|
|
||||||
import {useMutation} from "@tanstack/react-query"
|
import {useMutation} from "@tanstack/react-query"
|
||||||
|
|
||||||
@ -23,6 +24,14 @@ export interface AuthContextProviderProps {
|
|||||||
export default function AuthContextProvider({
|
export default function AuthContextProvider({
|
||||||
children,
|
children,
|
||||||
}: AuthContextProviderProps): ReactElement {
|
}: AuthContextProviderProps): ReactElement {
|
||||||
|
const {mutateAsync: refresh} = useMutation<
|
||||||
|
RefreshTokenResult,
|
||||||
|
AxiosError,
|
||||||
|
void
|
||||||
|
>(refreshToken, {
|
||||||
|
onError: () => logout(false),
|
||||||
|
})
|
||||||
|
|
||||||
const [decryptionPassword, setDecryptionPassword] = useLocalStorage<
|
const [decryptionPassword, setDecryptionPassword] = useLocalStorage<
|
||||||
string | null
|
string | null
|
||||||
>("_global-context-auth-decryption-password", null)
|
>("_global-context-auth-decryption-password", null)
|
||||||
@ -30,6 +39,7 @@ export default function AuthContextProvider({
|
|||||||
"_global-context-auth-user",
|
"_global-context-auth-user",
|
||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
|
|
||||||
const masterPassword = useMemo<string | null>(() => {
|
const masterPassword = useMemo<string | null>(() => {
|
||||||
if (decryptionPassword === null || !user?.encryptedPassword) {
|
if (decryptionPassword === null || !user?.encryptedPassword) {
|
||||||
return null
|
return null
|
||||||
@ -46,7 +56,7 @@ export default function AuthContextProvider({
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const encryptContent = useCallback(
|
const encryptUsingMasterPassword = useCallback(
|
||||||
(content: string) => {
|
(content: string) => {
|
||||||
if (!masterPassword) {
|
if (!masterPassword) {
|
||||||
throw new Error("Master password not set.")
|
throw new Error("Master password not set.")
|
||||||
@ -57,7 +67,7 @@ export default function AuthContextProvider({
|
|||||||
[masterPassword],
|
[masterPassword],
|
||||||
)
|
)
|
||||||
|
|
||||||
const decryptContent = useCallback(
|
const decryptUsingMasterPassword = useCallback(
|
||||||
(content: string) => {
|
(content: string) => {
|
||||||
if (!masterPassword) {
|
if (!masterPassword) {
|
||||||
throw new Error("Master password not set.")
|
throw new Error("Master password not set.")
|
||||||
@ -68,75 +78,68 @@ export default function AuthContextProvider({
|
|||||||
[masterPassword],
|
[masterPassword],
|
||||||
)
|
)
|
||||||
|
|
||||||
const tryDecryptUserNote = useCallback(
|
const decryptUsingPrivateKey = useCallback(
|
||||||
(newUser?: ServerUser): void => {
|
async (message: string): Promise<string> => {
|
||||||
const userData: ServerUser = newUser ?? user
|
if (!user) {
|
||||||
|
throw new Error("User not set.")
|
||||||
if (userData?.encryptedNotes && masterPassword) {
|
|
||||||
const note = JSON.parse(
|
|
||||||
decryptContent(userData.encryptedNotes!),
|
|
||||||
)
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const newUser: User = {
|
|
||||||
...user,
|
|
||||||
notes: note,
|
|
||||||
isDecrypted: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
setUser(newUser)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<AuthContextType>(
|
const value = useMemo<AuthContextType>(
|
||||||
() => ({
|
() => ({
|
||||||
user: user ?? null,
|
user: user ?? null,
|
||||||
login: updateUser,
|
login: setUser,
|
||||||
logout,
|
logout,
|
||||||
isAuthenticated: user !== null,
|
isAuthenticated: user !== null,
|
||||||
_encryptContent: encryptContent,
|
_encryptUsingMasterPassword: encryptUsingMasterPassword,
|
||||||
_decryptContent: decryptContent,
|
_decryptUsingMasterPassword: decryptUsingMasterPassword,
|
||||||
_setDecryptionPassword: updateDecryptionPassword,
|
_decryptUsingPrivateKey: decryptUsingPrivateKey,
|
||||||
_updateUser: updateUser,
|
_setDecryptionPassword: setDecryptionPassword,
|
||||||
|
_updateUser: setUser,
|
||||||
}),
|
}),
|
||||||
[
|
[user, logout, encryptUsingMasterPassword, decryptUsingMasterPassword],
|
||||||
user,
|
|
||||||
logout,
|
|
||||||
encryptContent,
|
|
||||||
decryptContent,
|
|
||||||
updateDecryptionPassword,
|
|
||||||
updateUser,
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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(() => {
|
useEffect(() => {
|
||||||
const interceptor = client.interceptors.response.use(
|
const interceptor = client.interceptors.response.use(
|
||||||
response => response,
|
response => response,
|
||||||
|
@ -1,37 +1,28 @@
|
|||||||
import {ReactElement} from "react"
|
import {ReactElement, useContext} from "react"
|
||||||
import {useAsync} from "react-use"
|
import {useAsync} from "react-use"
|
||||||
import {useUser} from "~/hooks"
|
import camelcaseKeys from "camelcase-keys"
|
||||||
import {decrypt, readMessage, readPrivateKey} from "openpgp"
|
|
||||||
|
import AuthContext from "~/AuthContext/AuthContext"
|
||||||
|
|
||||||
export interface DecryptedReportProps {
|
export interface DecryptedReportProps {
|
||||||
encryptedNotes: string
|
encryptedContent: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DecryptedReport({
|
export default function DecryptedReport({
|
||||||
encryptedNotes,
|
encryptedContent,
|
||||||
}: DecryptedReportProps): ReactElement {
|
}: DecryptedReportProps): ReactElement {
|
||||||
const user = useUser()
|
const {_decryptUsingPrivateKey} = useContext(AuthContext)
|
||||||
|
|
||||||
const {value} = useAsync(async () => {
|
const {value} = useAsync(async () => {
|
||||||
if (user.isDecrypted) {
|
const message = await _decryptUsingPrivateKey(encryptedContent)
|
||||||
// @ts-ignore
|
return camelcaseKeys(JSON.parse(message))
|
||||||
const key = await readPrivateKey({
|
}, [encryptedContent])
|
||||||
armoredKey: user.notes.privateKey,
|
|
||||||
})
|
|
||||||
const message = await readMessage({
|
|
||||||
armoredMessage: encryptedNotes,
|
|
||||||
})
|
|
||||||
|
|
||||||
return await decrypt({
|
|
||||||
message: message,
|
|
||||||
decryptionKeys: key,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [encryptedNotes])
|
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div>{value}</div>
|
console.log(value)
|
||||||
|
|
||||||
|
return <></>
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ export default function AuthenticatedRoute(): ReactElement {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={8} md={10}>
|
<Grid item xs={12} sm={8} md={10}>
|
||||||
<Paper>
|
<Paper>
|
||||||
<Box padding={4} maxHeight="60vh">
|
<Box padding={4} maxHeight="60vh" overflow="scroll">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
@ -25,7 +25,10 @@ export default function LoginRoute(): ReactElement {
|
|||||||
/>,
|
/>,
|
||||||
<ConfirmCodeForm
|
<ConfirmCodeForm
|
||||||
key="confirm_code_form"
|
key="confirm_code_form"
|
||||||
onConfirm={user => login(user, () => navigate("/"))}
|
onConfirm={user => {
|
||||||
|
login(user)
|
||||||
|
navigate("/")
|
||||||
|
}}
|
||||||
email={email}
|
email={email}
|
||||||
sameRequestToken={sameRequestToken}
|
sameRequestToken={sameRequestToken}
|
||||||
/>,
|
/>,
|
||||||
|
@ -19,7 +19,7 @@ export default function ReportsRoute(): ReactElement {
|
|||||||
{reports.map(report => (
|
{reports.map(report => (
|
||||||
<DecryptedReport
|
<DecryptedReport
|
||||||
key={report.id}
|
key={report.id}
|
||||||
encryptedNotes={report.encryptedNotes}
|
encryptedContent={report.encryptedContent}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
@ -91,7 +91,7 @@ export interface Alias {
|
|||||||
|
|
||||||
export interface Report {
|
export interface Report {
|
||||||
id: string
|
id: string
|
||||||
encryptedNotes: string
|
encryptedContent: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserNote {
|
export interface UserNote {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user