Fix auth session race
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
@@ -14,6 +14,7 @@ const COOKIE_NAME = authCookieName();
|
||||
const SESSION_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;
|
||||
const SESSION_REFRESH_INTERVAL_MS = 60 * 60 * 12 * 1000;
|
||||
const LINUX_ACCOUNT_WORKSPACE_NAME = "zk-agent";
|
||||
let sessionsWriteQueue: Promise<unknown> = Promise.resolve();
|
||||
|
||||
type UserRecord = {
|
||||
id: string;
|
||||
@@ -131,9 +132,10 @@ export async function logoutAccount() {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get(COOKIE_NAME)?.value;
|
||||
if (token) {
|
||||
const sessionsFile = await readSessions();
|
||||
delete sessionsFile.sessions[token];
|
||||
await writeSessions(sessionsFile);
|
||||
await mutateSessions((sessionsFile) => {
|
||||
delete sessionsFile.sessions[token];
|
||||
return sessionsFile;
|
||||
});
|
||||
}
|
||||
cookieStore.delete(COOKIE_NAME);
|
||||
}
|
||||
@@ -151,8 +153,12 @@ export async function getCurrentAccount(): Promise<AccountSession | null> {
|
||||
const account = usersFile.users.find((user) => user.id === session.accountId);
|
||||
if (!account) return null;
|
||||
if (shouldRefreshSession(session.updatedAt ?? session.createdAt)) {
|
||||
session.updatedAt = new Date().toISOString();
|
||||
await writeSessions(sessionsFile);
|
||||
await mutateSessions((latestSessionsFile) => {
|
||||
const latestSession = latestSessionsFile.sessions[token];
|
||||
if (!latestSession) return latestSessionsFile;
|
||||
latestSession.updatedAt = new Date().toISOString();
|
||||
return latestSessionsFile;
|
||||
});
|
||||
await setSessionCookie(token);
|
||||
}
|
||||
return { id: account.id, username: account.username };
|
||||
@@ -199,13 +205,15 @@ export function accountSessionRoot(accountId: string, sessionId: string) {
|
||||
|
||||
async function createSession(account: UserRecord) {
|
||||
const token = randomBytes(32).toString("hex");
|
||||
const sessionsFile = await readSessions();
|
||||
sessionsFile.sessions[token] = {
|
||||
accountId: account.id,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
await writeSessions(sessionsFile);
|
||||
const now = new Date().toISOString();
|
||||
await mutateSessions((sessionsFile) => {
|
||||
sessionsFile.sessions[token] = {
|
||||
accountId: account.id,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
return sessionsFile;
|
||||
});
|
||||
|
||||
await setSessionCookie(token);
|
||||
return { id: account.id, username: account.username };
|
||||
@@ -257,7 +265,22 @@ async function readSessions(): Promise<SessionsFile> {
|
||||
|
||||
async function writeSessions(payload: SessionsFile) {
|
||||
await mkdir(AUTH_STATE_ROOT, { recursive: true });
|
||||
await writeFile(SESSIONS_PATH, JSON.stringify(payload, null, 2), "utf8");
|
||||
const tempPath = `${SESSIONS_PATH}.${process.pid}.${Date.now()}.${randomBytes(4).toString("hex")}.tmp`;
|
||||
await writeFile(tempPath, JSON.stringify(payload, null, 2), "utf8");
|
||||
await rename(tempPath, SESSIONS_PATH);
|
||||
}
|
||||
|
||||
async function mutateSessions(
|
||||
mutator: (payload: SessionsFile) => SessionsFile | Promise<SessionsFile>,
|
||||
) {
|
||||
const run = sessionsWriteQueue.then(async () => {
|
||||
const current = await readSessions();
|
||||
const next = await mutator(current);
|
||||
await writeSessions(next);
|
||||
return next;
|
||||
});
|
||||
sessionsWriteQueue = run.catch(() => undefined);
|
||||
return run;
|
||||
}
|
||||
|
||||
function normalizeUsername(username: string) {
|
||||
|
||||
Reference in New Issue
Block a user