admin/config: prevent disabling admin_localhost if you didn't create an admin account

Massimo Melina committed Apr 13, 2022 at 18:30 UTC 582dee65206ca194f061beccc83bc61ec91a768c
3 files changed +16 -7
admin/src/ConfigPage.ts
+9 -4
@@ -2,7 +2,7 @@
2
3 import { Box, Button, FormHelperText, Link } from '@mui/material';
4 import { createElement as h, isValidElement, useEffect, useRef } from 'react';
5 -import { apiCall, useApiComp } from './api'
5 +import { apiCall, useApi, useApiComp } from './api'
6 import { state, useSnapState } from './state'
7 import { Info, Refresh } from '@mui/icons-material'
8 import { Dict, modifiedSx } from './misc'
@@ -32,6 +32,8 @@ export default function ConfigPage() {
32 exposedReloadStatus = reloadStatus
33 useEffect(() => () => exposedReloadStatus = undefined, []) // clear on unmount
34
35 + const admins = useApi('get_admins')[0]?.list
36 +
37 if (isValidElement(res))
38 return res
39 if (isValidElement(status))
@@ -88,8 +90,11 @@ export default function ConfigPage() {
90 helperText: "To avoid an endlessly-growing single log file, you can opt for rotation"
91 },
92 { k: 'open_browser_at_start', comp: BoolField },
91 - { k: 'localhost_admin', comp: BoolField, label: "Admin access for localhost connections", helperText: "To access Admin without entering credentials" },
92 - { k: 'proxies', comp: NumberField, min: 0, max: 9, sm: 6, lg: 6, label: "How many proxies between this server and users?",
93 + { k: 'localhost_admin', comp: BoolField, label: "Admin access for localhost connections",
94 + validate: x => x || admins?.length>0 || "First create at least one admin account",
95 + helperText: "To access Admin without entering credentials"
96 + },
97 + { k: 'proxies', comp: NumberField, min: 0, max: 9, sm: 6, lg: 6, label: "How many HTTP proxies between this server and users?",
98 error: proxyWarning(values, status),
99 helperText: "Wrong number will prevent detection of users' IP address"
100 },
@@ -167,7 +172,7 @@ function ServerPort({ label, value, onChange, status, suggestedPort=1 }: FieldPr
172 ],
173 onChange,
174 }),
170 - value! > 0 && h(NumberField, { label: 'Number', fullWidth: false, value, onChange }),
175 + value! > 0 && h(NumberField, { label: 'Number', fullWidth: false, value, onChange, min: 1, max: 65535 }),
176 ),
177 status && h(FormHelperText, { error: Boolean(error) },
178 status === true ? '...'
admin/src/Form.ts
+3 -3
@@ -315,7 +315,7 @@ export function NumberField({ value, onChange, min, max, step, ...props }: Field
315 })
316 }
317
318 -export function BoolField({ label='', value, onChange, helperText, fromField=_.identity, toField=_.identity, ...props }: FieldProps<boolean>) {
318 +export function BoolField({ label='', value, onChange, helperText, error, fromField=_.identity, toField=_.identity, ...props }: FieldProps<boolean>) {
319 const setter = () => toField(value) ?? false
320 const [state, setState] = useState(setter)
321 useEffect(() => setState(setter),
@@ -327,9 +327,9 @@ export function BoolField({ label='', value, onChange, helperText, fromField=_.i
327 onChange(fromField(event.target.checked), { event, was: value })
328 }
329 })
330 - return h(Box, { ml: 1, mt: 1 },
330 + return h(Box, { ml: 1, mt: 1, sx: error && { color: 'error.main', outlineOffset: 6, outline: '1px solid' } },
331 h(FormControlLabel, { label, control, labelPlacement: 'end' }),
332 - helperText && h(FormHelperText,{},helperText)
332 + helperText && h(FormHelperText, { error }, helperText)
333 )
334 }
335
server/src/api.accounts.ts
+4
@@ -40,6 +40,10 @@ const apis: ApiHandlers = {
40 return { list: Object.values(getAccounts()).map(prepareAccount) }
41 },
42
43 + get_admins() {
44 + return { list: Object.values(getAccounts()).map(prepareAccount).filter(ac => ac?.adminActualAccess).map(ac => ac!.username) }
45 + },
46 +
47 set_account({ username, changes }) {
48 const { admin } = changes
49 if (admin === null)