Skip to content

Commit

Permalink
feat: encrypted backup and restore (#140)
Browse files Browse the repository at this point in the history
* feat: encrypted backup and restore

* fix: non-encrypted backup can't be restored

* fix: compatible with backups in previous versions

* fix: fix codefactor
  • Loading branch information
itsHenry35 committed Feb 1, 2024
1 parent 8440101 commit 6e396fc
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 10 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -67,6 +67,7 @@
"axios": "^1.0.0",
"clsx": "^2.0.0",
"copy-to-clipboard": "^3.3.2",
"crypto-js": "^4.2.0",
"flv.js": "^1.6.2",
"hls.js": "^1.2.1",
"just-once": "^2.2.0",
Expand Down
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion src/lang/en/br.json
Expand Up @@ -10,5 +10,8 @@
"finish_restore": "Finish restore",
"success_restore_item": "[ {{item}} ] Restore was successful",
"failed_restore_item": "[ {{item}} ] Restore failed",
"override": "Override"
"override": "Override",
"encrypt_password_placeholder": "Leave empty if you don't need encryption",
"encrypt_password": "Encryption password",
"wrong_encrypt_password": "Wrong encryption password"
}
89 changes: 80 additions & 9 deletions src/pages/manage/backup-restore.tsx
Expand Up @@ -4,6 +4,10 @@ import {
VStack,
Text,
Switch as HopeSwitch,
Input,
FormControl,
FormLabel,
Flex,
} from "@hope-ui/solid"
import { r, handleRespWithoutNotify, notify } from "~/utils"
import { useFetch, useManageTitle, useT } from "~/hooks"
Expand All @@ -18,8 +22,10 @@ import {
PPageResp,
} from "~/types"
import { createSignal, For } from "solid-js"
import crypto from "crypto-js"

interface Data {
encrypted: string
settings: SettingItem[]
users: User[]
storages: Storage[]
Expand Down Expand Up @@ -51,6 +57,7 @@ const Log = (props: { msg: string; type: LogType }) => {

const BackupRestore = () => {
const [override, setOverride] = createSignal(false)
const [password, setPassword] = createSignal("")
const t = useT()
useManageTitle("manage.sidemenu.backup-restore")
let logRef: HTMLDivElement
Expand Down Expand Up @@ -84,14 +91,36 @@ const BackupRestore = () => {
getStoragesLoading()
)
}
function encrypt(data: any, key: string): string {
if (key == "") return data
const encJson = crypto.AES.encrypt(JSON.stringify(data), key).toString()
return crypto.enc.Base64.stringify(crypto.enc.Utf8.parse(encJson))
}

function decrypt(
data: any,
key: string,
raw: boolean,
encrypted: boolean,
): string {
if (!encrypted) return data
const decData = crypto.enc.Base64.parse(data).toString(crypto.enc.Utf8)
if (raw) return crypto.AES.decrypt(decData, key).toString(crypto.enc.Utf8)
return JSON.parse(
crypto.AES.decrypt(decData, key).toString(crypto.enc.Utf8),
)
}

const backup = async () => {
appendLog(t("br.start_backup"), "info")
const allData: Data = {
encrypted: "",
settings: [],
users: [],
storages: [],
metas: [],
}
if (password() != "") allData.encrypted = encrypt("encrypted", password())
for (const item of [
{ name: "settings", fn: getSettings, page: false },
{ name: "users", fn: getUsers, page: true },
Expand All @@ -109,8 +138,20 @@ const BackupRestore = () => {
"success",
)
if (item.page) {
for (let i = 0; i < data.content.length; i++) {
const obj = data.content[i]
for (const key in obj) {
obj[key] = encrypt(obj[key], password())
}
}
allData[item.name] = data.content
} else {
for (let i = 0; i < data.length; i++) {
const obj = data[i]
for (const key in obj) {
obj[key] = encrypt(obj[key], password())
}
}
allData[item.name] = data
}
},
Expand Down Expand Up @@ -228,6 +269,25 @@ const BackupRestore = () => {
const reader = new FileReader()
reader.onload = async () => {
const data: Data = JSON.parse(reader.result as string)
const encrypted = Boolean(data.encrypted)
if (encrypted)
if (
decrypt(data.encrypted, password(), true, true) !== '"encrypted"'
) {
appendLog(t("br.wrong_encrypt_password"), "error")
return
}
const dataasarray = Object.values(data)
for (let i = dataasarray.length - 4; i < dataasarray.length; i++) {
const obj = dataasarray[i]
console.log(obj)
for (let a = 0; a < obj.length; a++) {
const obj1 = obj[a]
for (const key in obj1) {
obj1[key] = decrypt(obj1[key], password(), false, encrypted)
}
}
}
if (override()) {
await backup()
}
Expand Down Expand Up @@ -348,16 +408,27 @@ const BackupRestore = () => {
>
{t("br.restore")}
</Button>
<HopeSwitch
id="restore-override"
checked={override()}
onChange={(e: { currentTarget: HTMLInputElement }) =>
setOverride(e.currentTarget.checked)
}
>
{t("br.override")}
</HopeSwitch>
</HStack>
<FormControl w="$full" display="flex" flexDirection="column">
<Flex w="$full" direction="column" gap="$1">
<FormLabel>{t(`br.override`)}</FormLabel>
<HopeSwitch
id="restore-override"
checked={override()}
onChange={(e: { currentTarget: HTMLInputElement }) =>
setOverride(e.currentTarget.checked)
}
></HopeSwitch>

<FormLabel>{t(`br.encrypt_password`)}</FormLabel>
<Input
id="password"
type="password"
placeholder={t(`br.encrypt_password_placeholder`)}
onInput={(e) => setPassword(e.currentTarget.value)}
/>
</Flex>
</FormControl>
<VStack
p="$2"
ref={logRef!}
Expand Down

0 comments on commit 6e396fc

Please sign in to comment.