admin/accounts: days to live
Massimo Melina committed
Jan 27, 2024 at 00:02 UTC
bcc86f1d93fd69eecce9e44f2ffa506a59ba6e63
4 files changed
+20
-11
admin/src/AccountForm.ts
+6
-2
@@ -1,7 +1,7 @@
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 { createElement as h, ReactNode, useEffect, useRef, useState } from 'react'
4
-import { BoolField, Form, MultiSelectField } from '@hfs/mui-grid-form'
4
+import { BoolField, Form, MultiSelectField, NumberField } from '@hfs/mui-grid-form'
5
import { Alert } from '@mui/material'
6
import { apiCall } from './api'
7
import { alertDialog, toast, useDialogBarColors } from './dialog'
@@ -29,6 +29,7 @@ export default function AccountForm({ account, done, groups, addToBar, reload }:
29
const add = !account.username
30
const group = !values.hasPassword
31
const ref = useRef<HTMLFormElement>()
32
+ const expired = Boolean(values.expire)
33
return h(Form, {
34
formRef: ref,
35
values,
@@ -79,7 +80,10 @@ export default function AccountForm({ account, done, groups, addToBar, reload }:
80
+ (!group ? '' : ". A group can inherit from another group")
81
+ (belongsOptions.length ? '' : ". Now disabled because there are no groups to select, create one first.")
82
},
82
- { k: 'expire', label: "Expiration", comp: DateTimeField, toField: x => x && new Date(x) },
83
+ { k: 'expire', label: "Expiration", xs: true, comp: DateTimeField, toField: x => x && new Date(x),
84
+ helperText: "When expired, login won't be allowed" },
85
+ { k: 'days_to_live', xs: 12, sm: 6, comp: NumberField, disabled: expired, step: 'any', min: 1/1000, // 10 minutes
86
+ helperText: "Used to set expiration on first login" + (expired ? " (already expired)" : '') },
87
{ k: 'redirect', comp: VfsPathField,
88
helperText: "If you want this account to be redirected to a specific folder/address at login time" },
89
],
admin/src/AccountsPage.ts
+4
-5
@@ -3,7 +3,7 @@
3
import { createElement as h, useState, useEffect, Fragment } from "react"
4
import { apiCall, useApiEx } from './api'
5
import { Alert, Box, Button, Card, CardContent, Grid, List, ListItem, ListItemText, Typography } from '@mui/material'
6
-import { Close, Delete, DoNotDisturb, Group, MilitaryTech, Person, PersonAdd } from '@mui/icons-material'
6
+import { Close, Delete, DoNotDisturb, Group, MilitaryTech, Person, PersonAdd, Schedule } from '@mui/icons-material'
7
import { newDialog, with_ } from './misc'
8
import { Flex, IconBtn, iconTooltip, reloadBtn, useBreakpoint } from './mui'
9
import { TreeItem, TreeView } from '@mui/x-tree-view'
@@ -123,6 +123,8 @@ export default function AccountsPage() {
123
}
124
},
125
account2icon(ac),
126
+ ac.disabled && h(DoNotDisturb),
127
+ (ac.expire || ac.days_to_live) && h(Schedule),
128
ac.adminActualAccess && iconTooltip(MilitaryTech, "Can login into Admin"),
129
ac.username,
130
Boolean(ac.belongs?.length) && h(Box, { sx: { color: 'text.secondary', fontSize: 'small' } },
@@ -161,8 +163,5 @@ export default function AccountsPage() {
163
}
164
165
export function account2icon(ac: Account, props={}) {
164
- return h(Fragment, {},
165
- h(ac.hasPassword ? Person : Group, props),
166
- ac.disabled && h(DoNotDisturb),
167
- )
166
+ return h(ac.hasPassword ? Person : Group, props)
167
}
src/auth.ts
+7
-3
@@ -1,9 +1,9 @@
1
-import { Account, getAccount, normalizeUsername } from './perm'
1
+import { Account, getAccount, normalizeUsername, updateAccount } from './perm'
2
import { HTTP_NOT_ACCEPTABLE, HTTP_SERVER_ERROR } from './cross-const'
3
import { SRPParameters, SRPRoutines, SRPServerSession } from 'tssrp6a'
4
import { Context } from 'koa'
5
import { srpClientPart } from './srp'
6
-import { getOrSet } from './cross'
6
+import { DAY, getOrSet } from './cross'
7
import { createHash } from 'node:crypto'
8
9
const srp6aNimbusRoutines = new SRPRoutines(new SRPParameters())
@@ -48,7 +48,11 @@ export async function setLoggedIn(ctx: Context, username: string | false) {
48
}
49
invalidSessions.delete(username)
50
s.username = normalizeUsername(username)
51
- ctx.state.account = getAccount(username)
51
+ const a = ctx.state.account = getAccount(username)
52
+ if (a && !a.expire && a.days_to_live)
53
+ updateAccount(a, x => {
54
+ x.expire = new Date(Date.now() + a.days_to_live! * DAY)
55
+ })
56
}
57
58
export const invalidSessions = new Set<string>() // since session are currently stored in cookies, we need to memorize this until we meet again
src/perm.ts
+3
-1
@@ -18,6 +18,7 @@ export interface Account {
18
redirect?: string
19
disabled?: boolean
20
expire?: Date
21
+ days_to_live?: number
22
}
23
interface Accounts { [username:string]: Account }
24
@@ -133,7 +134,8 @@ export function renameAccount(from: string, to: string) {
134
}
135
136
// we consider all the following fields, when falsy, as equivalent to be missing. If this changes in the future, please adjust addAccount and setAccount
136
-const assignableProps: (keyof Account)[] = ['redirect','ignore_limits','belongs','admin','disabled','disable_password_change','expire']
137
+const assignableProps: (keyof Account)[] = ['redirect', 'ignore_limits', 'belongs', 'admin', 'disabled',
138
+ 'disable_password_change', 'expire', 'days_to_live']
139
140
export function addAccount(username: string, props: Partial<Account>) {
141
username = normalizeUsername(username)