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 0000000000..8f1744c0d9 Binary files /dev/null and b/public/images/dropbox.png differ 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",