mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-20 08:15:26 +02:00
improvements
This commit is contained in:
parent
62faf096bb
commit
4097aa8c2d
@ -10,7 +10,7 @@ interface AuthContextTypeBase {
|
|||||||
_decryptUsingMasterPassword: (content: string) => string
|
_decryptUsingMasterPassword: (content: string) => string
|
||||||
_encryptUsingMasterPassword: (content: string) => string
|
_encryptUsingMasterPassword: (content: string) => string
|
||||||
_decryptUsingPrivateKey: (message: string) => Promise<string>
|
_decryptUsingPrivateKey: (message: string) => Promise<string>
|
||||||
_setDecryptionPassword: (decryptionPassword: string) => void
|
_setDecryptionPassword: (decryptionPassword: string) => boolean
|
||||||
_updateUser: (user: ServerUser | User) => void
|
_updateUser: (user: ServerUser | User) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +102,34 @@ export default function AuthContextProvider({
|
|||||||
[user],
|
[user],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const updateDecryptionPassword = useCallback(
|
||||||
|
(password: string): boolean => {
|
||||||
|
if (!user) {
|
||||||
|
throw new Error("User not set.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.isDecrypted) {
|
||||||
|
// Password already set
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if the password is correct
|
||||||
|
const masterPassword = decryptString(
|
||||||
|
user.encryptedPassword,
|
||||||
|
password,
|
||||||
|
)
|
||||||
|
JSON.parse(decryptString(user.encryptedNotes, masterPassword))
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
setDecryptionPassword(password)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
[user?.encryptedPassword],
|
||||||
|
)
|
||||||
|
|
||||||
const value = useMemo<AuthContextType>(
|
const value = useMemo<AuthContextType>(
|
||||||
() => ({
|
() => ({
|
||||||
user: user ?? null,
|
user: user ?? null,
|
||||||
@ -111,7 +139,7 @@ export default function AuthContextProvider({
|
|||||||
_encryptUsingMasterPassword: encryptUsingMasterPassword,
|
_encryptUsingMasterPassword: encryptUsingMasterPassword,
|
||||||
_decryptUsingMasterPassword: decryptUsingMasterPassword,
|
_decryptUsingMasterPassword: decryptUsingMasterPassword,
|
||||||
_decryptUsingPrivateKey: decryptUsingPrivateKey,
|
_decryptUsingPrivateKey: decryptUsingPrivateKey,
|
||||||
_setDecryptionPassword: setDecryptionPassword,
|
_setDecryptionPassword: updateDecryptionPassword,
|
||||||
_updateUser: setUser,
|
_updateUser: setUser,
|
||||||
}),
|
}),
|
||||||
[user, logout, encryptUsingMasterPassword, decryptUsingMasterPassword],
|
[user, logout, encryptUsingMasterPassword, decryptUsingMasterPassword],
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {Alias} from "~/server-types"
|
import {Alias, PaginationResult} from "~/server-types"
|
||||||
import {client} from "~/constants/axios-client"
|
import {client} from "~/constants/axios-client"
|
||||||
|
|
||||||
export default async function getAliases(): Promise<Array<Alias>> {
|
export default async function getAliases(): Promise<PaginationResult<Alias>> {
|
||||||
const {data} = await client.get(
|
const {data} = await client.get(
|
||||||
`${import.meta.env.VITE_SERVER_BASE_URL}/alias`,
|
`${import.meta.env.VITE_SERVER_BASE_URL}/alias`,
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {Report} from "~/server-types"
|
import {PaginationResult, Report} from "~/server-types"
|
||||||
import {client} from "~/constants/axios-client"
|
import {client} from "~/constants/axios-client"
|
||||||
|
|
||||||
export default async function getReports(): Promise<Array<Report>> {
|
export default async function getReports(): Promise<PaginationResult<Report>> {
|
||||||
const {data} = await client.get(
|
const {data} = await client.get(
|
||||||
`${import.meta.env.VITE_SERVER_BASE_URL}/report`,
|
`${import.meta.env.VITE_SERVER_BASE_URL}/report`,
|
||||||
{
|
{
|
||||||
|
16
src/apis/helpers/parse-decrypted-report.ts
Normal file
16
src/apis/helpers/parse-decrypted-report.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import {DecryptedReportContent} from "~/server-types"
|
||||||
|
|
||||||
|
export default function parseDecryptedReport(
|
||||||
|
report: any,
|
||||||
|
): DecryptedReportContent {
|
||||||
|
return {
|
||||||
|
...report,
|
||||||
|
messageDetails: {
|
||||||
|
...report.messageDetails,
|
||||||
|
meta: {
|
||||||
|
...report.messageDetails.meta,
|
||||||
|
createdAt: new Date(report.messageDetails.meta.createdAt),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -2,27 +2,31 @@ import {ReactElement, useContext} from "react"
|
|||||||
import {useAsync} from "react-use"
|
import {useAsync} from "react-use"
|
||||||
import camelcaseKeys from "camelcase-keys"
|
import camelcaseKeys from "camelcase-keys"
|
||||||
|
|
||||||
|
import {DecryptedReportContent} from "~/server-types"
|
||||||
import AuthContext from "~/AuthContext/AuthContext"
|
import AuthContext from "~/AuthContext/AuthContext"
|
||||||
|
import parseDecryptedReport from "~/apis/helpers/parse-decrypted-report"
|
||||||
|
|
||||||
export interface DecryptedReportProps {
|
export interface DecryptReportProps {
|
||||||
encryptedContent: string
|
encryptedContent: string
|
||||||
|
children: (report: DecryptedReportContent) => ReactElement
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DecryptedReport({
|
export default function DecryptReport({
|
||||||
encryptedContent,
|
encryptedContent,
|
||||||
}: DecryptedReportProps): ReactElement {
|
children: render,
|
||||||
|
}: DecryptReportProps): ReactElement {
|
||||||
const {_decryptUsingPrivateKey} = useContext(AuthContext)
|
const {_decryptUsingPrivateKey} = useContext(AuthContext)
|
||||||
|
|
||||||
const {value} = useAsync(async () => {
|
const {value} = useAsync(async () => {
|
||||||
const message = await _decryptUsingPrivateKey(encryptedContent)
|
const message = await _decryptUsingPrivateKey(encryptedContent)
|
||||||
return camelcaseKeys(JSON.parse(message))
|
const content = camelcaseKeys(JSON.parse(message))
|
||||||
|
|
||||||
|
return parseDecryptedReport(content)
|
||||||
}, [encryptedContent])
|
}, [encryptedContent])
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(value)
|
return render(value)
|
||||||
|
|
||||||
return <></>
|
|
||||||
}
|
}
|
@ -4,14 +4,14 @@ import {AxiosError} from "axios"
|
|||||||
import {Grid, List, Typography} from "@mui/material"
|
import {Grid, List, Typography} from "@mui/material"
|
||||||
import {useQuery} from "@tanstack/react-query"
|
import {useQuery} from "@tanstack/react-query"
|
||||||
|
|
||||||
import {Alias} from "~/server-types"
|
import {Alias, PaginationResult} from "~/server-types"
|
||||||
import AliasListItem from "~/route-widgets/AliasRoute/AliasListItem"
|
import AliasListItem from "~/route-widgets/AliasRoute/AliasListItem"
|
||||||
import CreateRandomAliasButton from "~/route-widgets/AliasRoute/CreateRandomAliasButton"
|
import CreateRandomAliasButton from "~/route-widgets/AliasRoute/CreateRandomAliasButton"
|
||||||
import QueryResult from "~/components/QueryResult"
|
import QueryResult from "~/components/QueryResult"
|
||||||
import getAliases from "~/apis/get-aliases"
|
import getAliases from "~/apis/get-aliases"
|
||||||
|
|
||||||
export default function AliasesRoute(): ReactElement {
|
export default function AliasesRoute(): ReactElement {
|
||||||
const query = useQuery<Array<Alias>, AxiosError>(
|
const query = useQuery<PaginationResult<Alias>, AxiosError>(
|
||||||
["get_aliases"],
|
["get_aliases"],
|
||||||
getAliases,
|
getAliases,
|
||||||
)
|
)
|
||||||
@ -24,10 +24,10 @@ export default function AliasesRoute(): ReactElement {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<QueryResult<Array<Alias>> query={query}>
|
<QueryResult<PaginationResult<Alias>> query={query}>
|
||||||
{aliases => (
|
{result => (
|
||||||
<List>
|
<List>
|
||||||
{aliases.map(alias => (
|
{result.items.map(alias => (
|
||||||
<AliasListItem key={alias.id} alias={alias} />
|
<AliasListItem key={alias.id} alias={alias} />
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
|
@ -1,35 +1,83 @@
|
|||||||
import {ReactElement, useContext, useState} from "react"
|
import * as yup from "yup"
|
||||||
|
import {ReactElement, useContext} from "react"
|
||||||
import {useNavigate} from "react-router-dom"
|
import {useNavigate} from "react-router-dom"
|
||||||
|
import {useFormik} from "formik"
|
||||||
|
|
||||||
import {buildEncryptionPassword} from "~/utils"
|
import {buildEncryptionPassword} from "~/utils"
|
||||||
import {useUser} from "~/hooks"
|
import {useUser} from "~/hooks"
|
||||||
|
import {PasswordField, SimpleForm} from "~/components"
|
||||||
|
import {InputAdornment} from "@mui/material"
|
||||||
|
import {MdLock} from "react-icons/md"
|
||||||
import AuthContext from "~/AuthContext/AuthContext"
|
import AuthContext from "~/AuthContext/AuthContext"
|
||||||
|
|
||||||
|
interface Form {
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = yup.object().shape({
|
||||||
|
password: yup.string().required(),
|
||||||
|
})
|
||||||
|
|
||||||
export default function EnterDecryptionPassword(): ReactElement {
|
export default function EnterDecryptionPassword(): ReactElement {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
const {_setDecryptionPassword} = useContext(AuthContext)
|
const {_setDecryptionPassword} = useContext(AuthContext)
|
||||||
|
|
||||||
const [password, setPassword] = useState<string>("")
|
const formik = useFormik<Form>({
|
||||||
|
validationSchema: schema,
|
||||||
return (
|
initialValues: {
|
||||||
<div>
|
password: "",
|
||||||
<input
|
},
|
||||||
value={password}
|
onSubmit: async ({password}, {setErrors}) => {
|
||||||
onChange={event => setPassword(event.target.value)}
|
const decryptionPassword = buildEncryptionPassword(
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
const encryptionPassword = buildEncryptionPassword(
|
|
||||||
password,
|
password,
|
||||||
user.email.address,
|
user.email.address,
|
||||||
)
|
)
|
||||||
|
|
||||||
_setDecryptionPassword(encryptionPassword)
|
if (!_setDecryptionPassword(decryptionPassword)) {
|
||||||
|
setErrors({password: "Password is invalid."})
|
||||||
|
} else {
|
||||||
navigate("/")
|
navigate("/")
|
||||||
}}
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={formik.handleSubmit}>
|
||||||
|
<SimpleForm
|
||||||
|
title="Decrypt reports"
|
||||||
|
description="Please enter your password so that your reports can de decrypted."
|
||||||
|
cancelActionLabel="Don't decrypt"
|
||||||
|
continueActionLabel="Continue"
|
||||||
|
isSubmitting={formik.isSubmitting}
|
||||||
>
|
>
|
||||||
Ok
|
{[
|
||||||
</button>
|
<PasswordField
|
||||||
</div>
|
key="password"
|
||||||
|
fullWidth
|
||||||
|
name="password"
|
||||||
|
id="password"
|
||||||
|
label="Password"
|
||||||
|
value={formik.values.password}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
disabled={formik.isSubmitting}
|
||||||
|
error={
|
||||||
|
formik.touched.password &&
|
||||||
|
Boolean(formik.errors.password)
|
||||||
|
}
|
||||||
|
helperText={
|
||||||
|
formik.touched.password && formik.errors.password
|
||||||
|
}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<MdLock />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
]}
|
||||||
|
</SimpleForm>
|
||||||
|
</form>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,14 @@ export default function LoginRoute(): ReactElement {
|
|||||||
key="confirm_code_form"
|
key="confirm_code_form"
|
||||||
onConfirm={user => {
|
onConfirm={user => {
|
||||||
login(user)
|
login(user)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (user.encryptedPassword) {
|
||||||
|
navigate("/enter-password")
|
||||||
|
} else {
|
||||||
navigate("/")
|
navigate("/")
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
}}
|
}}
|
||||||
email={email}
|
email={email}
|
||||||
sameRequestToken={sameRequestToken}
|
sameRequestToken={sameRequestToken}
|
||||||
|
@ -1,28 +1,43 @@
|
|||||||
import {useQuery} from "@tanstack/react-query"
|
|
||||||
import {ReactElement} from "react"
|
import {ReactElement} from "react"
|
||||||
import {Report} from "~/server-types"
|
|
||||||
import {AxiosError} from "axios"
|
import {AxiosError} from "axios"
|
||||||
|
|
||||||
|
import {useQuery} from "@tanstack/react-query"
|
||||||
|
import {List, ListItem, ListItemText} from "@mui/material"
|
||||||
|
|
||||||
|
import {PaginationResult, Report} from "~/server-types"
|
||||||
import {getReports} from "~/apis"
|
import {getReports} from "~/apis"
|
||||||
import DecryptedReport from "~/route-widgets/SettingsRoute/DecryptedReport"
|
import DecryptReport from "~/route-widgets/SettingsRoute/DecryptReport"
|
||||||
import QueryResult from "~/components/QueryResult"
|
import QueryResult from "~/components/QueryResult"
|
||||||
|
|
||||||
export default function ReportsRoute(): ReactElement {
|
export default function ReportsRoute(): ReactElement {
|
||||||
const query = useQuery<Array<Report>, AxiosError>(
|
const query = useQuery<PaginationResult<Report>, AxiosError>(
|
||||||
["get_reports"],
|
["get_reports"],
|
||||||
getReports,
|
getReports,
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryResult<Array<Report>> query={query}>
|
<QueryResult<PaginationResult<Report>> query={query}>
|
||||||
{reports => (
|
{result => (
|
||||||
<>
|
<List>
|
||||||
{reports.map(report => (
|
{result.items.map(report => (
|
||||||
<DecryptedReport
|
<DecryptReport
|
||||||
key={report.id}
|
key={report.id}
|
||||||
encryptedContent={report.encryptedContent}
|
encryptedContent={report.encryptedContent}
|
||||||
/>
|
>
|
||||||
|
{report => (
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
report.messageDetails.content
|
||||||
|
.subject
|
||||||
|
}
|
||||||
|
secondary={`${report.messageDetails.meta.from} -> ${report.messageDetails.meta.to}`}
|
||||||
|
></ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
</DecryptReport>
|
||||||
))}
|
))}
|
||||||
</>
|
</List>
|
||||||
)}
|
)}
|
||||||
</QueryResult>
|
</QueryResult>
|
||||||
)
|
)
|
||||||
|
@ -94,6 +94,36 @@ export interface Report {
|
|||||||
encryptedContent: string
|
encryptedContent: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DecryptedReportContent {
|
||||||
|
version: "1.0"
|
||||||
|
messageDetails: {
|
||||||
|
meta: {
|
||||||
|
from: string
|
||||||
|
to: string
|
||||||
|
createdAt: Date
|
||||||
|
}
|
||||||
|
content: {
|
||||||
|
subject: string
|
||||||
|
proxiedImages: Array<{
|
||||||
|
url: string
|
||||||
|
imageProxyId: string
|
||||||
|
}>
|
||||||
|
singlePixelImages: Array<{
|
||||||
|
source: string
|
||||||
|
trackerName: string
|
||||||
|
trackerUrl: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PaginationResult<T> = {
|
||||||
|
items: T[]
|
||||||
|
total: number
|
||||||
|
page: number
|
||||||
|
size: number
|
||||||
|
}
|
||||||
|
|
||||||
export interface UserNote {
|
export interface UserNote {
|
||||||
theme: Theme
|
theme: Theme
|
||||||
privateKey: string
|
privateKey: string
|
||||||
|
Loading…
x
Reference in New Issue
Block a user