Skip to content

Commit 6e396fc

Browse files
authored
feat: encrypted backup and restore (#140)
* feat: encrypted backup and restore * fix: non-encrypted backup can't be restored * fix: compatible with backups in previous versions * fix: fix codefactor
1 parent 8440101 commit 6e396fc

File tree

4 files changed

+92
-10
lines changed

4 files changed

+92
-10
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"axios": "^1.0.0",
6868
"clsx": "^2.0.0",
6969
"copy-to-clipboard": "^3.3.2",
70+
"crypto-js": "^4.2.0",
7071
"flv.js": "^1.6.2",
7172
"hls.js": "^1.2.1",
7273
"just-once": "^2.2.0",

pnpm-lock.yaml

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lang/en/br.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,8 @@
1010
"finish_restore": "Finish restore",
1111
"success_restore_item": "[ {{item}} ] Restore was successful",
1212
"failed_restore_item": "[ {{item}} ] Restore failed",
13-
"override": "Override"
13+
"override": "Override",
14+
"encrypt_password_placeholder": "Leave empty if you don't need encryption",
15+
"encrypt_password": "Encryption password",
16+
"wrong_encrypt_password": "Wrong encryption password"
1417
}

src/pages/manage/backup-restore.tsx

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import {
44
VStack,
55
Text,
66
Switch as HopeSwitch,
7+
Input,
8+
FormControl,
9+
FormLabel,
10+
Flex,
711
} from "@hope-ui/solid"
812
import { r, handleRespWithoutNotify, notify } from "~/utils"
913
import { useFetch, useManageTitle, useT } from "~/hooks"
@@ -18,8 +22,10 @@ import {
1822
PPageResp,
1923
} from "~/types"
2024
import { createSignal, For } from "solid-js"
25+
import crypto from "crypto-js"
2126

2227
interface Data {
28+
encrypted: string
2329
settings: SettingItem[]
2430
users: User[]
2531
storages: Storage[]
@@ -51,6 +57,7 @@ const Log = (props: { msg: string; type: LogType }) => {
5157

5258
const BackupRestore = () => {
5359
const [override, setOverride] = createSignal(false)
60+
const [password, setPassword] = createSignal("")
5461
const t = useT()
5562
useManageTitle("manage.sidemenu.backup-restore")
5663
let logRef: HTMLDivElement
@@ -84,14 +91,36 @@ const BackupRestore = () => {
8491
getStoragesLoading()
8592
)
8693
}
94+
function encrypt(data: any, key: string): string {
95+
if (key == "") return data
96+
const encJson = crypto.AES.encrypt(JSON.stringify(data), key).toString()
97+
return crypto.enc.Base64.stringify(crypto.enc.Utf8.parse(encJson))
98+
}
99+
100+
function decrypt(
101+
data: any,
102+
key: string,
103+
raw: boolean,
104+
encrypted: boolean,
105+
): string {
106+
if (!encrypted) return data
107+
const decData = crypto.enc.Base64.parse(data).toString(crypto.enc.Utf8)
108+
if (raw) return crypto.AES.decrypt(decData, key).toString(crypto.enc.Utf8)
109+
return JSON.parse(
110+
crypto.AES.decrypt(decData, key).toString(crypto.enc.Utf8),
111+
)
112+
}
113+
87114
const backup = async () => {
88115
appendLog(t("br.start_backup"), "info")
89116
const allData: Data = {
117+
encrypted: "",
90118
settings: [],
91119
users: [],
92120
storages: [],
93121
metas: [],
94122
}
123+
if (password() != "") allData.encrypted = encrypt("encrypted", password())
95124
for (const item of [
96125
{ name: "settings", fn: getSettings, page: false },
97126
{ name: "users", fn: getUsers, page: true },
@@ -109,8 +138,20 @@ const BackupRestore = () => {
109138
"success",
110139
)
111140
if (item.page) {
141+
for (let i = 0; i < data.content.length; i++) {
142+
const obj = data.content[i]
143+
for (const key in obj) {
144+
obj[key] = encrypt(obj[key], password())
145+
}
146+
}
112147
allData[item.name] = data.content
113148
} else {
149+
for (let i = 0; i < data.length; i++) {
150+
const obj = data[i]
151+
for (const key in obj) {
152+
obj[key] = encrypt(obj[key], password())
153+
}
154+
}
114155
allData[item.name] = data
115156
}
116157
},
@@ -228,6 +269,25 @@ const BackupRestore = () => {
228269
const reader = new FileReader()
229270
reader.onload = async () => {
230271
const data: Data = JSON.parse(reader.result as string)
272+
const encrypted = Boolean(data.encrypted)
273+
if (encrypted)
274+
if (
275+
decrypt(data.encrypted, password(), true, true) !== '"encrypted"'
276+
) {
277+
appendLog(t("br.wrong_encrypt_password"), "error")
278+
return
279+
}
280+
const dataasarray = Object.values(data)
281+
for (let i = dataasarray.length - 4; i < dataasarray.length; i++) {
282+
const obj = dataasarray[i]
283+
console.log(obj)
284+
for (let a = 0; a < obj.length; a++) {
285+
const obj1 = obj[a]
286+
for (const key in obj1) {
287+
obj1[key] = decrypt(obj1[key], password(), false, encrypted)
288+
}
289+
}
290+
}
231291
if (override()) {
232292
await backup()
233293
}
@@ -348,16 +408,27 @@ const BackupRestore = () => {
348408
>
349409
{t("br.restore")}
350410
</Button>
351-
<HopeSwitch
352-
id="restore-override"
353-
checked={override()}
354-
onChange={(e: { currentTarget: HTMLInputElement }) =>
355-
setOverride(e.currentTarget.checked)
356-
}
357-
>
358-
{t("br.override")}
359-
</HopeSwitch>
360411
</HStack>
412+
<FormControl w="$full" display="flex" flexDirection="column">
413+
<Flex w="$full" direction="column" gap="$1">
414+
<FormLabel>{t(`br.override`)}</FormLabel>
415+
<HopeSwitch
416+
id="restore-override"
417+
checked={override()}
418+
onChange={(e: { currentTarget: HTMLInputElement }) =>
419+
setOverride(e.currentTarget.checked)
420+
}
421+
></HopeSwitch>
422+
423+
<FormLabel>{t(`br.encrypt_password`)}</FormLabel>
424+
<Input
425+
id="password"
426+
type="password"
427+
placeholder={t(`br.encrypt_password_placeholder`)}
428+
onInput={(e) => setPassword(e.currentTarget.value)}
429+
/>
430+
</Flex>
431+
</FormControl>
361432
<VStack
362433
p="$2"
363434
ref={logRef!}

0 commit comments

Comments
 (0)