improvements

This commit is contained in:
Myzel394 2022-10-22 16:32:07 +02:00
parent 62faf096bb
commit 4097aa8c2d
11 changed files with 198 additions and 50 deletions

View File

@ -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
}

View File

@ -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],

View File

@ -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`,
{

View File

@ -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`,
{

View 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),
},
},
}
}

View File

@ -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)
}

View File

@ -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>

View File

@ -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>("")
const formik = useFormik<Form>({
validationSchema: schema,
initialValues: {
password: "",
},
onSubmit: async ({password}, {setErrors}) => {
const decryptionPassword = buildEncryptionPassword(
password,
user.email.address,
)
if (!_setDecryptionPassword(decryptionPassword)) {
setErrors({password: "Password is invalid."})
} else {
navigate("/")
}
},
})
return (
<div>
<input
value={password}
onChange={event => setPassword(event.target.value)}
/>
<button
onClick={() => {
const encryptionPassword = buildEncryptionPassword(
password,
user.email.address,
)
_setDecryptionPassword(encryptionPassword)
navigate("/")
}}
<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>
)
}

View File

@ -27,7 +27,14 @@ export default function LoginRoute(): ReactElement {
key="confirm_code_form"
onConfirm={user => {
login(user)
navigate("/")
setTimeout(() => {
if (user.encryptedPassword) {
navigate("/enter-password")
} else {
navigate("/")
}
}, 0)
}}
email={email}
sameRequestToken={sameRequestToken}

View File

@ -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>
)

View File

@ -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