diff --git a/live/src/core/extensions/index.ts b/live/src/core/extensions/index.ts index b3ac6843fa5..4867cad3d7b 100644 --- a/live/src/core/extensions/index.ts +++ b/live/src/core/extensions/index.ts @@ -1,28 +1,26 @@ // Third-party libraries import { Redis } from "ioredis"; - // Hocuspocus extensions and core import { Database } from "@hocuspocus/extension-database"; import { Extension } from "@hocuspocus/server"; import { Logger } from "@hocuspocus/extension-logger"; import { Redis as HocusPocusRedis } from "@hocuspocus/extension-redis"; - -// Core helpers and utilities +// core helpers and utilities import { manualLogger } from "@/core/helpers/logger.js"; import { getRedisUrl } from "@/core/lib/utils/redis-url.js"; - -// Core libraries +// core libraries import { fetchPageDescriptionBinary, updatePageDescription, } from "@/core/lib/page.js"; - -// Core types -import { TDocumentTypes } from "@/core/types/common.js"; - -// Plane live libraries +// plane live libraries import { fetchDocument } from "@/plane-live/lib/fetch-document.js"; import { updateDocument } from "@/plane-live/lib/update-document.js"; +// types +import { + type HocusPocusServerContext, + type TDocumentTypes, +} from "@/core/types/common.js"; export const getExtensions: () => Promise = async () => { const extensions: Extension[] = [ @@ -33,13 +31,8 @@ export const getExtensions: () => Promise = async () => { }, }), new Database({ - fetch: async ({ - documentName: pageId, - requestHeaders, - requestParameters, - }) => { - // request headers - const cookie = requestHeaders.cookie?.toString(); + fetch: async ({ context, documentName: pageId, requestParameters }) => { + const cookie = (context as HocusPocusServerContext).cookie; // query params const params = requestParameters; const documentType = params.get("documentType")?.toString() as @@ -54,7 +47,7 @@ export const getExtensions: () => Promise = async () => { fetchedData = await fetchPageDescriptionBinary( params, pageId, - cookie + cookie, ); } else { fetchedData = await fetchDocument({ @@ -71,13 +64,12 @@ export const getExtensions: () => Promise = async () => { }); }, store: async ({ + context, state, documentName: pageId, - requestHeaders, requestParameters, }) => { - // request headers - const cookie = requestHeaders.cookie?.toString(); + const cookie = (context as HocusPocusServerContext).cookie; // query params const params = requestParameters; const documentType = params.get("documentType")?.toString() as @@ -124,7 +116,7 @@ export const getExtensions: () => Promise = async () => { } manualLogger.warn( `Redis Client wasn't able to connect, continuing without Redis (you won't be able to sync data between multiple plane live servers)`, - error + error, ); reject(error); }); @@ -138,12 +130,12 @@ export const getExtensions: () => Promise = async () => { } catch (error) { manualLogger.warn( `Redis Client wasn't able to connect, continuing without Redis (you won't be able to sync data between multiple plane live servers)`, - error + error, ); } } else { manualLogger.warn( - "Redis URL is not set, continuing without Redis (you won't be able to sync data between multiple plane live servers)" + "Redis URL is not set, continuing without Redis (you won't be able to sync data between multiple plane live servers)", ); } diff --git a/live/src/core/hocuspocus-server.ts b/live/src/core/hocuspocus-server.ts index 0aa411b9334..b34a8fbb221 100644 --- a/live/src/core/hocuspocus-server.ts +++ b/live/src/core/hocuspocus-server.ts @@ -4,6 +4,10 @@ import { v4 as uuidv4 } from "uuid"; import { handleAuthentication } from "@/core/lib/authentication.js"; // extensions import { getExtensions } from "@/core/extensions/index.js"; +// editor types +import { TUserDetails } from "@plane/editor"; +// types +import { type HocusPocusServerContext } from "@/core/types/common.js"; export const getHocusPocusServer = async () => { const extensions = await getExtensions(); @@ -12,20 +16,40 @@ export const getHocusPocusServer = async () => { name: serverName, onAuthenticate: async ({ requestHeaders, + context, // user id used as token for authentication token, }) => { - // request headers - const cookie = requestHeaders.cookie?.toString(); + let cookie: string | undefined = undefined; + let userId: string | undefined = undefined; - if (!cookie) { - throw Error("Credentials not provided"); + // Extract cookie (fallback to request headers) and userId from token (for scenarios where + // the cookies are not passed in the request headers) + try { + const parsedToken = JSON.parse(token) as TUserDetails; + userId = parsedToken.id; + cookie = parsedToken.cookie; + } catch (error) { + // If token parsing fails, fallback to request headers + console.error("Token parsing failed, using request headers:", error); + } finally { + // If cookie is still not found, fallback to request headers + if (!cookie) { + cookie = requestHeaders.cookie?.toString(); + } } + if (!cookie || !userId) { + throw new Error("Credentials not provided"); + } + + // set cookie in context, so it can be used throughout the ws connection + (context as HocusPocusServerContext).cookie = cookie; + try { await handleAuthentication({ cookie, - token, + userId, }); } catch (error) { throw Error("Authentication unsuccessful!"); diff --git a/live/src/core/lib/authentication.ts b/live/src/core/lib/authentication.ts index ee01b020908..0f679337c77 100644 --- a/live/src/core/lib/authentication.ts +++ b/live/src/core/lib/authentication.ts @@ -7,11 +7,11 @@ const userService = new UserService(); type Props = { cookie: string; - token: string; + userId: string; }; export const handleAuthentication = async (props: Props) => { - const { cookie, token } = props; + const { cookie, userId } = props; // fetch current user info let response; try { @@ -20,7 +20,7 @@ export const handleAuthentication = async (props: Props) => { manualLogger.error("Failed to fetch current user:", error); throw error; } - if (response.id !== token) { + if (response.id !== userId) { throw Error("Authentication failed: Token doesn't match the current user."); } diff --git a/live/src/core/types/common.d.ts b/live/src/core/types/common.d.ts index 3b9dff8e140..3156060efb3 100644 --- a/live/src/core/types/common.d.ts +++ b/live/src/core/types/common.d.ts @@ -2,3 +2,7 @@ import { TAdditionalDocumentTypes } from "@/plane-live/types/common.js"; export type TDocumentTypes = "project_page" | TAdditionalDocumentTypes; + +export type HocusPocusServerContext = { + cookie: string; +}; diff --git a/packages/editor/src/core/hooks/use-collaborative-editor.ts b/packages/editor/src/core/hooks/use-collaborative-editor.ts index 5a004bff284..ea3c4877ef8 100644 --- a/packages/editor/src/core/hooks/use-collaborative-editor.ts +++ b/packages/editor/src/core/hooks/use-collaborative-editor.ts @@ -39,7 +39,7 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => { name: id, parameters: realtimeConfig.queryParams, // using user id as a token to verify the user on the server - token: user.id, + token: JSON.stringify(user), url: realtimeConfig.url, onAuthenticationFailed: () => { serverHandler?.onServerError?.(); diff --git a/packages/editor/src/core/types/editor.ts b/packages/editor/src/core/types/editor.ts index 1aa85822451..bdaf70d68cf 100644 --- a/packages/editor/src/core/types/editor.ts +++ b/packages/editor/src/core/types/editor.ts @@ -138,6 +138,7 @@ export type TUserDetails = { color: string; id: string; name: string; + cookie?: string; }; export type TRealtimeConfig = {