@samitouri / QOSami-HFS / commits / bcc86f1d

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)