From 0e667c5d3d474e03571b4ee6df16d67b90643ef8 Mon Sep 17 00:00:00 2001 From: Baboon Date: Fri, 26 Jul 2024 14:47:35 +0200 Subject: [PATCH] add Dropbox embeddings support (#7299) * add Dropbox embedder support * Update embeds.ts --------- Co-authored-by: Tom Moor --- .env.sample | 4 ++++ public/images/dropbox.png | Bin 0 -> 2991 bytes server/env.ts | 7 +++++++ server/routes/embeds.ts | 34 +++++++++++++++++++++++++++++++ server/routes/index.ts | 1 + shared/editor/embeds/Dropbox.tsx | 23 +++++++++++++++++++++ shared/editor/embeds/index.tsx | 15 ++++++++++++++ 7 files changed, 84 insertions(+) create mode 100644 public/images/dropbox.png create mode 100644 shared/editor/embeds/Dropbox.tsx diff --git a/.env.sample b/.env.sample index fce81663bb..eb57ad85c6 100644 --- a/.env.sample +++ b/.env.sample @@ -189,6 +189,10 @@ SLACK_VERIFICATION_TOKEN=your_token SLACK_APP_ID=A0XXXXXXX SLACK_MESSAGE_ACTIONS=true +# For Dropbox integration, follow these instructions to get the key https://www.dropbox.com/developers/embedder#setup +# and do not forget to whitelist your domain name in the app settings +DROPBOX_APP_KEY= + # Optionally enable Sentry (sentry.io) to track errors and performance, # and optionally add a Sentry proxy tunnel for bypassing ad blockers in the UI: # https://docs.sentry.io/platforms/javascript/troubleshooting/#using-the-tunnel-option) diff --git a/public/images/dropbox.png b/public/images/dropbox.png new file mode 100644 index 0000000000000000000000000000000000000000..8f1744c0d94c6faeed8065d27539aca4c26e0a00 GIT binary patch literal 2991 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 literal 0 HcmV?d00001 diff --git a/server/env.ts b/server/env.ts index c6ffdd2c2f..84ac65f70f 100644 --- a/server/env.ts +++ b/server/env.ts @@ -348,6 +348,13 @@ export class Environment { */ public SMTP_SECURE = this.toBoolean(environment.SMTP_SECURE ?? "true"); + /** + * Dropbox app key for embedding Dropbox files + */ + @Public + @IsOptional() + public DROPBOX_APP_KEY = this.toOptionalString(environment.DROPBOX_APP_KEY); + /** * Sentry DSN for capturing errors and frontend performance. */ diff --git a/server/routes/embeds.ts b/server/routes/embeds.ts index 6a175d7516..15359823c6 100644 --- a/server/routes/embeds.ts +++ b/server/routes/embeds.ts @@ -1,5 +1,6 @@ import escape from "escape-html"; import { Context, Next } from "koa"; +import env from "@server/env"; /** * Resize observer script that sends a message to the parent window when content is resized. Inject @@ -117,5 +118,38 @@ ${resizeObserverScript(ctx)} return; } + if ( + parsed.host === "www.dropbox.com" && + parsed.protocol === "https:" && + ctx.path === "/embeds/dropbox" + ) { + const dropboxJs = "https://www.dropbox.com/static/api/2/dropins.js"; + const csp = ctx.response.get("Content-Security-Policy"); + + // Inject Dropbox domain into the script-src directive + ctx.set( + "Content-Security-Policy", + csp.replace("script-src", "script-src www.dropbox.com") + ); + ctx.set("X-Frame-Options", "sameorigin"); + + ctx.type = "html"; + ctx.body = ` + + + + +${iframeCheckScript(ctx)} + + + + +${resizeObserverScript(ctx)} + +`; + return; + } + return next(); }; diff --git a/server/routes/index.ts b/server/routes/index.ts index 77b3d8cde9..f65f478269 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -131,6 +131,7 @@ router.get("/s/:shareId/*", shareDomains(), renderShare); router.get("/embeds/gitlab", renderEmbed); router.get("/embeds/github", renderEmbed); +router.get("/embeds/dropbox", renderEmbed); // catch all for application router.get("*", shareDomains(), async (ctx, next) => { diff --git a/shared/editor/embeds/Dropbox.tsx b/shared/editor/embeds/Dropbox.tsx new file mode 100644 index 0000000000..1e230efbff --- /dev/null +++ b/shared/editor/embeds/Dropbox.tsx @@ -0,0 +1,23 @@ +import * as React from "react"; +import Frame from "../components/Frame"; +import { EmbedProps as Props } from "."; + +function Dropbox({ matches, ...props }: Props) { + // "fi" = file + // "fo" = folder + // Files need more vertical space to be readable + const embedHeight = matches[3].split("/")[0] === "fi" ? "550px" : "350px"; + + // Wrap inside an iframe to isolate external script and losened CSP + return ( + + ); +} + +export default Dropbox; diff --git a/shared/editor/embeds/index.tsx b/shared/editor/embeds/index.tsx index c25fab2fc3..9662264f96 100644 --- a/shared/editor/embeds/index.tsx +++ b/shared/editor/embeds/index.tsx @@ -1,12 +1,14 @@ import * as React from "react"; import styled from "styled-components"; import { Primitive } from "utility-types"; +import env from "../../env"; import { IntegrationService, IntegrationType } from "../../types"; import type { IntegrationSettings } from "../../types"; import { urlRegex } from "../../utils/urls"; import Image from "../components/Img"; import Berrycast from "./Berrycast"; import Diagrams from "./Diagrams"; +import Dropbox from "./Dropbox"; import Gist from "./Gist"; import GitLabSnippet from "./GitLabSnippet"; import InVision from "./InVision"; @@ -228,6 +230,19 @@ const embeds: EmbedDescriptor[] = [ `https://share.descript.com/embed/${matches[1]}`, icon: Descript, }), + ...(env.DROPBOX_APP_KEY + ? [ + new EmbedDescriptor({ + title: "Dropbox", + keywords: "file document", + regexMatch: [ + new RegExp("^https?://(www.)?dropbox.com/(s|scl)/(.*)$"), + ], + icon: Dropbox, + component: Dropbox, + }), + ] + : []), new EmbedDescriptor({ title: "Figma", keywords: "design svg vector",