mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-19 07:55:25 +02:00
adding encryption
This commit is contained in:
parent
5e4e491483
commit
ade221be7d
@ -28,10 +28,12 @@
|
||||
"react-icons": "^4.4.0",
|
||||
"react-router-dom": "^6.4.2",
|
||||
"react-use": "^17.4.0",
|
||||
"secure-random-password": "^0.2.3",
|
||||
"ua-parser-js": "^1.0.2",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/date-fns": "^2.6.0",
|
||||
"@types/openpgp": "^4.4.18",
|
||||
"@types/react": "^18.0.17",
|
||||
@ -39,6 +41,7 @@
|
||||
"@types/react-icons": "^3.0.0",
|
||||
"@types/react-router": "^5.1.19",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/secure-random-password": "^0.2.1",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@types/yup": "^0.32.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.40.0",
|
||||
|
@ -7,6 +7,9 @@ interface AuthContextTypeBase {
|
||||
isAuthenticated: boolean
|
||||
login: (user: User, callback: () => void) => Promise<void>
|
||||
logout: () => void
|
||||
_decryptContent: (content: string) => string
|
||||
_encryptContent: (content: string) => string
|
||||
_setMasterPassword: (masterPassword: string) => void
|
||||
}
|
||||
|
||||
interface AuthContextTypeAuthenticated {
|
||||
@ -32,6 +35,15 @@ const AuthContext = createContext<AuthContextType>({
|
||||
logout: () => {
|
||||
throw new Error("logout() not implemented")
|
||||
},
|
||||
_decryptContent: () => {
|
||||
throw new Error("_decryptContent() not implemented")
|
||||
},
|
||||
_encryptContent: () => {
|
||||
throw new Error("_encryptContent() not implemented")
|
||||
},
|
||||
_setMasterPassword: () => {
|
||||
throw new Error("_setMasterPassword() not implemented")
|
||||
},
|
||||
})
|
||||
|
||||
export default AuthContext
|
||||
|
@ -1,4 +1,11 @@
|
||||
import {ReactElement, ReactNode, useCallback, useEffect, useMemo} from "react"
|
||||
import {
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react"
|
||||
import {useLocalStorage} from "react-use"
|
||||
import {AxiosError} from "axios"
|
||||
|
||||
@ -12,6 +19,7 @@ import {
|
||||
refreshToken,
|
||||
} from "~/apis"
|
||||
import {client} from "~/constants/axios-client"
|
||||
import {decryptString, encryptString} from "~/utils"
|
||||
|
||||
import AuthContext, {AuthContextType} from "./AuthContext"
|
||||
|
||||
@ -22,6 +30,7 @@ export interface AuthContextProviderProps {
|
||||
export default function AuthContextProvider({
|
||||
children,
|
||||
}: AuthContextProviderProps): ReactElement {
|
||||
const [masterPassword, setMasterPassword] = useState<string | null>(null)
|
||||
const [user, setUser] = useLocalStorage<User | null>(
|
||||
"_global-context-auth-user",
|
||||
null,
|
||||
@ -41,6 +50,28 @@ export default function AuthContextProvider({
|
||||
callback?.()
|
||||
}, [])
|
||||
|
||||
const encryptContent = useCallback(
|
||||
(content: string) => {
|
||||
if (!masterPassword) {
|
||||
throw new Error("Master password not set.")
|
||||
}
|
||||
|
||||
return encryptString(content, masterPassword)
|
||||
},
|
||||
[masterPassword],
|
||||
)
|
||||
|
||||
const decryptContent = useCallback(
|
||||
(content: string) => {
|
||||
if (!masterPassword) {
|
||||
throw new Error("Master password not set.")
|
||||
}
|
||||
|
||||
return decryptString(content, masterPassword)
|
||||
},
|
||||
[masterPassword],
|
||||
)
|
||||
|
||||
const {mutateAsync: refresh} = useMutation<
|
||||
RefreshTokenResult,
|
||||
AxiosError,
|
||||
@ -55,6 +86,9 @@ export default function AuthContextProvider({
|
||||
login,
|
||||
logout,
|
||||
isAuthenticated: user !== null,
|
||||
_setMasterPassword: setMasterPassword,
|
||||
_encryptContent: encryptContent,
|
||||
_decryptContent: decryptContent,
|
||||
}),
|
||||
[refresh, login, logout],
|
||||
)
|
||||
|
1
src/constants/values.ts
Normal file
1
src/constants/values.ts
Normal file
@ -0,0 +1 @@
|
||||
export const MASTER_PASSWORD_LENGTH = 4096
|
@ -3,15 +3,17 @@ import {useFormik} from "formik"
|
||||
import {MdCheckCircle, MdChevronRight, MdLock} from "react-icons/md"
|
||||
import {generateKey} from "openpgp"
|
||||
import React, {ReactElement, useMemo} from "react"
|
||||
import passwordGenerator from "secure-random-password"
|
||||
|
||||
import {LoadingButton} from "@mui/lab"
|
||||
import {Box, Grid, InputAdornment, Typography} from "@mui/material"
|
||||
import {useMutation} from "@tanstack/react-query"
|
||||
|
||||
import {PasswordField} from "~/components"
|
||||
import {encryptString} from "~/utils"
|
||||
import {buildEncryptionPassword, encryptString} from "~/utils"
|
||||
import {isDev} from "~/constants/development"
|
||||
import {useUser} from "~/hooks"
|
||||
import {useMutation} from "@tanstack/react-query"
|
||||
import {MASTER_PASSWORD_LENGTH} from "~/constants/values"
|
||||
|
||||
interface Form {
|
||||
password: string
|
||||
@ -29,7 +31,7 @@ const schema = yup.object().shape({
|
||||
|
||||
export default function PasswordForm(): ReactElement {
|
||||
const user = useUser()
|
||||
const {} = useMutation()
|
||||
const {} = useMutation<>()
|
||||
const awaitGenerateKey = useMemo(
|
||||
() =>
|
||||
generateKey({
|
||||
@ -52,12 +54,22 @@ export default function PasswordForm(): ReactElement {
|
||||
try {
|
||||
const keyPair = await awaitGenerateKey
|
||||
|
||||
const encryptedPrivateKey = encryptString(
|
||||
keyPair.privateKey,
|
||||
const masterPassword = passwordGenerator.randomPassword({
|
||||
length: MASTER_PASSWORD_LENGTH,
|
||||
})
|
||||
|
||||
const encryptionPassword = buildEncryptionPassword(
|
||||
values.password,
|
||||
user.email.address,
|
||||
)
|
||||
const encryptedMasterPassword = encryptString(
|
||||
masterPassword,
|
||||
`${values.password}-${user.email.address}`,
|
||||
)
|
||||
|
||||
console.log(encryptedPrivateKey)
|
||||
const encryptedPrivateKey = encryptString(
|
||||
keyPair.privateKey,
|
||||
masterPassword,
|
||||
)
|
||||
} catch (error) {
|
||||
setErrors({detail: "An error occurred"})
|
||||
}
|
||||
|
@ -22,6 +22,11 @@ export enum Language {
|
||||
EN_US = "en_US",
|
||||
}
|
||||
|
||||
export enum Theme {
|
||||
LIGHT = "light",
|
||||
DARK = "dark",
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: string
|
||||
createdAt: Date
|
||||
@ -72,3 +77,8 @@ export interface Alias {
|
||||
imageProxyFormat: ImageProxyFormatType
|
||||
imageProxyUserAgent: ProxyUserAgentType
|
||||
}
|
||||
|
||||
export interface UserNote {
|
||||
theme: Theme
|
||||
privateKey: string
|
||||
}
|
||||
|
6
src/utils/build-encryption-password.ts
Normal file
6
src/utils/build-encryption-password.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export default function buildEncryptionPassword(
|
||||
password: string,
|
||||
email: string,
|
||||
): string {
|
||||
return `${password}-blablabla-do-not-bruteforce-passwords-${email}`
|
||||
}
|
5
src/utils/decrypt-string.ts
Normal file
5
src/utils/decrypt-string.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import Crypto from "crypto-js"
|
||||
|
||||
export default function decryptString(value: string, password: string): string {
|
||||
return Crypto.AES.decrypt(value, password).toString(Crypto.enc.Utf8)
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
import Crypto from "crypto-js"
|
||||
|
||||
export default function encryptString(value: string, password: string): string {
|
||||
const key = `${password}-${process.env.NODE_PUBLIC_NEXT_PUBLIC_PUBLIC_ENCRYPTION_SAL}`
|
||||
|
||||
return Crypto.AES.encrypt(value, key).toString()
|
||||
return Crypto.AES.encrypt(value, password).toString()
|
||||
}
|
||||
|
42
src/utils/encrypt-user-note.ts
Normal file
42
src/utils/encrypt-user-note.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import * as yup from "yup"
|
||||
|
||||
import {Theme, UserNote} from "~/server-types"
|
||||
|
||||
import decryptString from "./decrypt-string"
|
||||
import encryptString from "./encrypt-string"
|
||||
|
||||
export const USER_NOTE_SCHEMA = yup.object().shape({
|
||||
privateKey: yup.string().required(),
|
||||
theme: yup.string().oneOf(Object.values(Theme)).required(),
|
||||
})
|
||||
|
||||
export function createUserNote(
|
||||
privateKey: string,
|
||||
theme: Theme = Theme.LIGHT,
|
||||
): UserNote {
|
||||
return {
|
||||
theme,
|
||||
privateKey,
|
||||
}
|
||||
}
|
||||
|
||||
export function decryptUserNote(
|
||||
encryptedUserNote: string,
|
||||
password: string,
|
||||
): UserNote {
|
||||
const data = decryptString(encryptedUserNote, password)
|
||||
const userNote = JSON.parse(data)
|
||||
|
||||
USER_NOTE_SCHEMA.validateSync(userNote)
|
||||
|
||||
return userNote
|
||||
}
|
||||
|
||||
export function encryptUserNote(userNote: UserNote, password: string): string {
|
||||
const data = JSON.stringify({
|
||||
...userNote,
|
||||
encryptionDate: new Date(),
|
||||
})
|
||||
|
||||
return encryptString(data, password)
|
||||
}
|
@ -6,3 +6,7 @@ export * from "./parse-fastapi-error"
|
||||
export {default as parseFastapiError} from "./parse-fastapi-error"
|
||||
export * from "./when-element-has-bounds"
|
||||
export {default as whenElementHasBounds} from "./when-element-has-bounds"
|
||||
export * from "./build-encryption-password"
|
||||
export {default as buildEncryptionPassword} from "./build-encryption-password"
|
||||
export * from "./decrypt-string"
|
||||
export {default as decryptString} from "./decrypt-string"
|
||||
|
Loading…
x
Reference in New Issue
Block a user