finish up turbo and add stimulus.
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
import { authenticateRequest, extractTokenFromRequest } from "../../auth";
|
||||
import { AuditAction, logAudit } from "../../auditLog";
|
||||
import {
|
||||
deleteInactiveSessions,
|
||||
getSessionById,
|
||||
hashTokenForSession,
|
||||
listUserSessions,
|
||||
persistRevokedTokenHash,
|
||||
revokeSessionById,
|
||||
} from "../../db";
|
||||
import { htmlResponse } from "../ui/html";
|
||||
import { renderSessionsFrame } from "../ui/sessions-view";
|
||||
import { makeAuthCookieClear } from "./auth-cookie";
|
||||
|
||||
type UiRouteServer = {
|
||||
requestIP: (req: Request) => { address?: string } | null | undefined;
|
||||
};
|
||||
|
||||
function redirectToLogin(req: Request): Response {
|
||||
return Response.redirect(new URL("/login.html", req.url), 303);
|
||||
}
|
||||
|
||||
function currentTokenHash(req: Request): string | null {
|
||||
const token = extractTokenFromRequest(req);
|
||||
return token ? hashTokenForSession(token) : null;
|
||||
}
|
||||
|
||||
export async function handleUiRoutes(
|
||||
req: Request,
|
||||
url: URL,
|
||||
server: UiRouteServer,
|
||||
): Promise<Response | null> {
|
||||
if (!url.pathname.startsWith("/ui/")) return null;
|
||||
|
||||
const user = await authenticateRequest(req);
|
||||
if (!user) return redirectToLogin(req);
|
||||
|
||||
if (req.method === "GET" && url.pathname === "/ui/settings/sessions") {
|
||||
return htmlResponse(renderSessionsFrame(
|
||||
listUserSessions(user.userId),
|
||||
currentTokenHash(req),
|
||||
));
|
||||
}
|
||||
|
||||
if (req.method === "POST" && url.pathname === "/ui/settings/sessions/inactive") {
|
||||
const removed = deleteInactiveSessions(user.userId);
|
||||
logAudit({
|
||||
timestamp: Date.now(),
|
||||
username: user.username,
|
||||
ip: server.requestIP(req)?.address || "unknown",
|
||||
action: AuditAction.LOGOUT,
|
||||
details: `Removed ${removed} inactive session(s)`,
|
||||
success: true,
|
||||
});
|
||||
return htmlResponse(renderSessionsFrame(
|
||||
listUserSessions(user.userId),
|
||||
currentTokenHash(req),
|
||||
{ text: `Removed ${removed} inactive session(s).` },
|
||||
));
|
||||
}
|
||||
|
||||
const revokeMatch = req.method === "POST"
|
||||
? url.pathname.match(/^\/ui\/settings\/sessions\/([^/]+)\/revoke$/)
|
||||
: null;
|
||||
if (revokeMatch) {
|
||||
let sessionId = "";
|
||||
try {
|
||||
sessionId = decodeURIComponent(revokeMatch[1]);
|
||||
} catch {
|
||||
return htmlResponse(renderSessionsFrame(
|
||||
listUserSessions(user.userId),
|
||||
currentTokenHash(req),
|
||||
{ text: "Invalid session identifier.", type: "error" },
|
||||
), { status: 400 });
|
||||
}
|
||||
|
||||
const session = getSessionById(sessionId);
|
||||
if (!session || session.userId !== user.userId) {
|
||||
return htmlResponse(renderSessionsFrame(
|
||||
listUserSessions(user.userId),
|
||||
currentTokenHash(req),
|
||||
{ text: "Session not found.", type: "error" },
|
||||
), { status: 404 });
|
||||
}
|
||||
|
||||
const tokenHash = currentTokenHash(req);
|
||||
const revokingCurrentSession = session.tokenHash === tokenHash;
|
||||
const result = revokeSessionById(sessionId);
|
||||
if (result.tokenHash) persistRevokedTokenHash(result.tokenHash, session.expiresAt);
|
||||
|
||||
logAudit({
|
||||
timestamp: Date.now(),
|
||||
username: user.username,
|
||||
ip: server.requestIP(req)?.address || "unknown",
|
||||
action: AuditAction.LOGOUT,
|
||||
details: `Revoked session ${sessionId}`,
|
||||
success: true,
|
||||
});
|
||||
|
||||
if (revokingCurrentSession) {
|
||||
return new Response(null, {
|
||||
status: 303,
|
||||
headers: {
|
||||
"Location": "/",
|
||||
"Set-Cookie": makeAuthCookieClear(req),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return htmlResponse(renderSessionsFrame(
|
||||
listUserSessions(user.userId),
|
||||
tokenHash,
|
||||
{ text: "Session revoked successfully." },
|
||||
));
|
||||
}
|
||||
|
||||
return htmlResponse("<h1>Not found</h1>", { status: 404 });
|
||||
}
|
||||
Reference in New Issue
Block a user