better code: split files
Massimo Melina committed
Jun 5, 2022 at 13:51 UTC
61701113c52a45b19cda6343b3ff373ac1e75b0d
3 files changed
+96
-90
admin/src/AccountForm.ts
new
+92
@@ -0,0 +1,92 @@
1
+import { createElement as h, useEffect, useRef, useState } from 'react'
2
+import { BoolField, Form, MultiSelectField } from '@hfs/mui-grid-form'
3
+import { Box, Button } from '@mui/material'
4
+import { apiCall } from './api'
5
+import { alertDialog } from './dialog'
6
+import { isEqualLax, modifiedSx } from './misc'
7
+import { Account, account2icon } from './AccountsPage'
8
+import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a'
9
+
10
+interface FormProps { account: Account, groups: string[], done: (username: string)=>void, close: ()=>void }
11
+export default function AccountForm({ account, done, groups, close }: FormProps) {
12
+ const [values, setValues] = useState<Account & { password?: string, password2?: string }>(account)
13
+ const [belongsOptions, setBelongOptions] = useState<string[]>([])
14
+ useEffect(() => {
15
+ setValues(account)
16
+ setBelongOptions(groups.filter(x => x !== account.username ))
17
+ ref.current?.querySelector('input')?.focus()
18
+ }, [JSON.stringify(account)]) //eslint-disable-line
19
+ const add = !account.username
20
+ const group = !values.hasPassword
21
+ const ref = useRef<HTMLFormElement>()
22
+ return h(Form, {
23
+ formRef: ref,
24
+ values,
25
+ set(v, k) {
26
+ setValues({ ...values, [k]: v })
27
+ },
28
+ addToBar: [
29
+ h(Button, { onClick: close, sx: { ml: 2 } }, "Close"),
30
+ h(Box, { flex:1 }),
31
+ account2icon(values, { fontSize: 'large', sx: { p: 1 }})
32
+ ],
33
+ fields: [
34
+ { k: 'username', label: group ? 'Group name' : undefined, autoComplete: 'off', required: true, xl: group ? 12 : 4,
35
+ getError: v => v !== account.username && apiCall('get_account', { username: v }).then(() => "already used", () => false),
36
+ },
37
+ !group && { k: 'password', md: 6, xl: 4, type: 'password', autoComplete: 'new-password', required: add,
38
+ label: add ? "Password" : "Change password"
39
+ },
40
+ !group && { k: 'password2', md: 6, xl: 4, type: 'password', autoComplete: 'new-password', label: 'Repeat password',
41
+ getError: (x, { values }) => (x||'') !== (values.password||'') && "Enter same password" },
42
+ { k: 'ignore_limits', comp: BoolField, xl: 6,
43
+ helperText: values.ignore_limits ? "Speed limits don't apply to this account" : "Speed limits apply to this account" },
44
+ { k: 'admin', comp: BoolField, xl: 6, fromField: (v:boolean) => v||null, label: "Permission to access Admin interface",
45
+ helperText: "It's THIS interface you are using right now.",
46
+ ...account.adminActualAccess && { value: true, disabled: true, helperText: "This permission is inherited" },
47
+ },
48
+ { k: 'belongs', comp: MultiSelectField, label: "Inherits from", options: belongsOptions,
49
+ helperText: "Specify groups to inherit permissions from."
50
+ + (belongsOptions.length ? '' : " There are no groups available, create one first.")
51
+ },
52
+ { k: 'redirect', helperText: "If you want this account to be redirected to a specific folder/address at login time" },
53
+ ],
54
+ onError: alertDialog,
55
+ save: {
56
+ sx: modifiedSx( !isEqualLax(values, account)),
57
+ async onClick() {
58
+ const { password='', password2, adminActualAccess, ...withoutPassword } = values
59
+ const { username } = values
60
+ if (add) {
61
+ await apiCall('add_account', withoutPassword)
62
+ if (password)
63
+ try { await apiNewPassword(username, password) }
64
+ catch(e) {
65
+ apiCall('del_account', { username }).then() // best effort, don't wait
66
+ throw e
67
+ }
68
+ done(username)
69
+ return alertDialog("Account created", 'success')
70
+ }
71
+ await apiCall('set_account', {
72
+ username: account.username,
73
+ changes: withoutPassword,
74
+ })
75
+ if (password)
76
+ await apiNewPassword(username, password)
77
+ done(username)
78
+ return alertDialog("Account modified", 'success')
79
+ }
80
+ }
81
+ })
82
+}
83
+
84
+async function apiNewPassword(username: string, password: string) {
85
+ const srp6aNimbusRoutines = new SRPRoutines(new SRPParameters())
86
+ const res = await createVerifierAndSalt(srp6aNimbusRoutines, username, password)
87
+ return apiCall('change_srp_others', { username, salt: String(res.s), verifier: String(res.v) }).catch(e => {
88
+ if (e.code !== 406) // 406 = server was configured to support clear text authentication
89
+ throw e
90
+ return apiCall('change_password_others', { username, newPassword: password }) // unencrypted version
91
+ })
92
+}
admin/src/AccountsPage.ts
+4
-89
@@ -1,16 +1,15 @@
1
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
-import { createElement as h, useState, useEffect, Fragment, useRef } from "react"
3
+import { createElement as h, useState, useEffect, Fragment } from "react"
4
import { apiCall, useApiEx } from './api'
5
import { Box, Button, Card, CardContent, Grid, List, ListItem, ListItemText, Typography } from '@mui/material'
6
import { Delete, Group, MilitaryTech, Person, PersonAdd, Refresh } from '@mui/icons-material'
7
-import { BoolField, Form, MultiSelectField } from '@hfs/mui-grid-form'
7
import { alertDialog, confirmDialog } from './dialog'
9
-import { iconTooltip, isEqualLax, modifiedSx, onlyTruthy } from './misc'
8
+import { iconTooltip, onlyTruthy } from './misc'
9
import { TreeItem, TreeView } from '@mui/lab'
10
import { makeStyles } from '@mui/styles'
12
-import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a'
11
import MenuButton from './MenuButton'
12
+import AccountForm from './AccountForm'
13
14
const useStyles = makeStyles({
15
label: {
@@ -141,90 +140,6 @@ function hList(heading: string, list: any[]) {
140
)
141
}
142
144
-interface FormProps { account: Account, groups: string[], done: (username: string)=>void, close: ()=>void }
145
-function AccountForm({ account, done, groups, close }: FormProps) {
146
- const [values, setValues] = useState<Account & { password?: string, password2?: string }>(account)
147
- const [belongsOptions, setBelongOptions] = useState<string[]>([])
148
- useEffect(() => {
149
- setValues(account)
150
- setBelongOptions(groups.filter(x => x !== account.username ))
151
- ref.current?.querySelector('input')?.focus()
152
- }, [JSON.stringify(account)]) //eslint-disable-line
153
- const add = !account.username
154
- const group = !values.hasPassword
155
- const ref = useRef<HTMLFormElement>()
156
- return h(Form, {
157
- formRef: ref,
158
- values,
159
- set(v, k) {
160
- setValues({ ...values, [k]: v })
161
- },
162
- addToBar: [
163
- h(Button, { onClick: close, sx: { ml: 2 } }, "Close"),
164
- h(Box, { flex:1 }),
165
- account2icon(values, { fontSize: 'large', sx: { p: 1 }})
166
- ],
167
- fields: [
168
- { k: 'username', label: group ? 'Group name' : undefined, autoComplete: 'off', required: true, xl: group ? 12 : 4,
169
- getError: v => v !== account.username && apiCall('get_account', { username: v }).then(() => "already used", () => false),
170
- },
171
- !group && { k: 'password', md: 6, xl: 4, type: 'password', autoComplete: 'new-password', required: add,
172
- label: add ? "Password" : "Change password"
173
- },
174
- !group && { k: 'password2', md: 6, xl: 4, type: 'password', autoComplete: 'new-password', label: 'Repeat password',
175
- getError: (x, { values }) => (x||'') !== (values.password||'') && "Enter same password" },
176
- { k: 'ignore_limits', comp: BoolField, xl: 6,
177
- helperText: values.ignore_limits ? "Speed limits don't apply to this account" : "Speed limits apply to this account" },
178
- { k: 'admin', comp: BoolField, xl: 6, fromField: (v:boolean) => v||null, label: "Permission to access Admin interface",
179
- helperText: "It's THIS interface you are using right now.",
180
- ...account.adminActualAccess && { value: true, disabled: true, helperText: "This permission is inherited" },
181
- },
182
- { k: 'belongs', comp: MultiSelectField, label: "Inherits from", options: belongsOptions,
183
- helperText: "Specify groups to inherit permissions from."
184
- + (belongsOptions.length ? '' : " There are no groups available, create one first.")
185
- },
186
- { k: 'redirect', helperText: "If you want this account to be redirected to a specific folder/address at login time" },
187
- ],
188
- onError: alertDialog,
189
- save: {
190
- sx: modifiedSx( !isEqualLax(values, account)),
191
- async onClick() {
192
- const { password='', password2, adminActualAccess, ...withoutPassword } = values
193
- const { username } = values
194
- if (add) {
195
- await apiCall('add_account', withoutPassword)
196
- if (password)
197
- try { await apiNewPassword(username, password) }
198
- catch(e) {
199
- apiCall('del_account', { username }).then() // best effort, don't wait
200
- throw e
201
- }
202
- done(username)
203
- return alertDialog("Account created", 'success')
204
- }
205
- await apiCall('set_account', {
206
- username: account.username,
207
- changes: withoutPassword,
208
- })
209
- if (password)
210
- await apiNewPassword(username, password)
211
- done(username)
212
- return alertDialog("Account modified", 'success')
213
- }
214
- }
215
- })
216
-}
217
-
218
-function account2icon(ac: Account, props={}) {
143
+export function account2icon(ac: Account, props={}) {
144
return h(ac.hasPassword ? Person : Group, props)
145
}
221
-
222
-async function apiNewPassword(username: string, password: string) {
223
- const srp6aNimbusRoutines = new SRPRoutines(new SRPParameters())
224
- const res = await createVerifierAndSalt(srp6aNimbusRoutines, username, password)
225
- return apiCall('change_srp_others', { username, salt: String(res.s), verifier: String(res.v) }).catch(e => {
226
- if (e.code !== 406) // 406 = server was configured to support clear text authentication
227
- throw e
228
- return apiCall('change_password_others', { username, newPassword: password }) // unencrypted version
229
- })
230
-}
todo.md
-1
@@ -5,7 +5,6 @@
5
- easier deploy on cloud server
6
- admin/fs: sort items
7
- plugin.api.subscribeConfig
8
-- watch certificates for change
8
- admin/fs: render virtual folders differently
9
- admin/config: hide advanced settings
10
- admin/fs: drag&drop to move items around