98 lines
3.0 KiB
TypeScript
98 lines
3.0 KiB
TypeScript
/**
|
|
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
* See the LICENSE file for details.
|
|
*/
|
|
|
|
// plane imports
|
|
import type { IncomingHttpHeaders } from "http";
|
|
import type { TUserDetails } from "@plane/editor";
|
|
import { logger } from "@plane/logger";
|
|
import { AppError } from "@/lib/errors";
|
|
// services
|
|
import { UserService } from "@/services/user.service";
|
|
// types
|
|
import type { HocusPocusServerContext, TDocumentTypes } from "@/types";
|
|
|
|
/**
|
|
* Authenticate the user
|
|
* @param requestHeaders - The request headers
|
|
* @param context - The context
|
|
* @param token - The token
|
|
* @returns The authenticated user
|
|
*/
|
|
export const onAuthenticate = async ({
|
|
requestHeaders,
|
|
requestParameters,
|
|
context,
|
|
token,
|
|
}: {
|
|
requestHeaders: IncomingHttpHeaders;
|
|
context: HocusPocusServerContext;
|
|
requestParameters: URLSearchParams;
|
|
token: string;
|
|
}) => {
|
|
let cookie: string | undefined = undefined;
|
|
let userId: string | undefined = undefined;
|
|
|
|
// 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) {
|
|
const appError = new AppError(error, {
|
|
context: { operation: "onAuthenticate" },
|
|
});
|
|
logger.error("Token parsing failed, using request headers", appError);
|
|
} finally {
|
|
// If cookie is still not found, fallback to request headers
|
|
if (!cookie) {
|
|
cookie = requestHeaders.cookie?.toString();
|
|
}
|
|
}
|
|
|
|
if (!cookie || !userId) {
|
|
const appError = new AppError("Credentials not provided", { code: "AUTH_MISSING_CREDENTIALS" });
|
|
logger.error("Credentials not provided", appError);
|
|
throw appError;
|
|
}
|
|
|
|
// set cookie in context, so it can be used throughout the ws connection
|
|
context.cookie = cookie ?? requestParameters.get("cookie") ?? "";
|
|
context.documentType = requestParameters.get("documentType")?.toString() as TDocumentTypes;
|
|
context.projectId = requestParameters.get("projectId");
|
|
context.userId = userId;
|
|
context.workspaceSlug = requestParameters.get("workspaceSlug");
|
|
|
|
return await handleAuthentication({
|
|
cookie: context.cookie,
|
|
userId: context.userId,
|
|
});
|
|
};
|
|
|
|
export const handleAuthentication = async ({ cookie, userId }: { cookie: string; userId: string }) => {
|
|
// fetch current user info
|
|
try {
|
|
const userService = new UserService();
|
|
const user = await userService.currentUser(cookie);
|
|
if (user.id !== userId) {
|
|
throw new AppError("Authentication unsuccessful: User ID mismatch", { code: "AUTH_USER_MISMATCH" });
|
|
}
|
|
|
|
return {
|
|
user: {
|
|
id: user.id,
|
|
name: user.display_name,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
const appError = new AppError(error, {
|
|
context: { operation: "handleAuthentication" },
|
|
});
|
|
logger.error("Authentication failed", appError);
|
|
throw new AppError("Authentication unsuccessful", { code: appError.code });
|
|
}
|
|
};
|