Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions frontend/src/hooks/useDocumentStatusSocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Document status socket hook (#1502, #1503).
*
* The document page opened a raw WebSocket to `/documents/:id/status`, but the
* backend serves a Socket.IO namespace, and its manual reconnect loop kept
* running after the page was closed. This hook connects through Socket.IO and
* tears the connection down on unmount.
*/
import { useEffect, useRef } from "react";
import { io, type Socket } from "socket.io-client";

export interface DocumentStatusEvent {
documentId: string;
status: string;
}

export function useDocumentStatusSocket(
documentId: string,
onStatus: (event: DocumentStatusEvent) => void,
baseUrl: string = process.env.NEXT_PUBLIC_API_URL ?? "",
): void {
const handlerRef = useRef(onStatus);
handlerRef.current = onStatus;

useEffect(() => {
if (!documentId) {
return;
}
const socket: Socket = io(`${baseUrl}/documents`, {
transports: ["websocket"],
withCredentials: true,
});
socket.on("status", (event: DocumentStatusEvent) =>
handlerRef.current(event),
);
return () => {
socket.off("status");
socket.disconnect();
};
}, [documentId, baseUrl]);
}
29 changes: 29 additions & 0 deletions frontend/src/lib/auth-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Auth credential storage policy (#1501, #1500).
*
* Access/refresh tokens were written to `localStorage` even though the backend
* already sets httpOnly cookies. Pages that read tokens from `localStorage`
* also bypassed `lib/api-client`, so they missed 401 handling and refresh. This
* module makes cookies authoritative and exposes a migration helper to drop
* legacy localStorage tokens.
*/

/** Cookies are the single source of truth for auth (backend sets httpOnly). */
export const AUTH_COOKIE_MODE = true;

/** Whether client code is allowed to persist tokens itself. */
export function shouldStoreTokensInLocalStorage(): boolean {
return !AUTH_COOKIE_MODE;
}

/**
* Remove any legacy tokens persisted in storage. Call once on boot to migrate
* existing sessions onto cookie auth (#1501).
*/
export function clearLegacyLocalStorageTokens(
storage: Pick<Storage, "removeItem">,
): void {
for (const key of ["access_token", "refresh_token", "token"]) {
storage.removeItem(key);
}
}