Doceditor: show access denied and file not found errors in doceditor

This commit is contained in:
Timofey Boyko 2024-04-02 09:47:50 +03:00
parent b7b9b7ea7b
commit c5d6489d7f
13 changed files with 190 additions and 117 deletions

View File

@ -11,7 +11,7 @@
"deploy": "shx --silent mkdir -p ../../../publish/web/editor && shx --silent mkdir -p ../../../publish/web/editor/.next && shx --silent mkdir -p ../../../publish/web/editor/config && shx cp -r config/* ../../../publish/web/editor/config && shx --silent mkdir -p ../../../publish/web/editor/node_modules && shx --silent mkdir -p ../../../publish/web/editor/.next/static && shx cp -r .next/standalone/node_modules/* ../../../publish/web/editor/node_modules && shx cp -r .next/static/* ../../../publish/web/editor/.next/static && shx cp -r .next/standalone/packages/doceditor/.next/* ../../../publish/web/editor/.next && shx cp -f server.prod.js ../../../publish/web/editor/server.js"
},
"dependencies": {
"@onlyoffice/document-editor-react": "^1.5.0",
"@onlyoffice/document-editor-react": "file:./onlyoffice-document-editor-react-1.5.1.tgz",
"i18next": "^20.6.1",
"next": "14.0.4",
"react": "^18.2.0",

View File

@ -58,6 +58,7 @@ const Editor = ({
documentserverUrl,
fileInfo,
isSharingAccess,
errorMessage,
t,
onSDKRequestSharingSettings,
onSDKRequestSaveAs,
@ -91,6 +92,7 @@ const Editor = ({
fileInfo,
config,
doc,
errorMessage,
t,
});
@ -104,23 +106,25 @@ const Editor = ({
t,
});
const newConfig: IConfig = {
document: config.document,
documentType: config.documentType,
token: config.token,
type: config.type,
};
const newConfig: IConfig = config
? {
document: config.document,
documentType: config.documentType,
token: config.token,
type: config.type,
}
: {};
newConfig.editorConfig = { ...config.editorConfig };
if (config) newConfig.editorConfig = { ...config.editorConfig };
const search = typeof window !== "undefined" ? window.location.search : "";
const editorType = new URLSearchParams(search).get("editorType");
//if (view && newConfig.editorConfig) newConfig.editorConfig.mode = "view";
if (editorType) config.type = editorType;
if (editorType) newConfig.type = editorType;
if (isMobile) config.type = "mobile";
if (isMobile) newConfig.type = "mobile";
let goBack: TGoBack = {} as TGoBack;
@ -229,7 +233,7 @@ const Editor = ({
newConfig.events.onRequestSharingSettings = onSDKRequestSharingSettings;
}
if (!fileInfo.providerKey) {
if (!fileInfo?.providerKey) {
newConfig.events.onRequestReferenceData = onSDKRequestReferenceData;
if (!IS_ZOOM) {
@ -237,16 +241,16 @@ const Editor = ({
}
}
if (fileInfo.security.Rename) {
if (fileInfo?.security.Rename) {
newConfig.events.onRequestRename = (obj: object) =>
onSDKRequestRename(obj, fileInfo.id);
}
if (fileInfo.security.ReadHistory) {
if (fileInfo?.security.ReadHistory) {
newConfig.events.onRequestHistory = onSDKRequestHistory;
}
if (fileInfo.security.EditHistory) {
if (fileInfo?.security.EditHistory) {
newConfig.events.onRequestRestore = onSDKRequestRestore;
}
@ -261,7 +265,15 @@ const Editor = ({
<DocumentEditor
id={"docspace_editor"}
documentServerUrl={documentserverUrl}
config={newConfig}
config={
errorMessage
? {
events: {
onAppReady: onSDKAppReady,
},
}
: newConfig
}
height="100%"
width="100%"
events_onDocumentReady={onDocumentReady}
@ -270,4 +282,3 @@ const Editor = ({
};
export default Editor;

View File

@ -65,6 +65,7 @@ import Editor from "./Editor";
import SelectFileDialog from "./SelectFileDialog";
import SelectFolderDialog from "./SelectFolderDialog";
import SharingDialog from "./ShareDialog";
import { EditorConfigErrorType } from "@docspace/shared/enums";
const Root = ({
settings,
@ -84,6 +85,11 @@ const Root = ({
settings?.firebase ?? ({} as TFirebaseSettings),
);
const instanceId = config?.document?.referenceData.instanceId;
const isSkipError =
error?.status === "not-found" ||
error?.status === "access-denied" ||
error?.type === EditorConfigErrorType.AccessDeniedScope ||
error?.type === EditorConfigErrorType.NotFoundScope;
useRootInit({
documentType: config?.documentType,
@ -152,7 +158,7 @@ const Root = ({
i18n={i18n}
onError={onError}
>
{!fileId ? (
{!fileId || false ? (
<AppLoader />
) : isShowDeepLink ? (
<DeepLink
@ -164,7 +170,7 @@ const Root = ({
deepLinkConfig={settings?.deepLink}
setIsShowDeepLink={setIsShowDeepLink}
/>
) : error && error.message === "restore-backup" ? (
) : error && error.message === "restore-backup" && !isSkipError ? (
<StyledComponentsRegistry>
<ErrorContainer
headerText={t?.("Common:Error")}
@ -172,7 +178,7 @@ const Root = ({
isEditor
/>
</StyledComponentsRegistry>
) : error && error.message !== "unauthorized" ? (
) : error && error.message !== "unauthorized" && !isSkipError ? (
<Error520SSR
i18nProp={i18n}
errorLog={error as Error}
@ -184,7 +190,7 @@ const Root = ({
/>
) : isShowDeepLink ? null : (
<div style={{ width: "100%", height: "100%" }}>
{config && documentserverUrl && fileInfo && (
{documentserverUrl && (
<Editor
config={config}
user={user}
@ -195,6 +201,7 @@ const Root = ({
t={t}
documentserverUrl={documentserverUrl}
fileInfo={fileInfo}
errorMessage={error?.message}
onSDKRequestSharingSettings={onSDKRequestSharingSettings}
onSDKRequestSaveAs={onSDKRequestSaveAs}
onSDKRequestInsertImage={onSDKRequestInsertImage}

View File

@ -77,6 +77,7 @@ const useEditorEvents = ({
fileInfo,
config,
doc,
errorMessage,
t,
}: UseEventsProps) => {
const [events, setEvents] = React.useState<IConfigEvents>({});
@ -110,7 +111,7 @@ const useEditorEvents = ({
instanceId: reference.referenceData
? reference.referenceData.instanceId
: "",
fileId: fileInfo.id,
fileId: fileInfo?.id,
path: reference.path || "",
};
@ -131,12 +132,14 @@ const useEditorEvents = ({
);
}
},
[fileInfo.id, t],
[fileInfo?.id, t],
);
const onSDKAppReady = React.useCallback(() => {
docEditor = window.DocEditor.instances[EDITOR_ID];
if (errorMessage) return docEditor?.showMessage?.(errorMessage);
console.log("ONLYOFFICE Document Editor is ready", docEditor);
const url = window.location.href;
@ -154,7 +157,7 @@ const useEditorEvents = ({
if (config?.Error) docEditor?.showMessage?.(config.Error);
}
}
}, [config.Error]);
}, [config?.Error, errorMessage]);
const onDocumentReady = React.useCallback(() => {
// console.log("onDocumentReady", arguments, { docEditor });
@ -174,7 +177,7 @@ const useEditorEvents = ({
["ASC", "Files", "Editor", "docEditor"],
docEditor,
); //Do not remove: it's for Back button on Mobile App
}, [config.errorMessage]);
}, [config?.errorMessage]);
const getBackUrl = React.useCallback(() => {
if (!fileInfo) return;
@ -257,6 +260,8 @@ const useEditorEvents = ({
const onSDKRequestCreateNew = React.useCallback(() => {
const defaultFileName = getDefaultFileName(true);
if (!fileInfo?.folderId) return;
createFile(fileInfo.folderId, defaultFileName ?? "")
?.then((newFile) => {
const newUrl = combineUrl(
@ -271,7 +276,7 @@ const useEditorEvents = ({
.catch((e) => {
toastr.error(e);
});
}, [fileInfo.folderId, getDefaultFileName]);
}, [fileInfo?.folderId, getDefaultFileName]);
const getDocumentHistory = React.useCallback(
(fileHistory: TEditHistory[], historyLength: number) => {
@ -287,14 +292,14 @@ const useEditorEvents = ({
changesModified.forEach((item) => {
item.created = `${new Date(item.created).toLocaleString(
config.editorConfig.lang,
config?.editorConfig.lang,
)}`;
});
let obj = {
...(changes.length !== 0 && { changes: changesModified }),
created: `${new Date(fileHistory[i].created).toLocaleString(
config.editorConfig.lang,
config?.editorConfig.lang,
)}`,
...(serverVersion && { serverVersion }),
key: fileHistory[i].key,
@ -310,13 +315,14 @@ const useEditorEvents = ({
}
return result;
},
[config.editorConfig.lang],
[config?.editorConfig.lang],
);
const onSDKRequestRestore = React.useCallback(
async (event: object) => {
const restoreVersion = (event as TEvent).data.version;
if (!fileInfo?.id) return;
try {
const updateVersions = await restoreDocumentsVersion(
fileInfo.id,
@ -351,7 +357,7 @@ const useEditorEvents = ({
});
}
},
[doc, fileInfo.id, getDocumentHistory],
[doc, fileInfo?.id, getDocumentHistory],
);
const onSDKRequestHistory = React.useCallback(async () => {
@ -362,6 +368,8 @@ const useEditorEvents = ({
// shareIndex > -1 ? search.substring(shareIndex + 6) : null;
// const docIdx = search.indexOf("doc=");
if (!fileInfo?.id) return;
const fileHistory = await getEditHistory(fileInfo.id, doc ?? "");
const historyLength = fileHistory.length;
@ -386,7 +394,7 @@ const useEditorEvents = ({
error: `${errorMessage}`, //TODO: maybe need to display something else.
});
}
}, [doc, fileInfo.id, getDocumentHistory]);
}, [doc, fileInfo?.id, getDocumentHistory]);
const onSDKRequestSendNotify = React.useCallback(
async (event: object) => {
@ -396,6 +404,7 @@ const useEditorEvents = ({
const comment = currEvent.data.message;
const emails = currEvent.data.emails;
if (!fileInfo?.id) return;
try {
await sendEditorNotify(
fileInfo.id,
@ -425,7 +434,7 @@ const useEditorEvents = ({
toastr.error(e as TData);
}
},
[fileInfo.id, t, usersInRoom],
[fileInfo?.id, t, usersInRoom],
);
const onSDKRequestUsers = React.useCallback(
@ -433,6 +442,7 @@ const useEditorEvents = ({
try {
const currEvent = event as TEvent;
const c = currEvent?.data?.c;
if (!fileInfo?.id) return;
const users = await (c == "protect"
? getProtectUsers(fileInfo.id)
: getSharedUsers(fileInfo.id));
@ -460,7 +470,7 @@ const useEditorEvents = ({
);
}
},
[fileInfo.id, t],
[fileInfo?.id, t],
);
const onSDKRequestHistoryData = React.useCallback(
@ -472,7 +482,7 @@ const useEditorEvents = ({
// const shareIndex = search.indexOf("share=");
// const requestToken =
// shareIndex > -1 ? search.substring(shareIndex + 6) : null;
if (!fileInfo?.id) return;
const versionDifference = await getEditDiff(
fileInfo.id,
version,
@ -520,7 +530,7 @@ const useEditorEvents = ({
});
}
},
[doc, fileInfo.id],
[doc, fileInfo?.id],
);
const onDocumentStateChange = React.useCallback(
@ -533,21 +543,21 @@ const useEditorEvents = ({
docSaved
? setDocumentTitle(
docTitle,
config.document.fileType,
config?.document.fileType ?? "",
documentReady,
successAuth,
successAuth ?? false,
setDocTitle,
)
: setDocumentTitle(
`*${docTitle}`,
config.document.fileType,
config?.document.fileType ?? "",
documentReady,
successAuth,
successAuth ?? false,
setDocTitle,
);
}, 500);
},
[config.document.fileType, docSaved, docTitle, documentReady, successAuth],
[config?.document.fileType, docSaved, docTitle, documentReady, successAuth],
);
const onMetaChange = React.useCallback(
@ -558,15 +568,15 @@ const useEditorEvents = ({
if (newTitle && newTitle !== docTitle) {
setDocumentTitle(
newTitle,
config.document.fileType,
config?.document.fileType ?? "",
documentReady,
successAuth,
successAuth ?? false,
setDocTitle,
);
setDocTitle(newTitle);
}
},
[config.document.fileType, docTitle, documentReady, successAuth],
[config?.document.fileType, docTitle, documentReady, successAuth],
);
const onMakeActionLink = React.useCallback((event: object) => {

View File

@ -57,16 +57,16 @@ const useInit = ({
config.document.title,
config.document.fileType,
documentReady,
successAuth,
successAuth ?? false,
setDocTitle,
);
}, [config, documentReady, fileInfo, setDocTitle, successAuth]);
React.useEffect(() => {
if (config && IS_DESKTOP_EDITOR) {
if (config && IS_DESKTOP_EDITOR && user && fileInfo?.id) {
initDesktop(config, user, fileInfo.id, t);
}
}, [config, fileInfo.id, t, user]);
}, [config, fileInfo?.id, t, user]);
React.useEffect(() => {
try {
@ -91,4 +91,3 @@ const useInit = ({
};
export default useInit;

View File

@ -40,7 +40,7 @@ export interface UseThemeProps {
i18n?: i18n;
}
const useTheme = ({ user, i18n = {} }: UseThemeProps) => {
const useTheme = ({ user, i18n }: UseThemeProps) => {
const [currentColorTheme, setCurrentColorTheme] =
React.useState<TColorScheme>({} as TColorScheme);
@ -67,7 +67,7 @@ const useTheme = ({ user, i18n = {} }: UseThemeProps) => {
const getUserTheme = React.useCallback(() => {
if (!user?.theme) return;
let theme = user.theme;
const interfaceDirection = i18n.dir ? i18n.dir() : "ltr";
const interfaceDirection = i18n?.dir ? i18n.dir() : "ltr";
if (user.theme === ThemeKeys.SystemStr) theme = SYSTEM_THEME;
@ -86,7 +86,7 @@ const useTheme = ({ user, i18n = {} }: UseThemeProps) => {
currentColorScheme: currentColorTheme,
interfaceDirection,
});
}, [currentColorTheme, user?.theme, i18n.dir]);
}, [user?.theme, i18n, currentColorTheme]);
React.useEffect(() => {
getCurrentColorTheme();

View File

@ -170,7 +170,8 @@ export interface IInitialConfig {
export type TError = {
message: "unauthorized" | "restore-backup" | string;
status?: number | string;
status?: "not-found" | "access-denied" | number | string;
type?: string;
};
export type TResponse =
@ -200,14 +201,15 @@ export type TResponse =
};
export type EditorProps = {
config: IInitialConfig;
successAuth: boolean;
user: TUser;
view: boolean;
config?: IInitialConfig;
successAuth?: boolean;
user?: TUser;
view?: boolean;
doc?: string;
documentserverUrl: string;
fileInfo: TFile;
isSharingAccess: boolean;
fileInfo?: TFile;
isSharingAccess?: boolean;
errorMessage?: string;
t: TTranslation | null;
onSDKRequestSharingSettings: () => void;
onSDKRequestSaveAs: (event: object) => void;
@ -316,19 +318,20 @@ export interface UseSocketHelperProps {
}
export interface UseEventsProps {
user: TUser;
successAuth: boolean;
fileInfo: TFile;
config: IInitialConfig;
user?: TUser;
successAuth?: boolean;
fileInfo?: TFile;
config?: IInitialConfig;
doc?: string;
errorMessage?: string;
t?: Nullable<TTranslation>;
}
export interface UseInitProps {
config: IInitialConfig;
successAuth: boolean;
fileInfo: TFile;
user: TUser;
config?: IInitialConfig;
successAuth?: boolean;
fileInfo?: TFile;
user?: TUser;
t: Nullable<TTranslation>;
setDocTitle: (value: string) => void;

View File

@ -29,55 +29,16 @@
import { headers } from "next/headers";
import { TenantStatus, EditorConfigErrorType } from "@docspace/shared/enums";
import {
createRequest,
getBaseUrl,
} from "@docspace/shared/utils/next-ssr-helper";
import type { TDocServiceLocation } from "@docspace/shared/api/files/types";
import type { IInitialConfig, TCatchError, TError, TResponse } from "@/types";
import { isTemplateFile } from ".";
const API_PREFIX = "api/2.0";
export const getBaseUrl = () => {
const hdrs = headers();
const host = hdrs.get("x-forwarded-host");
const proto = hdrs.get("x-forwarded-proto");
const baseURL = `${proto}://${host}`;
return baseURL;
};
export const getAPIUrl = () => {
const baseUrl = getBaseUrl();
const baseAPIUrl = `${baseUrl}/${API_PREFIX}`;
return baseAPIUrl;
};
export const createRequest = (
paths: string[],
newHeaders: [string, string][],
method: string,
body?: string,
) => {
const hdrs = new Headers(headers());
const apiURL = getAPIUrl();
newHeaders.forEach((hdr) => {
if (hdr[0]) hdrs.set(hdr[0], hdr[1]);
});
const urls = paths.map((path) => `${apiURL}${path}`);
const requests = urls.map(
(url) => new Request(url, { headers: hdrs, method, body }),
);
return requests;
};
export async function getErrorData() {
const hdrs = headers();
const cookie = hdrs.get("cookie");
@ -138,7 +99,7 @@ const processFillFormDraft = async (
if (!response.ok) return;
const { response: formUrl, ...rest } = await response.json();
const { response: formUrl } = await response.json();
const basePath = getBaseUrl();
const url = new URL(basePath + formUrl);
@ -405,22 +366,33 @@ export async function getData(
console.log("initDocEditor failed", config.error);
const status =
config.error.type === EditorConfigErrorType.NotFoundScope
? "not-found"
: config.error.type === EditorConfigErrorType.AccessDeniedScope
? "access-denied"
: undefined;
const message = status ? config.error.message : undefined;
const response: TResponse = {
error:
user || share
? config.error.type === EditorConfigErrorType.LinkScope
? { message: "unauthorized" }
? { message: message ?? "unauthorized", status }
: config.error
: { message: "unauthorized" },
: { message: message ?? "unauthorized", status },
user: user?.response,
settings: settings?.response,
fileId,
editorUrl: editorUrl.response,
};
return response;
} catch (e) {
const err = e as TCatchError;
console.error("initDocEditor failed", err);
let message = "";
if (typeof err === "string") message = err;
else

View File

@ -72,7 +72,7 @@ export const onSDKRequestHistoryClose = () => {
document.location.reload();
};
export const onSDKRequestEditRights = async (fileInfo: TFile) => {
export const onSDKRequestEditRights = async (fileInfo?: TFile) => {
console.log("ONLYOFFICE Document Editor requests editing rights");
const url = window.location.href;
@ -106,4 +106,3 @@ export const onSDKRequestRename = async (
const title = (event as TRenameEvent).data;
await updateFile(id, title);
};

View File

@ -510,4 +510,6 @@ export const enum WhiteLabelLogoType {
export const enum EditorConfigErrorType {
System = "System.Exception",
LinkScope = "ASC.Files.Core.Exceptions.LinkScopeException",
NotFoundScope = "System.IO.FileNotFoundException",
AccessDeniedScope = "System.Security.SecurityException",
}

View File

@ -0,0 +1,70 @@
// (c) Copyright Ascensio System SIA 2009-2024
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import { headers } from "next/headers";
const API_PREFIX = "api/2.0";
export const getBaseUrl = () => {
const hdrs = headers();
const host = hdrs.get("x-forwarded-host");
const proto = hdrs.get("x-forwarded-proto");
const baseURL = `${proto}://${host}`;
return baseURL;
};
export const getAPIUrl = () => {
const baseUrl = getBaseUrl();
const baseAPIUrl = `${baseUrl}/${API_PREFIX}`;
return baseAPIUrl;
};
export const createRequest = (
paths: string[],
newHeaders: [string, string][],
method: string,
body?: string,
) => {
const hdrs = new Headers(headers());
const apiURL = getAPIUrl();
newHeaders.forEach((hdr) => {
if (hdr[0]) hdrs.set(hdr[0], hdr[1]);
});
const urls = paths.map((path) => `${apiURL}${path}`);
const requests = urls.map(
(url) => new Request(url, { headers: hdrs, method, body }),
);
return requests;
};

View File

@ -3163,7 +3163,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@docspace/doceditor@workspace:packages/doceditor"
dependencies:
"@onlyoffice/document-editor-react": "npm:^1.5.0"
"@onlyoffice/document-editor-react": "file:./onlyoffice-document-editor-react-1.5.1.tgz"
"@svgr/webpack": "npm:^8.1.0"
"@types/node": "npm:^20"
"@types/react": "npm:^18"
@ -5224,15 +5224,15 @@ __metadata:
languageName: node
linkType: hard
"@onlyoffice/document-editor-react@npm:^1.5.0":
version: 1.5.0
resolution: "@onlyoffice/document-editor-react@npm:1.5.0"
"@onlyoffice/document-editor-react@file:./onlyoffice-document-editor-react-1.5.1.tgz::locator=%40docspace%2Fdoceditor%40workspace%3Apackages%2Fdoceditor":
version: 1.5.1
resolution: "@onlyoffice/document-editor-react@file:./onlyoffice-document-editor-react-1.5.1.tgz#./onlyoffice-document-editor-react-1.5.1.tgz::hash=0845a7&locator=%40docspace%2Fdoceditor%40workspace%3Apackages%2Fdoceditor"
dependencies:
lodash: "npm:4.17.21"
peerDependencies:
react: ^16.9.0 || ^17 || ^18
react-dom: ^16.9.0 || ^17 || ^18
checksum: 2214ba9982f70c7facbcd2b778bfaf92d90ff4fd71dd36697d2ac7f15b9a5d271872954a6fac84db04c16e9976ae20c7196586bef34addde7b65ce19e8a379b4
checksum: d7f1ac9ed3510484a8622da828d4a5edad127d4f8af66cd55990253557ad10dc0100b3fb8b39e696a00ee51e3ca9b64f7c031d50ad540f37cd3bdb7d8cb0f8e0
languageName: node
linkType: hard