editor: restrict attachment upload for not logged-in users

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
This commit is contained in:
01zulfi
2025-10-09 12:05:59 +05:00
parent 91ef98df4f
commit 351d412fdf
11 changed files with 62 additions and 5 deletions

View File

@@ -44,6 +44,7 @@ import {
useStore as useAppStore, useStore as useAppStore,
store as appstore store as appstore
} from "../../stores/app-store"; } from "../../stores/app-store";
import { useStore as useUserStore } from "../../stores/user-store";
import { useStore as useSearchStore } from "../../stores/search-store"; import { useStore as useSearchStore } from "../../stores/search-store";
import { AppEventManager, AppEvents } from "../../common/app-events"; import { AppEventManager, AppEvents } from "../../common/app-events";
import { FlexScrollContainer } from "../scroll-container"; import { FlexScrollContainer } from "../scroll-container";
@@ -77,6 +78,7 @@ import { Pane, SplitPane } from "../split-pane";
import { TITLE_BAR_HEIGHT } from "../title-bar"; import { TITLE_BAR_HEIGHT } from "../title-bar";
import { isMobile } from "../../hooks/use-mobile"; import { isMobile } from "../../hooks/use-mobile";
import { isTablet } from "../../hooks/use-tablet"; import { isTablet } from "../../hooks/use-tablet";
import { ConfirmDialog } from "../../dialogs/confirm";
const PDFPreview = React.lazy(() => import("../pdf-preview")); const PDFPreview = React.lazy(() => import("../pdf-preview"));
@@ -604,6 +606,15 @@ export function Editor(props: EditorProps) {
} }
}} }}
onInsertAttachment={async (type) => { onInsertAttachment={async (type) => {
if (!useUserStore.getState().isLoggedIn) {
ConfirmDialog.show({
title: strings.notLoggedIn(),
message: strings.loginToUploadAttachments(),
positiveButtonText: strings.okay()
});
return;
}
const mime = type === "file" ? "*/*" : "image/*"; const mime = type === "file" ? "*/*" : "image/*";
const attachments = await insertAttachments(mime); const attachments = await insertAttachments(mime);
const editor = useEditorManager.getState().getEditor(id)?.editor; const editor = useEditorManager.getState().getEditor(id)?.editor;

View File

@@ -51,6 +51,7 @@ import {
import { IEditor, MAX_AUTO_SAVEABLE_WORDS } from "./types"; import { IEditor, MAX_AUTO_SAVEABLE_WORDS } from "./types";
import { useEditorConfig, useToolbarConfig, useEditorManager } from "./manager"; import { useEditorConfig, useToolbarConfig, useEditorManager } from "./manager";
import { useStore as useSettingsStore } from "../../stores/setting-store"; import { useStore as useSettingsStore } from "../../stores/setting-store";
import { useStore as useUserStore } from "../../stores/user-store";
import { debounce, useAreFeaturesAvailable } from "@notesnook/common"; import { debounce, useAreFeaturesAvailable } from "@notesnook/common";
import { ScopedThemeProvider } from "../theme-provider"; import { ScopedThemeProvider } from "../theme-provider";
import { useStore as useThemeStore } from "../../stores/theme-store"; import { useStore as useThemeStore } from "../../stores/theme-store";
@@ -65,6 +66,8 @@ import { EDITOR_ZOOM } from "./common";
import { ScrollContainer } from "@notesnook/ui"; import { ScrollContainer } from "@notesnook/ui";
import { showFeatureNotAllowedToast } from "../../common/toasts"; import { showFeatureNotAllowedToast } from "../../common/toasts";
import { UpgradeDialog } from "../../dialogs/buy-dialog/upgrade-dialog"; import { UpgradeDialog } from "../../dialogs/buy-dialog/upgrade-dialog";
import { ConfirmDialog } from "../../dialogs/confirm";
import { strings } from "@notesnook/intl";
export type OnChangeHandler = ( export type OnChangeHandler = (
content: () => string, content: () => string,
@@ -185,7 +188,6 @@ function TipTap(props: TipTapProps) {
const autoSave = useRef(true); const autoSave = useRef(true);
const { toolbarConfig } = useToolbarConfig(); const { toolbarConfig } = useToolbarConfig();
const features = useAreFeaturesAvailable([ const features = useAreFeaturesAvailable([
"callout", "callout",
"outlineList", "outlineList",
@@ -196,9 +198,19 @@ function TipTap(props: TipTapProps) {
claims: { claims: {
callout: !!features?.callout?.isAllowed, callout: !!features?.callout?.isAllowed,
outlineList: !!features?.outlineList?.isAllowed, outlineList: !!features?.outlineList?.isAllowed,
taskList: !!features?.taskList?.isAllowed taskList: !!features?.taskList?.isAllowed,
insertAttachment: !!useUserStore.getState().isLoggedIn
}, },
onPermissionDenied: (claim, silent) => { onPermissionDenied: (claim, silent) => {
if (claim === "insertAttachment") {
ConfirmDialog.show({
title: strings.notLoggedIn(),
message: strings.loginToUploadAttachments(),
positiveButtonText: strings.okay()
});
return;
}
if (silent) { if (silent) {
console.log(features, features?.[claim]); console.log(features, features?.[claim]);
if (features?.[claim]) showFeatureNotAllowedToast(features[claim]); if (features?.[claim]) showFeatureNotAllowedToast(features[claim]);

View File

@@ -0,0 +1,8 @@
---
title: Login to upload attachments
description: We require users to be logged in to upload attachments.
---
# Login to upload attachments
We require users to be logged in to upload attachments. This is because attachments are encrypted using a sub-key derived from your database encryption key. Without a login, we cannot encrypt/upload/sync attachments.

View File

@@ -84,3 +84,4 @@ navigation:
children: children:
- path: faqs/what-are-merge-conflicts.md - path: faqs/what-are-merge-conflicts.md
- path: faqs/is-there-an-eta.md - path: faqs/is-there-an-eta.md
- paht: faqs/login-to-upload-attachments.md

View File

@@ -23,6 +23,7 @@ import { createNodeView } from "../react/index.js";
import { AttachmentComponent } from "./component.js"; import { AttachmentComponent } from "./component.js";
import { Attachment } from "./types.js"; import { Attachment } from "./types.js";
import { tiptapKeys } from "@notesnook/common"; import { tiptapKeys } from "@notesnook/common";
import { hasPermission } from "../../types.js";
export type AttachmentType = "image" | "file" | "camera"; export type AttachmentType = "image" | "file" | "camera";
export interface AttachmentOptions { export interface AttachmentOptions {
@@ -110,6 +111,10 @@ export const AttachmentNode = Node.create<AttachmentOptions>({
insertAttachment: insertAttachment:
(attachment) => (attachment) =>
({ commands, state }) => { ({ commands, state }) => {
if (!hasPermission("insertAttachment")) {
return false;
}
const { $from } = state.selection; const { $from } = state.selection;
const maybeAttachmentNode = state.doc.nodeAt($from.pos); const maybeAttachmentNode = state.doc.nodeAt($from.pos);
if (maybeAttachmentNode?.type === this.type) { if (maybeAttachmentNode?.type === this.type) {

View File

@@ -28,7 +28,7 @@ import { createNodeView } from "../react/index.js";
import { TextDirections } from "../text-direction/index.js"; import { TextDirections } from "../text-direction/index.js";
import { ImageComponent } from "./component.js"; import { ImageComponent } from "./component.js";
import { tiptapKeys } from "@notesnook/common"; import { tiptapKeys } from "@notesnook/common";
import { DOMParser } from "@tiptap/pm/model"; import { hasPermission } from "../../types.js";
export interface ImageOptions { export interface ImageOptions {
inline: boolean; inline: boolean;
@@ -159,6 +159,10 @@ export const ImageNode = Node.create<ImageOptions>({
insertImage: insertImage:
(options) => (options) =>
({ commands, state }) => { ({ commands, state }) => {
if (!hasPermission("insertAttachment")) {
return false;
}
const { $from } = state.selection; const { $from } = state.selection;
const maybeImageNode = state.doc.nodeAt($from.pos); const maybeImageNode = state.doc.nodeAt($from.pos);
if (maybeImageNode?.type === this.type) { if (maybeImageNode?.type === this.type) {

View File

@@ -30,7 +30,8 @@ export type PermissionHandlerOptions = {
const ClaimsMap = { const ClaimsMap = {
callout: ["setCallout"] as (keyof UnionCommands)[], callout: ["setCallout"] as (keyof UnionCommands)[],
outlineList: ["toggleOutlineList"] as (keyof UnionCommands)[], outlineList: ["toggleOutlineList"] as (keyof UnionCommands)[],
taskList: ["toggleTaskList"] as (keyof UnionCommands)[] taskList: ["toggleTaskList"] as (keyof UnionCommands)[],
insertAttachment: ["insertAttachment"] as (keyof UnionCommands)[]
}; };
export function usePermissionHandler(options: PermissionHandlerOptions) { export function usePermissionHandler(options: PermissionHandlerOptions) {

View File

@@ -25,6 +25,7 @@ import { Popup } from "../components/popup.js";
import { downloadImage, toDataURL } from "../../utils/downloader.js"; import { downloadImage, toDataURL } from "../../utils/downloader.js";
import { useToolbarStore } from "../stores/toolbar-store.js"; import { useToolbarStore } from "../stores/toolbar-store.js";
import { strings } from "@notesnook/intl"; import { strings } from "@notesnook/intl";
import { hasPermission } from "../../types.js";
export type ImageUploadPopupProps = { export type ImageUploadPopupProps = {
onInsert: (image: Partial<ImageAttributes>) => void; onInsert: (image: Partial<ImageAttributes>) => void;
@@ -46,6 +47,10 @@ export function ImageUploadPopup(props: ImageUploadPopupProps) {
title: strings.insert(), title: strings.insert(),
disabled: !url, disabled: !url,
onClick: async () => { onClick: async () => {
if (!hasPermission("insertAttachment")) {
return false;
}
setIsDownloading(true); setIsDownloading(true);
setError(undefined); setError(undefined);

View File

@@ -3699,6 +3699,10 @@ msgstr "Login successful"
msgid "Login to encrypt and sync notes" msgid "Login to encrypt and sync notes"
msgstr "Login to encrypt and sync notes" msgstr "Login to encrypt and sync notes"
#: src/strings.ts:2608
msgid "Login to upload attachments. [Read more](https://help.notesnook.com/faqs/login-to-upload-attachments)"
msgstr "Login to upload attachments. [Read more](https://help.notesnook.com/faqs/login-to-upload-attachments)"
#: src/strings.ts:541 #: src/strings.ts:541
msgid "Login to your account" msgid "Login to your account"
msgstr "Login to your account" msgstr "Login to your account"

View File

@@ -3679,6 +3679,10 @@ msgstr ""
msgid "Login to encrypt and sync notes" msgid "Login to encrypt and sync notes"
msgstr "" msgstr ""
#: src/strings.ts:2608
msgid "Login to upload attachments. [Read more](https://help.notesnook.com/faqs/login-to-upload-attachments)"
msgstr ""
#: src/strings.ts:541 #: src/strings.ts:541
msgid "Login to your account" msgid "Login to your account"
msgstr "" msgstr ""

View File

@@ -2603,5 +2603,7 @@ Use this if changes from other devices are not appearing on this device. This wi
finishPurchaseInBrowser: () => t`Finish your purchase in the browser.`, finishPurchaseInBrowser: () => t`Finish your purchase in the browser.`,
goBack: () => t`Go back`, goBack: () => t`Go back`,
clickToDirectlyClaimPromo: () => clickToDirectlyClaimPromo: () =>
t`Click here to directly claim the promotion.` t`Click here to directly claim the promotion.`,
loginToUploadAttachments: () =>
t`Login to upload attachments. [Read more](https://help.notesnook.com/faqs/login-to-upload-attachments)`
}; };