|
|
|
|
@@ -167,17 +167,36 @@ export class WebSocketGateway implements OnGatewayConnection, OnGatewayDisconnec
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @description Extract authentication token from Socket.IO handshake
|
|
|
|
|
* @description Extract authentication token from Socket.IO handshake.
|
|
|
|
|
*
|
|
|
|
|
* Checks sources in order:
|
|
|
|
|
* 1. handshake.auth.token — explicit token (e.g. from API clients)
|
|
|
|
|
* 2. handshake.headers.cookie — session cookie sent by browser via withCredentials
|
|
|
|
|
* 3. query.token — URL query parameter fallback
|
|
|
|
|
* 4. Authorization header — Bearer token fallback
|
|
|
|
|
*
|
|
|
|
|
* @param client - The socket client
|
|
|
|
|
* @returns The token string or undefined if not found
|
|
|
|
|
*/
|
|
|
|
|
private extractTokenFromHandshake(client: Socket): string | undefined {
|
|
|
|
|
// Check handshake.auth.token (preferred method)
|
|
|
|
|
// Check handshake.auth.token (preferred method for non-browser clients)
|
|
|
|
|
const authToken = client.handshake.auth.token as unknown;
|
|
|
|
|
if (typeof authToken === "string" && authToken.length > 0) {
|
|
|
|
|
return authToken;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fallback: parse session cookie from request headers.
|
|
|
|
|
// Browsers send httpOnly cookies automatically when withCredentials: true is set
|
|
|
|
|
// on the socket.io client. BetterAuth uses one of these cookie names depending
|
|
|
|
|
// on whether the connection is HTTPS (Secure prefix) or HTTP (dev).
|
|
|
|
|
const cookieHeader = client.handshake.headers.cookie;
|
|
|
|
|
if (typeof cookieHeader === "string" && cookieHeader.length > 0) {
|
|
|
|
|
const cookieToken = this.extractTokenFromCookieHeader(cookieHeader);
|
|
|
|
|
if (cookieToken) {
|
|
|
|
|
return cookieToken;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fallback: check query parameters
|
|
|
|
|
const queryToken = client.handshake.query.token as unknown;
|
|
|
|
|
if (typeof queryToken === "string" && queryToken.length > 0) {
|
|
|
|
|
@@ -197,6 +216,45 @@ export class WebSocketGateway implements OnGatewayConnection, OnGatewayDisconnec
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @description Parse the BetterAuth session token from a raw Cookie header string.
|
|
|
|
|
*
|
|
|
|
|
* BetterAuth names the session cookie differently based on the security context:
|
|
|
|
|
* - `__Secure-better-auth.session_token` — HTTPS with Secure flag
|
|
|
|
|
* - `better-auth.session_token` — HTTP (development)
|
|
|
|
|
* - `__Host-better-auth.session_token` — HTTPS with Host prefix
|
|
|
|
|
*
|
|
|
|
|
* @param cookieHeader - The raw Cookie header value
|
|
|
|
|
* @returns The session token value or undefined if no matching cookie found
|
|
|
|
|
*/
|
|
|
|
|
private extractTokenFromCookieHeader(cookieHeader: string): string | undefined {
|
|
|
|
|
const SESSION_COOKIE_NAMES = [
|
|
|
|
|
"__Secure-better-auth.session_token",
|
|
|
|
|
"better-auth.session_token",
|
|
|
|
|
"__Host-better-auth.session_token",
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
// Parse the Cookie header into a key-value map
|
|
|
|
|
const cookies = Object.fromEntries(
|
|
|
|
|
cookieHeader.split(";").map((pair) => {
|
|
|
|
|
const eqIndex = pair.indexOf("=");
|
|
|
|
|
if (eqIndex === -1) {
|
|
|
|
|
return [pair.trim(), ""];
|
|
|
|
|
}
|
|
|
|
|
return [pair.slice(0, eqIndex).trim(), pair.slice(eqIndex + 1).trim()];
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for (const name of SESSION_COOKIE_NAMES) {
|
|
|
|
|
const value = cookies[name];
|
|
|
|
|
if (typeof value === "string" && value.length > 0) {
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @description Handle client disconnect by leaving the workspace room.
|
|
|
|
|
* @param client - The socket client containing workspaceId in data.
|
|
|
|
|
|