ux: admin/home: show info also if logged in but without admin permission (localhost)
Massimo Melina committed
Mar 30, 2022 at 17:54 UTC
8fd0d490f32e81052bde5487290e9f6f36409969
3 files changed
+27
-17
admin/src/HomePage.ts
+8
-5
@@ -11,9 +11,11 @@ import { useSnapState } from './state'
11
interface ServerStatus { listening: boolean, port: number, error?: string, busy?: string }
12
13
export default function HomePage() {
14
+ const SOLUTION_SEP = " — "
15
const { username } = useSnapState()
16
const [status] = useApi<Dict<ServerStatus>>('get_status')
17
const [vfs] = useApi('get_vfs')
18
+ const [account] = useApi(username && 'get_account')
19
const [cfg] = useApi('get_config', { only: ['https_port', 'cert', 'private_key'] })
20
if (!status)
21
return spinner()
@@ -22,17 +24,17 @@ export default function HomePage() {
24
const srv = goSecure ? https : (http?.listening && http)
25
const href = srv && `http${goSecure}://`+window.location.hostname + (srv.port === (goSecure ? 443 : 80) ? '' : ':'+srv.port)
26
const errorMap = objSameKeys(status, v =>
25
- v.busy ? [`port ${v.port} already used by ${v.busy} — choose a `, cfgLink('different port'), ` or stop ${v.busy}`]
27
+ v.busy ? [`port ${v.port} already used by ${v.busy}${SOLUTION_SEP}choose a `, cfgLink('different port'), ` or stop ${v.busy}`]
28
: v.error )
29
const errors = errorMap && onlyTruthy(Object.entries(errorMap).map(([k,v]) =>
28
- v && [md(`Protocol _${k}_ cannot work: `), v, typeof v === 'string' && /certificate|key/.test(v) && [' — ', cfgLink("provide adequate files")]]))
30
+ v && [md(`Protocol _${k}_ cannot work: `), v, typeof v === 'string' && /certificate|key/.test(v) && [SOLUTION_SEP, cfgLink("provide adequate files")]]))
31
return h(Box, { display:'flex', gap: 2, flexDirection:'column' },
32
username && entry('', "Welcome "+username),
33
!cfg ? spinner() :
34
errors.length ? dontBotherWithKeys(errors.map(msg => entry('error', dontBotherWithKeys(msg))))
35
: entry('success', "Server is working"),
36
!vfs?.root?.children?.length && !vfs?.root?.source
35
- ? entry('warning', "You have no files shared — ", fsLink("add some"))
37
+ ? entry('warning', "You have no files shared", SOLUTION_SEP, fsLink("add some"))
38
: entry('', "Here you manage your server. There is a SEPARATED interface to access your shared files: ",
39
h(Link, { target:'frontend', href: '/' }, "Frontend interface", h(Launch, { sx: { verticalAlign: 'sub', ml: '.2em' } }))),
40
! href && entry('warning', "Frontend unreachable: ",
@@ -40,10 +42,11 @@ export default function HomePage() {
42
: errors.length === 2 ? "both http and https are in error"
43
: h(Fragment, {},
44
['http','https'].map(k => k + " " + (errorMap[k] ? "is in error" : "is off")).join(', '),
43
- !errors.length && h(Fragment, {}, ' — ', cfgLink("switch http or https on"))
45
+ !errors.length && h(Fragment, {}, SOLUTION_SEP, cfgLink("switch http or https on"))
46
)
47
),
46
- !username && entry('', "You are accessing on localhost without an account — ", h(InLink, { to:'accounts' }, "give admin access to an account to be able to access from other computers")),
48
+ !account?.adminActualAccess && entry('', "You are accessing on localhost, therefore permission is not required",
49
+ SOLUTION_SEP, h(InLink, { to:'accounts' }, "give admin access to an account to be able to access from other computers") ),
50
)
51
}
52
admin/src/LoginRequired.ts
+2
-3
@@ -36,8 +36,6 @@ function LoginForm() {
36
try {
37
setError('')
38
await login(username, password)
39
- state.loginRequired = false
40
- state.username = username
39
}
40
catch(e) {
41
setError(String(e))
@@ -67,7 +65,8 @@ async function login(username: string, password: string) {
65
throw "This account has no Admin access"
66
67
// login was successful, update state
70
- sessionRefresher({ username, exp:res.exp })
68
+ state.loginRequired = false
69
+ sessionRefresher(res)
70
}
71
72
// @ts-ignore
server/src/api.accounts.ts
+17
-9
@@ -3,33 +3,41 @@
3
import { changePasswordHelper, changeSrpHelper } from './api.helpers'
4
import { ApiError, ApiHandlers } from './apiMiddleware'
5
import {
6
+ Account,
7
accountCanLogin,
8
accountHasPassword,
9
addAccount,
10
delAccount,
11
getAccount,
11
- getAccounts,
12
+ getAccounts, getCurrentUsername,
13
getFromAccount,
14
setAccount
15
} from './perm'
16
import _ from 'lodash'
17
import { FORBIDDEN } from './const'
18
19
+function prepareAccount(ac: Account | undefined) {
20
+ return ac && {
21
+ ..._.omit(ac, ['password','hashed_password','srp']),
22
+ username: ac.username, // omit won't copy it because it's a hidden prop
23
+ hasPassword: accountHasPassword(ac),
24
+ adminActualAccess: accountCanLogin(ac) && getFromAccount(ac, a => a.admin),
25
+ }
26
+}
27
+
28
const apis: ApiHandlers = {
29
30
get_usernames() {
31
return { list: Object.keys(getAccounts()) }
32
},
33
34
+ get_account({ username }, ctx) {
35
+ return prepareAccount(getAccount(username || getCurrentUsername(ctx)))
36
+ || new ApiError(404)
37
+ },
38
+
39
get_accounts() {
25
- return {
26
- list: Object.values(getAccounts()).map(ac => ({
27
- ..._.omit(ac, ['password','hashed_password','srp']),
28
- username: ac.username, // omit won't copy it because it's a hidden prop
29
- hasPassword: accountHasPassword(ac),
30
- adminActualAccess: accountCanLogin(ac) && getFromAccount(ac, a => a.admin),
31
- }))
32
- }
40
+ return { list: Object.values(getAccounts()).map(prepareAccount) }
41
},
42
43
set_account({ username, changes }) {