[WEB-2729] chore: updated live server auth cookies handling (#5913)

* chore: updated live server auth cookies handling

* chore: update token parsing logic

* fix: types and better logical seperation between the existing two tokens

* fix: better fallback to use request headers for cookies

---------

Co-authored-by: Palanikannan M <akashmalinimurugu@gmail.com>
This commit is contained in:
Lakhan Baheti 2024-10-30 17:38:29 +05:30 committed by GitHub
parent 403482fa6e
commit 8ea34b5995
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 54 additions and 33 deletions

View file

@ -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<Extension[]> = async () => {
const extensions: Extension[] = [
@ -33,13 +31,8 @@ export const getExtensions: () => Promise<Extension[]> = 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<Extension[]> = async () => {
fetchedData = await fetchPageDescriptionBinary(
params,
pageId,
cookie
cookie,
);
} else {
fetchedData = await fetchDocument({
@ -71,13 +64,12 @@ export const getExtensions: () => Promise<Extension[]> = 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<Extension[]> = 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<Extension[]> = 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)",
);
}

View file

@ -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!");

View file

@ -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.");
}

View file

@ -2,3 +2,7 @@
import { TAdditionalDocumentTypes } from "@/plane-live/types/common.js";
export type TDocumentTypes = "project_page" | TAdditionalDocumentTypes;
export type HocusPocusServerContext = {
cookie: string;
};

View file

@ -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?.();

View file

@ -138,6 +138,7 @@ export type TUserDetails = {
color: string;
id: string;
name: string;
cookie?: string;
};
export type TRealtimeConfig = {