security: prevent websocket JWT leakage through URL query tokens (#349)

* security: prevent websocket JWT query authentication

* fix: harden websocket connection tickets

* fix: expire unused websocket tickets
This commit is contained in:
Chius
2026-08-30 01:56:31 +08:00
committed by GitHub
parent df6b0b9767
commit e63f9663e8
6 changed files with 439 additions and 24 deletions
+12 -4
View File
@@ -1674,17 +1674,25 @@ export default function App() {
reconnectAttempts += 1;
reconnectTimer = window.setTimeout(() => {
reconnectTimer = null;
connect();
void connect();
}, delay);
};
const connect = () => {
const connect = async () => {
if (disposed) return;
const accessToken = session.accessToken;
if (!accessToken) return;
try {
const negotiateResponse = await fetch('/notifications/hub/negotiate?negotiateVersion=1', {
method: 'POST',
headers: { Authorization: `Bearer ${accessToken}` },
});
if (!negotiateResponse.ok) throw new Error('Notification negotiation failed');
const negotiation = (await negotiateResponse.json()) as { connectionToken?: string };
if (!negotiation.connectionToken || disposed) throw new Error('Notification connection token missing');
const hubUrl = new URL('/notifications/hub', window.location.origin);
hubUrl.searchParams.set('access_token', accessToken);
hubUrl.searchParams.set('id', negotiation.connectionToken);
hubUrl.protocol = hubUrl.protocol === 'https:' ? 'wss:' : 'ws:';
socket = new WebSocket(hubUrl.toString());
} catch {
@@ -1809,7 +1817,7 @@ export default function App() {
});
};
connect();
void connect();
return () => {
disposed = true;