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",
|
||||
"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",
|
||||
|
@ -5,10 +5,11 @@ import {ServerUser, User} from "~/server-types"
|
||||
interface AuthContextTypeBase {
|
||||
user: ServerUser | User | null
|
||||
isAuthenticated: boolean
|
||||
login: (user: ServerUser) => Promise<void>
|
||||
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<string>
|
||||
_setDecryptionPassword: (decryptionPassword: string) => void
|
||||
_updateUser: (user: ServerUser | User) => void
|
||||
}
|
||||
@ -36,12 +37,15 @@ const AuthContext = createContext<AuthContextType>({
|
||||
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")
|
||||
},
|
||||
|
@ -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<string | null>(() => {
|
||||
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<string> => {
|
||||
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<AuthContextType>(
|
||||
() => ({
|
||||
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,
|
||||
|
@ -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 <div>{value}</div>
|
||||
console.log(value)
|
||||
|
||||
return <></>
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ export default function AuthenticatedRoute(): ReactElement {
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={8} md={10}>
|
||||
<Paper>
|
||||
<Box padding={4} maxHeight="60vh">
|
||||
<Box padding={4} maxHeight="60vh" overflow="scroll">
|
||||
<Outlet />
|
||||
</Box>
|
||||
</Paper>
|
||||
|
@ -25,7 +25,10 @@ export default function LoginRoute(): ReactElement {
|
||||
/>,
|
||||
<ConfirmCodeForm
|
||||
key="confirm_code_form"
|
||||
onConfirm={user => login(user, () => navigate("/"))}
|
||||
onConfirm={user => {
|
||||
login(user)
|
||||
navigate("/")
|
||||
}}
|
||||
email={email}
|
||||
sameRequestToken={sameRequestToken}
|
||||
/>,
|
||||
|
@ -19,7 +19,7 @@ export default function ReportsRoute(): ReactElement {
|
||||
{reports.map(report => (
|
||||
<DecryptedReport
|
||||
key={report.id}
|
||||
encryptedNotes={report.encryptedNotes}
|
||||
encryptedContent={report.encryptedContent}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
@ -91,7 +91,7 @@ export interface Alias {
|
||||
|
||||
export interface Report {
|
||||
id: string
|
||||
encryptedNotes: string
|
||||
encryptedContent: string
|
||||
}
|
||||
|
||||
export interface UserNote {
|
||||
|
Loading…
x
Reference in New Issue
Block a user