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
}