admin/accounts: show when an account is disabled because of its groups

Massimo Melina committed Dec 14, 2024 at 11:40 UTC c6f7786c1a9c8e38a457005c15c99d6cff0580a0
3 files changed +22 -8
admin/src/AccountForm.ts
+3 -2
@@ -2,7 +2,7 @@
2
3 import { createElement as h, ReactNode, useEffect, useRef, useState } from 'react'
4 import { BoolField, Form, MultiSelectField, NumberField } from '@hfs/mui-grid-form'
5 -import { Alert } from '@mui/material'
5 +import { Alert, Box } from '@mui/material'
6 import { apiCall } from './api'
7 import { alertDialog, useDialogBarColors } from './dialog'
8 import { formatTimestamp, isEqualLax, prefix, useIsMobile, wantArray } from './misc'
@@ -65,7 +65,8 @@ export default function AccountForm({ account, done, groups, addToBar, reload }:
65 !group && { k: 'password2', md: 6, lg: 4, type: 'password', autoComplete: 'new-password', label: 'Repeat password',
66 getError: (x, { values }) => (x||'') !== (values.password||'') && "Enter same password" },
67 { k: 'disabled', comp: BoolField, fromField: x=>!x, toField: x=>!x, label: "Enabled", xs: 12, sm: 6, lg: 8,
68 - helperText: "Login is prevented if account is disabled, or if all its groups are disabled"},
68 + helperText: !values.disabled && values.canLogin === false ? h(Box, { color: 'warning.main', component: 'span' }, "Login is prevented because all of its groups are disabled")
69 + : "Login is prevented if account is disabled, or all its groups are disabled" },
70 { k: 'ignore_limits', comp: BoolField, xs: true,
71 helperText: values.ignore_limits ? "Speed limits don't apply to this account" : "Speed limits apply to this account" },
72 { k: 'admin', comp: BoolField, fromField: (v:boolean) => v||null, label: "Admin-panel access", xs: 12, sm: 6, lg: 8,
admin/src/AccountsPage.ts
+14 -3
@@ -42,7 +42,7 @@ export default function AccountsPage() {
42 h(ListItem, { key: username },
43 h(ListItemText, {}, username))))
44 )
45 - : with_(selectedAccount || { username: '', hasPassword: sel === 'new-user', adminActualAccess: false, invalidated: undefined }, a =>
45 + : with_(selectedAccount || newAccount(), a =>
46 h(AccountForm, {
47 account: a,
48 groups: list.filter(x => !x.hasPassword).map( x => x.username ),
@@ -74,7 +74,7 @@ export default function AccountsPage() {
74
75 const scrollProps = { height: '100%', display: 'flex', flexDirection: 'column', overflow: 'auto' } as const
76 return element || h(Grid, { container: true, rowSpacing: 1, columnSpacing: 2, top: 0, flex: '1 1 auto', height: 0 },
77 - h(Grid, { item: true, xs: 12, [sideBreakpoint]: 5, lg: 4, xl: 5, ...scrollProps },
77 + h(Grid, { item: true, xs: 12, [sideBreakpoint]: 5, lg: 4, xl: 5, ...scrollProps },
78 h(Box, {
79 display: 'flex',
80 flexWrap: 'wrap',
@@ -124,7 +124,8 @@ export default function AccountsPage() {
124 }
125 },
126 account2icon(ac),
127 - ac.disabled && h(DoNotDisturb),
127 + (ac.disabled || ac.canLogin === false)
128 + && iconTooltip(DoNotDisturb, ac.disabled ? "Disabled" : "Disabled by its groups", ac.disabled ? undefined : { color: 'text.secondary' }),
129 (ac.expire || ac.days_to_live) && h(Schedule),
130 ac.adminActualAccess && iconTooltip(MilitaryTech, "Can login into Admin"),
131 ac.username,
@@ -140,6 +141,16 @@ export default function AccountsPage() {
141 h(CardContent, {}, sideContent)) )
142 )
143
144 + function newAccount() {
145 + return {
146 + username: '',
147 + hasPassword: sel === 'new-user',
148 + adminActualAccess: false,
149 + invalidated: undefined,
150 + canLogin: true
151 + } satisfies Account
152 + }
153 +
154 function selectNone() {
155 setSel([])
156 }
src/api.accounts.ts
+5 -3
@@ -1,20 +1,22 @@
1 // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 import { ApiError, ApiHandlers } from './apiMiddleware'
4 -import { Account, accountCanLoginAdmin, accountHasPassword, accountsConfig, addAccount, delAccount, getAccount,
5 - changeSrpHelper, updateAccount } from './perm'
4 +import {
5 + Account, accountCanLoginAdmin, accountHasPassword, accountsConfig, addAccount, delAccount, getAccount,
6 + changeSrpHelper, updateAccount, accountCanLogin
7 +} from './perm'
8 import _ from 'lodash'
9 import { HTTP_BAD_REQUEST, HTTP_CONFLICT, HTTP_NOT_FOUND } from './const'
10 import { getCurrentUsername, invalidateSessionBefore } from './auth'
11 import { apiAssertTypes, onlyTruthy } from './misc'
12
11 -export type AccountAdminSend = NonNullable<ReturnType<typeof prepareAccount>>
13 function prepareAccount(ac: Account | undefined) {
14 return ac && {
15 ..._.omit(ac, ['password','hashed_password','srp']),
16 username: ac.username, // omit won't copy it because it's a hidden prop
17 hasPassword: accountHasPassword(ac),
18 adminActualAccess: accountCanLoginAdmin(ac),
19 + canLogin: accountHasPassword(ac) ? accountCanLogin(ac) : undefined,
20 invalidated: invalidateSessionBefore.get(ac.username),
21 }
22 }