@cuongtran001/kanna 1.71.2 → 1.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/{BoardsPage-DudYbDyu.js → BoardsPage-Cv8FIzb-.js} +1 -1
- package/dist/client/assets/{BoardsRoutePage-DUvJAyzh.js → BoardsRoutePage-D2B4CKzS.js} +1 -1
- package/dist/client/assets/{ChatPage-ChFKw7nK.js → ChatPage-DyVlp7tq.js} +1 -1
- package/dist/client/assets/{CronJobRow-DU1RtrCs.js → CronJobRow-DtlnNrFa.js} +1 -1
- package/dist/client/assets/{CronJobsPage-cjH7p3lh.js → CronJobsPage-BqBSaj2f.js} +1 -1
- package/dist/client/assets/{FilePreviewSheet-wcpWIb90.js → FilePreviewSheet-cAupHL1-.js} +1 -1
- package/dist/client/assets/{FlowSurface-Doyyct0h.js → FlowSurface-Cy6Ypvp4.js} +1 -1
- package/dist/client/assets/{GenUIBlock-D-TBN4Um.js → GenUIBlock-DS3aMR8o.js} +2 -2
- package/dist/client/assets/{KannaSocketProvider-uWU6WkNo.js → KannaSocketProvider-0GNmyg3O.js} +1 -1
- package/dist/client/assets/{LocalProjectsPage-D8V8a3WM.js → LocalProjectsPage-DQQtJlM1.js} +1 -1
- package/dist/client/assets/{SettingsPage-CiCU3nka.js → SettingsPage-X5P3EyCv.js} +1 -1
- package/dist/client/assets/{StackBoardsRoutePage-DDvdmA3Y.js → StackBoardsRoutePage-Dq3kQSm2.js} +1 -1
- package/dist/client/assets/{ToolCallMessage-CHJSiauG.js → ToolCallMessage-Dhc2sf0Y.js} +1 -1
- package/dist/client/assets/{VChartSurface-DVRoAi5e.js → VChartSurface-CWzLshgk.js} +1 -1
- package/dist/client/assets/{WorkflowsPage-LwL034ZP.js → WorkflowsPage-BW0zI7T4.js} +1 -1
- package/dist/client/assets/{WorkflowsSection-HlGaR0pJ.js → WorkflowsSection-yOlBEJiD.js} +1 -1
- package/dist/client/assets/{chart-renderer-BZXJQWtw.js → chart-renderer-CARfWmNI.js} +1 -1
- package/dist/client/assets/{chatSounds-BNvUSPAi.js → chatSounds-BgTY66uu.js} +1 -1
- package/dist/client/assets/chevron-left-CFGYD17H.js +1 -0
- package/dist/client/assets/{clipboard.adapter-CIRv1YNb.js → clipboard.adapter-DpHeqwMw.js} +1 -1
- package/dist/client/assets/{columnStyle-mS8DKLCV.js → columnStyle-DYXYcPnt.js} +1 -1
- package/dist/client/assets/{copyFeedbackStore-DHAfKV4i.js → copyFeedbackStore-DZw_cFtR.js} +1 -1
- package/dist/client/assets/{createScopedStore-or82_bxr.js → createScopedStore-BVEubecu.js} +1 -1
- package/dist/client/assets/{dist-Cvh6FEf5.js → dist-B_c3yYUS.js} +1 -1
- package/dist/client/assets/{ellipsis-CWiRFysC.js → ellipsis-63_6PNeI.js} +1 -1
- package/dist/client/assets/{flow-kinds-Digyo6AB.js → flow-kinds-CG9-P5sO.js} +1 -1
- package/dist/client/assets/{folder-CnSb5m9v.js → folder-uf9sKjmp.js} +1 -1
- package/dist/client/assets/{formatDuration-DsrNkArK.js → formatDuration-DXQnrqg6.js} +1 -1
- package/dist/client/assets/{genui-BagNQVNI.js → genui-D649Dv92.js} +1 -1
- package/dist/client/assets/{globe-tc_9UiU3.js → globe-DRxtkxRw.js} +1 -1
- package/dist/client/assets/{host-C7I7nFuH.js → host-BpGUm8Ks.js} +1 -1
- package/dist/client/assets/{index-D9OYZjb7.js → index-BIXIaFOx.js} +3 -3
- package/dist/client/assets/{info-CgLDBnLR.js → info-CzLBGkjs.js} +1 -1
- package/dist/client/assets/{input-BaAZ4OHy.js → input-Dj2AfDyu.js} +1 -1
- package/dist/client/assets/{lexicalToReact-ClgTGbzM.js → lexicalToReact-DTdGRuI2.js} +3 -3
- package/dist/client/assets/{log-Ch_SNBXH.js → log-C-CyaVcQ.js} +1 -1
- package/dist/client/assets/{monitor-fFRMWd1S.js → monitor-Cl6GABTi.js} +1 -1
- package/dist/client/assets/{pendingActionsStore-CWNaCyjS.js → pendingActionsStore-CpbHd9ei.js} +1 -1
- package/dist/client/assets/{play-DD0bVaYg.js → play-cuJ4aGHN.js} +1 -1
- package/dist/client/assets/{plus-By3sppx5.js → plus-DpSRUvXD.js} +1 -1
- package/dist/client/assets/{popover-CJNN95M0.js → popover-C_I8IOWx.js} +1 -1
- package/dist/client/assets/{pushClient-DbwI0iz1.js → pushClient-CtuTxUvx.js} +1 -1
- package/dist/client/assets/runDetached-QSRrHU3F.js +1 -0
- package/dist/client/assets/{segmented-control-DYmNRyQy.js → segmented-control-QukGPBuR.js} +1 -1
- package/dist/client/assets/{select-Dam9e0mi.js → select-BgMpt5jO.js} +1 -1
- package/dist/client/assets/{settings-header-button-DaCbsV31.js → settings-header-button-BLAONCyF.js} +1 -1
- package/dist/client/assets/{sidebarPendingActions-DxI6ubAL.js → sidebarPendingActions-Bde5y5HH.js} +1 -1
- package/dist/client/assets/{square-pen-DKZvXh3R.js → square-pen-CIU74x0s.js} +1 -1
- package/dist/client/assets/{state-mark-DxzVQsVa.js → state-mark-DS7GjjiN.js} +1 -1
- package/dist/client/assets/{status-pill-BFLy7eN9.js → status-pill-Bm1d4Yt2.js} +1 -1
- package/dist/client/assets/{terminal-DRYQ5avJ.js → terminal-D4dPFOU3.js} +1 -1
- package/dist/client/assets/{timer.adapter-BDh0KX9G.js → timer.adapter-5aM9hrUB.js} +1 -1
- package/dist/client/assets/{tools-toomKvSw.js → tools-5kr-voMM.js} +1 -1
- package/dist/client/assets/{triangle-alert-CM5uh-kq.js → triangle-alert-BAPOUptD.js} +1 -1
- package/dist/client/assets/{useAppGlobalState-BCUZEZEi.js → useAppGlobalState-BNrYZSMc.js} +1 -1
- package/dist/client/assets/{useKannaState-BvJAZTpL.js → useKannaState-DiqCTk6A.js} +1 -1
- package/dist/client/assets/{useTheme-B6cYOPpe.js → useTheme-DOxNN3XT.js} +1 -1
- package/dist/client/index.html +33 -33
- package/package.json +2 -1
- package/src/server/auth-backend.ts +16 -0
- package/src/server/auth.test.ts +62 -1
- package/src/server/auth.ts +90 -87
- package/src/server/better-auth.adapter.ts +197 -0
- package/src/server/http-dispatcher.ts +5 -5
- package/src/server/server.ts +7 -8
- package/dist/client/assets/chevron-left-FnMbL2tA.js +0 -1
- package/dist/client/assets/runDetached-CQOu05LK.js +0 -1
- package/src/server/auth-session-store.adapter.ts +0 -224
- package/src/server/auth-session-store.test.ts +0 -94
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto"
|
|
2
|
-
import { mkdir, readFile, rename, writeFile } from "node:fs/promises"
|
|
3
|
-
import path from "node:path"
|
|
4
|
-
import { LOG_PREFIX } from "../shared/branding"
|
|
5
|
-
import { log } from "../shared/log"
|
|
6
|
-
import { isErrnoException } from "../shared/errors"
|
|
7
|
-
import { isJsonArray, isJsonObject, safeJsonParse, type JsonValue } from "../shared/json"
|
|
8
|
-
|
|
9
|
-
const FILE_VERSION = 1
|
|
10
|
-
const PERSIST_DEBOUNCE_MS = 250
|
|
11
|
-
const PERSIST_DRIFT_MS = 60 * 60 * 1000
|
|
12
|
-
const SWEEP_INTERVAL_MS = 60 * 60 * 1000
|
|
13
|
-
|
|
14
|
-
export interface PersistedSession {
|
|
15
|
-
tokenHash: string
|
|
16
|
-
createdAt: number
|
|
17
|
-
lastSeenAt: number
|
|
18
|
-
expiresAt: number
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface SessionsFile {
|
|
22
|
-
version: number
|
|
23
|
-
sessions: PersistedSession[]
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface AuthSessionStore {
|
|
27
|
-
create(token: string, maxAgeMs: number): PersistedSession
|
|
28
|
-
validate(token: string): PersistedSession | null
|
|
29
|
-
touch(token: string, maxAgeMs: number): PersistedSession | null
|
|
30
|
-
revoke(token: string): void
|
|
31
|
-
sweep(): void
|
|
32
|
-
dispose(): Promise<void>
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface CreateAuthSessionStoreOptions {
|
|
36
|
-
filePath: string
|
|
37
|
-
now?: () => number
|
|
38
|
-
sweepIntervalMs?: number
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function hashToken(token: string) {
|
|
42
|
-
return createHash("sha256").update(token).digest("hex")
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function isPersistedSession(value: JsonValue): value is JsonValue & PersistedSession {
|
|
46
|
-
if (!isJsonObject(value)) return false
|
|
47
|
-
return typeof value.tokenHash === "string"
|
|
48
|
-
&& typeof value.createdAt === "number"
|
|
49
|
-
&& typeof value.lastSeenAt === "number"
|
|
50
|
-
&& typeof value.expiresAt === "number"
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async function loadSessionsFile(filePath: string): Promise<PersistedSession[]> {
|
|
54
|
-
let text: string
|
|
55
|
-
try {
|
|
56
|
-
text = await readFile(filePath, "utf8")
|
|
57
|
-
} catch (error) {
|
|
58
|
-
if (isErrnoException(error) && error.code === "ENOENT") return []
|
|
59
|
-
throw error
|
|
60
|
-
}
|
|
61
|
-
if (!text.trim()) return []
|
|
62
|
-
const parsed = safeJsonParse(text)
|
|
63
|
-
if (parsed === null) {
|
|
64
|
-
log.warn(`${LOG_PREFIX} sessions.json is invalid JSON; ignoring.`)
|
|
65
|
-
return []
|
|
66
|
-
}
|
|
67
|
-
if (!isJsonObject(parsed) || !isJsonArray(parsed.sessions)) return []
|
|
68
|
-
return parsed.sessions.filter(isPersistedSession)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export async function createAuthSessionStore(options: CreateAuthSessionStoreOptions): Promise<AuthSessionStore> {
|
|
72
|
-
const { filePath } = options
|
|
73
|
-
const now = options.now ?? (() => Date.now())
|
|
74
|
-
const sweepIntervalMs = options.sweepIntervalMs ?? SWEEP_INTERVAL_MS
|
|
75
|
-
|
|
76
|
-
await mkdir(path.dirname(filePath), { recursive: true })
|
|
77
|
-
const initialSessions = await loadSessionsFile(filePath)
|
|
78
|
-
const sessions = new Map<string, PersistedSession>()
|
|
79
|
-
const lastPersistedExpiry = new Map<string, number>()
|
|
80
|
-
for (const session of initialSessions) {
|
|
81
|
-
if (session.expiresAt > now()) {
|
|
82
|
-
sessions.set(session.tokenHash, session)
|
|
83
|
-
lastPersistedExpiry.set(session.tokenHash, session.expiresAt)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
let pendingPersist: ReturnType<typeof setTimeout> | null = null
|
|
88
|
-
let activeWrite: Promise<void> | null = null
|
|
89
|
-
let writeAgain = false
|
|
90
|
-
|
|
91
|
-
async function writeFileAtomic() {
|
|
92
|
-
const payload: SessionsFile = {
|
|
93
|
-
version: FILE_VERSION,
|
|
94
|
-
sessions: Array.from(sessions.values()),
|
|
95
|
-
}
|
|
96
|
-
const tmpPath = `${filePath}.tmp`
|
|
97
|
-
await writeFile(tmpPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8")
|
|
98
|
-
await rename(tmpPath, filePath)
|
|
99
|
-
for (const session of payload.sessions) {
|
|
100
|
-
lastPersistedExpiry.set(session.tokenHash, session.expiresAt)
|
|
101
|
-
}
|
|
102
|
-
for (const tokenHash of [...lastPersistedExpiry.keys()]) {
|
|
103
|
-
if (!sessions.has(tokenHash)) lastPersistedExpiry.delete(tokenHash)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function runWrite() {
|
|
108
|
-
do {
|
|
109
|
-
writeAgain = false
|
|
110
|
-
try {
|
|
111
|
-
await writeFileAtomic()
|
|
112
|
-
} catch (error) {
|
|
113
|
-
log.warn(`${LOG_PREFIX} Failed to persist sessions.json:`, String(error))
|
|
114
|
-
}
|
|
115
|
-
} while (writeAgain)
|
|
116
|
-
activeWrite = null
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function schedulePersist() {
|
|
120
|
-
if (pendingPersist) return
|
|
121
|
-
pendingPersist = setTimeout(() => {
|
|
122
|
-
pendingPersist = null
|
|
123
|
-
if (activeWrite) {
|
|
124
|
-
writeAgain = true
|
|
125
|
-
return
|
|
126
|
-
}
|
|
127
|
-
activeWrite = runWrite()
|
|
128
|
-
}, PERSIST_DEBOUNCE_MS)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async function flushPersist() {
|
|
132
|
-
if (pendingPersist) {
|
|
133
|
-
clearTimeout(pendingPersist)
|
|
134
|
-
pendingPersist = null
|
|
135
|
-
activeWrite = activeWrite ?? runWrite()
|
|
136
|
-
}
|
|
137
|
-
if (activeWrite) await activeWrite
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function shouldPersistTouch(tokenHash: string, expiresAt: number) {
|
|
141
|
-
const last = lastPersistedExpiry.get(tokenHash)
|
|
142
|
-
if (last === undefined) return true
|
|
143
|
-
return Math.abs(expiresAt - last) >= PERSIST_DRIFT_MS
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function create(token: string, maxAgeMs: number): PersistedSession {
|
|
147
|
-
const tokenHash = hashToken(token)
|
|
148
|
-
const timestamp = now()
|
|
149
|
-
const session: PersistedSession = {
|
|
150
|
-
tokenHash,
|
|
151
|
-
createdAt: timestamp,
|
|
152
|
-
lastSeenAt: timestamp,
|
|
153
|
-
expiresAt: timestamp + maxAgeMs,
|
|
154
|
-
}
|
|
155
|
-
sessions.set(tokenHash, session)
|
|
156
|
-
schedulePersist()
|
|
157
|
-
return session
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function validate(token: string): PersistedSession | null {
|
|
161
|
-
const tokenHash = hashToken(token)
|
|
162
|
-
const session = sessions.get(tokenHash)
|
|
163
|
-
if (!session) return null
|
|
164
|
-
if (session.expiresAt <= now()) {
|
|
165
|
-
sessions.delete(tokenHash)
|
|
166
|
-
schedulePersist()
|
|
167
|
-
return null
|
|
168
|
-
}
|
|
169
|
-
return session
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function touch(token: string, maxAgeMs: number): PersistedSession | null {
|
|
173
|
-
const tokenHash = hashToken(token)
|
|
174
|
-
const session = sessions.get(tokenHash)
|
|
175
|
-
if (!session) return null
|
|
176
|
-
const timestamp = now()
|
|
177
|
-
if (session.expiresAt <= timestamp) {
|
|
178
|
-
sessions.delete(tokenHash)
|
|
179
|
-
schedulePersist()
|
|
180
|
-
return null
|
|
181
|
-
}
|
|
182
|
-
const next: PersistedSession = {
|
|
183
|
-
...session,
|
|
184
|
-
lastSeenAt: timestamp,
|
|
185
|
-
expiresAt: timestamp + maxAgeMs,
|
|
186
|
-
}
|
|
187
|
-
sessions.set(tokenHash, next)
|
|
188
|
-
if (shouldPersistTouch(tokenHash, next.expiresAt)) {
|
|
189
|
-
schedulePersist()
|
|
190
|
-
}
|
|
191
|
-
return next
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function revoke(token: string) {
|
|
195
|
-
const tokenHash = hashToken(token)
|
|
196
|
-
if (sessions.delete(tokenHash)) {
|
|
197
|
-
schedulePersist()
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function sweep() {
|
|
202
|
-
const cutoff = now()
|
|
203
|
-
let changed = false
|
|
204
|
-
for (const [tokenHash, session] of sessions) {
|
|
205
|
-
if (session.expiresAt <= cutoff) {
|
|
206
|
-
sessions.delete(tokenHash)
|
|
207
|
-
changed = true
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
if (changed) schedulePersist()
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const sweepHandle = setInterval(sweep, sweepIntervalMs)
|
|
214
|
-
if (typeof sweepHandle === "object" && sweepHandle !== null && "unref" in sweepHandle) {
|
|
215
|
-
sweepHandle.unref?.()
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
async function dispose() {
|
|
219
|
-
clearInterval(sweepHandle)
|
|
220
|
-
await flushPersist()
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return { create, validate, touch, revoke, sweep, dispose }
|
|
224
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, test } from "bun:test"
|
|
2
|
-
import { createHash } from "node:crypto"
|
|
3
|
-
import { mkdtemp, readFile, rm } from "node:fs/promises"
|
|
4
|
-
import { tmpdir } from "node:os"
|
|
5
|
-
import path from "node:path"
|
|
6
|
-
import { createAuthSessionStore } from "./auth-session-store.adapter"
|
|
7
|
-
|
|
8
|
-
const tempDirs: string[] = []
|
|
9
|
-
|
|
10
|
-
afterEach(async () => {
|
|
11
|
-
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })))
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
async function makeFilePath() {
|
|
15
|
-
const dir = await mkdtemp(path.join(tmpdir(), "kanna-sessions-"))
|
|
16
|
-
tempDirs.push(dir)
|
|
17
|
-
return path.join(dir, "sessions.json")
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const DAY_MS = 86_400_000
|
|
21
|
-
|
|
22
|
-
describe("auth-session-store", () => {
|
|
23
|
-
test("persists only the sha256 hash of the token", async () => {
|
|
24
|
-
const filePath = await makeFilePath()
|
|
25
|
-
const store = await createAuthSessionStore({ filePath })
|
|
26
|
-
store.create("super-secret-token", DAY_MS)
|
|
27
|
-
await store.dispose()
|
|
28
|
-
|
|
29
|
-
const text = await readFile(filePath, "utf8")
|
|
30
|
-
expect(text).not.toContain("super-secret-token")
|
|
31
|
-
const expectedHash = createHash("sha256").update("super-secret-token").digest("hex")
|
|
32
|
-
expect(text).toContain(expectedHash)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
test("hydrates persisted entries on construction", async () => {
|
|
36
|
-
const filePath = await makeFilePath()
|
|
37
|
-
const first = await createAuthSessionStore({ filePath })
|
|
38
|
-
first.create("token-a", DAY_MS)
|
|
39
|
-
await first.dispose()
|
|
40
|
-
|
|
41
|
-
const second = await createAuthSessionStore({ filePath })
|
|
42
|
-
expect(second.validate("token-a")).not.toBeNull()
|
|
43
|
-
expect(second.validate("missing")).toBeNull()
|
|
44
|
-
await second.dispose()
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test("expired entries are dropped on validate and sweep", async () => {
|
|
48
|
-
const filePath = await makeFilePath()
|
|
49
|
-
let now = 1_000
|
|
50
|
-
const store = await createAuthSessionStore({ filePath, now: () => now })
|
|
51
|
-
store.create("token-x", 100)
|
|
52
|
-
expect(store.validate("token-x")).not.toBeNull()
|
|
53
|
-
|
|
54
|
-
now = 5_000
|
|
55
|
-
expect(store.validate("token-x")).toBeNull()
|
|
56
|
-
|
|
57
|
-
store.create("token-y", 100)
|
|
58
|
-
now = 100_000
|
|
59
|
-
store.sweep()
|
|
60
|
-
expect(store.validate("token-y")).toBeNull()
|
|
61
|
-
await store.dispose()
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
test("touch shifts expiresAt forward and survives reload", async () => {
|
|
65
|
-
const filePath = await makeFilePath()
|
|
66
|
-
let now = 1_000
|
|
67
|
-
const first = await createAuthSessionStore({ filePath, now: () => now })
|
|
68
|
-
first.create("token-z", DAY_MS)
|
|
69
|
-
|
|
70
|
-
now = 1_000 + 12 * 60 * 60 * 1000
|
|
71
|
-
const touched = first.touch("token-z", DAY_MS)
|
|
72
|
-
expect(touched).not.toBeNull()
|
|
73
|
-
expect(touched!.expiresAt).toBe(now + DAY_MS)
|
|
74
|
-
await first.dispose()
|
|
75
|
-
|
|
76
|
-
const second = await createAuthSessionStore({ filePath, now: () => now })
|
|
77
|
-
const reloaded = second.validate("token-z")
|
|
78
|
-
expect(reloaded).not.toBeNull()
|
|
79
|
-
expect(reloaded!.expiresAt).toBe(now + DAY_MS)
|
|
80
|
-
await second.dispose()
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
test("revoke removes entries from disk", async () => {
|
|
84
|
-
const filePath = await makeFilePath()
|
|
85
|
-
const store = await createAuthSessionStore({ filePath })
|
|
86
|
-
store.create("token-r", DAY_MS)
|
|
87
|
-
store.revoke("token-r")
|
|
88
|
-
await store.dispose()
|
|
89
|
-
|
|
90
|
-
const reloaded = await createAuthSessionStore({ filePath })
|
|
91
|
-
expect(reloaded.validate("token-r")).toBeNull()
|
|
92
|
-
await reloaded.dispose()
|
|
93
|
-
})
|
|
94
|
-
})
|