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
|
||||
_encryptUsingMasterPassword: (content: string) => string
|
||||
_decryptUsingPrivateKey: (message: string) => Promise<string>
|
||||
_setDecryptionPassword: (decryptionPassword: string) => void
|
||||
_setDecryptionPassword: (decryptionPassword: string) => boolean
|
||||
_updateUser: (user: ServerUser | User) => void
|
||||
}
|
||||
|
||||
|
@ -102,6 +102,34 @@ export default function AuthContextProvider({
|
||||
[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>(
|
||||
() => ({
|
||||
user: user ?? null,
|
||||
@ -111,7 +139,7 @@ export default function AuthContextProvider({
|
||||
_encryptUsingMasterPassword: encryptUsingMasterPassword,
|
||||
_decryptUsingMasterPassword: decryptUsingMasterPassword,
|
||||
_decryptUsingPrivateKey: decryptUsingPrivateKey,
|
||||
_setDecryptionPassword: setDecryptionPassword,
|
||||
_setDecryptionPassword: updateDecryptionPassword,
|
||||
_updateUser: setUser,
|
||||
}),
|
||||
[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"
|
||||
|
||||
export default async function getAliases(): Promise<Array<Alias>> {
|
||||
export default async function getAliases(): Promise<PaginationResult<Alias>> {
|
||||
const {data} = await client.get(
|
||||
`${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"
|
||||
|
||||
export default async function getReports(): Promise<Array<Report>> {
|
||||
export default async function getReports(): Promise<PaginationResult<Report>> {
|
||||
const {data} = await client.get(
|
||||
`${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 camelcaseKeys from "camelcase-keys"
|
||||
|
||||
import {DecryptedReportContent} from "~/server-types"
|
||||
import AuthContext from "~/AuthContext/AuthContext"
|
||||
import parseDecryptedReport from "~/apis/helpers/parse-decrypted-report"
|
||||
|
||||
export interface DecryptedReportProps {
|
||||
export interface DecryptReportProps {
|
||||
encryptedContent: string
|
||||
children: (report: DecryptedReportContent) => ReactElement
|
||||
}
|
||||
|
||||
export default function DecryptedReport({
|
||||
export default function DecryptReport({
|
||||
encryptedContent,
|
||||
}: DecryptedReportProps): ReactElement {
|
||||
children: render,
|
||||
}: DecryptReportProps): ReactElement {
|
||||
const {_decryptUsingPrivateKey} = useContext(AuthContext)
|
||||
|
||||
const {value} = useAsync(async () => {
|
||||
const message = await _decryptUsingPrivateKey(encryptedContent)
|
||||
return camelcaseKeys(JSON.parse(message))
|
||||
const content = camelcaseKeys(JSON.parse(message))
|
||||
|
||||
return parseDecryptedReport(content)
|
||||
}, [encryptedContent])
|
||||
|
||||
if (!value) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
console.log(value)
|
||||
|
||||
return <></>
|
||||
return render(value)
|
||||
}
|
@ -4,14 +4,14 @@ import {AxiosError} from "axios"
|
||||
import {Grid, List, Typography} from "@mui/material"
|
||||
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 CreateRandomAliasButton from "~/route-widgets/AliasRoute/CreateRandomAliasButton"
|
||||
import QueryResult from "~/components/QueryResult"
|
||||
import getAliases from "~/apis/get-aliases"
|
||||
|
||||
export default function AliasesRoute(): ReactElement {
|
||||
const query = useQuery<Array<Alias>, AxiosError>(
|
||||
const query = useQuery<PaginationResult<Alias>, AxiosError>(
|
||||
["get_aliases"],
|
||||
getAliases,
|
||||
)
|
||||
@ -24,10 +24,10 @@ export default function AliasesRoute(): ReactElement {
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<QueryResult<Array<Alias>> query={query}>
|
||||
{aliases => (
|
||||
<QueryResult<PaginationResult<Alias>> query={query}>
|
||||
{result => (
|
||||
<List>
|
||||
{aliases.map(alias => (
|
||||
{result.items.map(alias => (
|
||||
<AliasListItem key={alias.id} alias={alias} />
|
||||
))}
|
||||
</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 {useFormik} from "formik"
|
||||
|
||||
import {buildEncryptionPassword} from "~/utils"
|
||||
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"
|
||||
|
||||
interface Form {
|
||||
password: string
|
||||
}
|
||||
|
||||
const schema = yup.object().shape({
|
||||
password: yup.string().required(),
|
||||
})
|
||||
|
||||
export default function EnterDecryptionPassword(): ReactElement {
|
||||
const navigate = useNavigate()
|
||||
const user = useUser()
|
||||
const {_setDecryptionPassword} = useContext(AuthContext)
|
||||
|
||||
const [password, setPassword] = useState<string>("")
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
value={password}
|
||||
onChange={event => setPassword(event.target.value)}
|
||||
/>
|
||||
<button
|
||||
onClick={() => {
|
||||
const encryptionPassword = buildEncryptionPassword(
|
||||
const formik = useFormik<Form>({
|
||||
validationSchema: schema,
|
||||
initialValues: {
|
||||
password: "",
|
||||
},
|
||||
onSubmit: async ({password}, {setErrors}) => {
|
||||
const decryptionPassword = buildEncryptionPassword(
|
||||
password,
|
||||
user.email.address,
|
||||
)
|
||||
|
||||
_setDecryptionPassword(encryptionPassword)
|
||||
if (!_setDecryptionPassword(decryptionPassword)) {
|
||||
setErrors({password: "Password is invalid."})
|
||||
} else {
|
||||
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>
|
||||
</div>
|
||||
{[
|
||||
<PasswordField
|
||||
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"
|
||||
onConfirm={user => {
|
||||
login(user)
|
||||
|
||||
setTimeout(() => {
|
||||
if (user.encryptedPassword) {
|
||||
navigate("/enter-password")
|
||||
} else {
|
||||
navigate("/")
|
||||
}
|
||||
}, 0)
|
||||
}}
|
||||
email={email}
|
||||
sameRequestToken={sameRequestToken}
|
||||
|
@ -1,28 +1,43 @@
|
||||
import {useQuery} from "@tanstack/react-query"
|
||||
import {ReactElement} from "react"
|
||||
import {Report} from "~/server-types"
|
||||
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 DecryptedReport from "~/route-widgets/SettingsRoute/DecryptedReport"
|
||||
import DecryptReport from "~/route-widgets/SettingsRoute/DecryptReport"
|
||||
import QueryResult from "~/components/QueryResult"
|
||||
|
||||
export default function ReportsRoute(): ReactElement {
|
||||
const query = useQuery<Array<Report>, AxiosError>(
|
||||
const query = useQuery<PaginationResult<Report>, AxiosError>(
|
||||
["get_reports"],
|
||||
getReports,
|
||||
)
|
||||
|
||||
return (
|
||||
<QueryResult<Array<Report>> query={query}>
|
||||
{reports => (
|
||||
<>
|
||||
{reports.map(report => (
|
||||
<DecryptedReport
|
||||
<QueryResult<PaginationResult<Report>> query={query}>
|
||||
{result => (
|
||||
<List>
|
||||
{result.items.map(report => (
|
||||
<DecryptReport
|
||||
key={report.id}
|
||||
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>
|
||||
)
|
||||
|
@ -94,6 +94,36 @@ export interface Report {
|
||||
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 {
|
||||
theme: Theme
|
||||
privateKey: string
|
||||
|
Loading…
x
Reference in New Issue
Block a user