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 }) {