@samitouri / QOSami-HFS / commits / 5afbde27

fix: "invalidate past sessions" was affecting only the session that made next access

Massimo Melina committed May 5, 2024 at 11:36 UTC 5afbde27ebe02054b2881a0f55220b9de9a08645
6 files changed +16 -15
admin/src/AccountForm.ts
+2 -3
@@ -5,7 +5,7 @@ import { BoolField, Form, MultiSelectField, NumberField } from '@hfs/mui-grid-fo
5 import { Alert } from '@mui/material'
6 import { apiCall } from './api'
7 import { alertDialog, useDialogBarColors } from './dialog'
8 -import { isEqualLax, useIsMobile, wantArray } from './misc'
8 +import { formatTimestamp, isEqualLax, prefix, useIsMobile, wantArray } from './misc'
9 import { IconBtn, modifiedProps } from './mui'
10 import { Account } from './AccountsPage'
11 import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a'
@@ -48,9 +48,8 @@ export default function AccountForm({ account, done, groups, addToBar, reload }:
48 }),
49 h(IconBtn, {
50 icon: AutoDelete,
51 - title: "Invalidate past sessions",
51 + title: `Invalidate past sessions ${prefix('(', formatTimestamp(account.invalidated || 0), ')')}`,
52 doneMessage: true,
53 - disabled: account.invalidated,
53 onClick: () => apiCall('invalidate_sessions', { username: account.username }).then(reload)
54 }),
55 ...wantArray(addToBar),
admin/src/AccountsPage.ts
+1 -1
@@ -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: true }, a =>
45 + : with_(selectedAccount || { username: '', hasPassword: sel === 'new-user', adminActualAccess: false, invalidated: undefined }, a =>
46 h(AccountForm, {
47 account: a,
48 groups: list.filter(x => !x.hasPassword).map( x => x.username ),
src/api.accounts.ts
+3 -3
@@ -5,7 +5,7 @@ import { Account, accountCanLoginAdmin, accountHasPassword, accountsConfig, addA
5 changeSrpHelper, updateAccount } from './perm'
6 import _ from 'lodash'
7 import { HTTP_BAD_REQUEST, HTTP_CONFLICT, HTTP_NOT_FOUND } from './const'
8 -import { getCurrentUsername, invalidSessions } from './auth'
8 +import { getCurrentUsername, invalidateSessionBefore } from './auth'
9 import { apiAssertTypes } from './misc'
10
11 export type AccountAdminSend = NonNullable<ReturnType<typeof prepareAccount>>
@@ -15,7 +15,7 @@ function prepareAccount(ac: Account | undefined) {
15 username: ac.username, // omit won't copy it because it's a hidden prop
16 hasPassword: accountHasPassword(ac),
17 adminActualAccess: accountCanLoginAdmin(ac),
18 - invalidated: invalidSessions.has(ac.username),
18 + invalidated: invalidateSessionBefore.get(ac.username),
19 }
20 }
21
@@ -68,7 +68,7 @@ export default {
68
69 invalidate_sessions({ username }) {
70 apiAssertTypes({ string: { username } })
71 - invalidSessions.add(username)
71 + invalidateSessionBefore.set(username, Date.now())
72 return {}
73 },
74
src/auth.ts
+6 -4
@@ -46,11 +46,13 @@ export async function setLoggedIn(ctx: Context, username: string | false) {
46 delete s.username
47 return
48 }
49 - invalidSessions.delete(username)
50 - s.username = normalizeUsername(username)
49 const a = ctx.state.account = getAccount(username)
52 - if (a && !a.expire && a.days_to_live)
50 + if (!a) return
51 + s.username = normalizeUsername(username)
52 + s.ts = Date.now()
53 + if (!a.expire && a.days_to_live)
54 updateAccount(a, { expire: new Date(Date.now() + a.days_to_live! * DAY) })
55 }
56
56 -export const invalidSessions = new Set<string>() // since session are currently stored in cookies, we need to memorize this until we meet again
57 +// since session are currently stored in cookies, we need to store this information
58 +export const invalidateSessionBefore = new Map<string, number>()
src/cross.ts
+2 -2
@@ -330,8 +330,8 @@ export function repeat(everyMs: number, cb: Callback<Callback>): Callback {
330 }
331 }
332
333 -export function formatTimestamp(x: string | Date) {
334 - return !x ? '-' : (x instanceof Date ? x : new Date(x)).toLocaleString()
333 +export function formatTimestamp(x: number | string | Date) {
334 + return !x ? '' : (x instanceof Date ? x : new Date(x)).toLocaleString()
335 }
336
337 export function isPrimitive(x: unknown): x is boolean | string | number | undefined | null {
src/middlewares.ts
+2 -2
@@ -9,7 +9,7 @@ import { applyBlock } from './block'
9 import { Account, accountCanLogin, getAccount } from './perm'
10 import { Connection, normalizeIp, socket2connection, updateConnectionForCtx } from './connections'
11 import basicAuth from 'basic-auth'
12 -import { invalidSessions, setLoggedIn, srpCheck } from './auth'
12 +import { invalidateSessionBefore, setLoggedIn, srpCheck } from './auth'
13 import { constants } from 'zlib'
14 import { getHttpsWorkingPort } from './listen'
15 import { defineConfig } from './config'
@@ -95,7 +95,7 @@ export function getProxyDetected() {
95
96 export const prepareState: Koa.Middleware = async (ctx, next) => {
97 if (ctx.session?.username) {
98 - if (invalidSessions.delete(ctx.session.username))
98 + if (ctx.session.ts < invalidateSessionBefore.get(ctx.session.username)!)
99 delete ctx.session.username
100 ctx.session.maxAge = sessionDuration.compiled()
101 }