diff --git a/app/scenes/Settings/Security.tsx b/app/scenes/Settings/Security.tsx index f7525e605..840822f22 100644 --- a/app/scenes/Settings/Security.tsx +++ b/app/scenes/Settings/Security.tsx @@ -5,6 +5,7 @@ import { useState } from "react"; import * as React from "react"; import { useTranslation, Trans } from "react-i18next"; import { useTheme } from "styled-components"; +import { TeamPreference } from "@shared/types"; import AuthLogo from "~/components/AuthLogo"; import ConfirmationDialog from "~/components/ConfirmationDialog"; import Flex from "~/components/Flex"; @@ -86,6 +87,17 @@ function Security() { [data, saveData] ); + const handlePreferenceChange = React.useCallback( + async (ev: React.ChangeEvent) => { + const preferences = { + ...team.preferences, + [ev.target.id]: ev.target.checked, + }; + await saveData({ preferences }); + }, + [saveData, team.preferences] + ); + const handleInviteRequiredChange = React.useCallback( async (ev: React.ChangeEvent) => { const inviteRequired = ev.target.checked; @@ -239,6 +251,19 @@ function Security() { > + + + { return true; }); -allow(User, ["read", "download"], Document, (user, document) => { +allow(User, "read", Document, (user, document) => { if (!document) { return false; } @@ -22,6 +23,26 @@ allow(User, ["read", "download"], Document, (user, document) => { return user.teamId === document.teamId; }); +allow(User, "download", Document, (user, document) => { + if (!document) { + return false; + } + + // existence of collection option is not required here to account for share tokens + if (document.collection && cannot(user, "read", document.collection)) { + return false; + } + + if ( + user.isViewer && + !user.team.getPreference(TeamPreference.ViewersCanExport, true) + ) { + return false; + } + + return user.teamId === document.teamId; +}); + allow(User, "star", Document, (user, document) => { if (!document) { return false; diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json index 69e5c1117..54bb2f1b0 100644 --- a/shared/i18n/locales/en_US/translation.json +++ b/shared/i18n/locales/en_US/translation.json @@ -730,6 +730,8 @@ "Default role": "Default role", "The default user role for new accounts. Changing this setting does not affect existing user accounts.": "The default user role for new accounts. Changing this setting does not affect existing user accounts.", "When enabled, documents can be shared publicly on the internet by any member of the workspace": "When enabled, documents can be shared publicly on the internet by any member of the workspace", + "Viewer document exports": "Viewer document exports", + "When enabled, viewers can see download options for documents": "When enabled, viewers can see download options for documents", "Rich service embeds": "Rich service embeds", "Links to supported services are shown as rich embeds within your documents": "Links to supported services are shown as rich embeds within your documents", "Collection creation": "Collection creation", diff --git a/shared/types.ts b/shared/types.ts index 9cc093aff..f56e4744a 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -59,6 +59,8 @@ export enum TeamPreference { SeamlessEdit = "seamlessEdit", /** Whether to use team logo across the app for branding. */ PublicBranding = "publicBranding", + /** Whether viewers should see download options */ + ViewersCanExport = "viewersCanExport", } export type TeamPreferences = { [key in TeamPreference]?: boolean };