Skip to content

Commit

Permalink
Merge pull request #996 from internxt/feat/password-protected-sharing…
Browse files Browse the repository at this point in the history
…s-alone

[PB-1190] feature/password protected sharings alone
  • Loading branch information
CandelR authored Jan 16, 2024
2 parents ba58f48 + c39aa50 commit a5e9cb4
Show file tree
Hide file tree
Showing 17 changed files with 481 additions and 46 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,4 @@
"prettier --write"
]
}
}
}
7 changes: 7 additions & 0 deletions src/app/core/services/validation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@ const validateSearchText = (value: string): boolean => {
return alphanumericDotsAndSpaces.test(value);
};

const validatePasswordInput = (value: string): boolean => {
const latinAlphabetAndSymbols = /^[a-zA-Z0-9ñÑ ~`!@#$%^&*()_\-+={[}\]|\\:;"'<,>.?/]*$/gm;

return latinAlphabetAndSymbols.test(value);
};

const validationService = {
validate2FA,
validateSearchText,
validatePasswordInput,
};

export default validationService;
144 changes: 131 additions & 13 deletions src/app/drive/components/ShareDialog/ShareDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,18 @@ import Button from 'app/shared/components/Button/Button';
import Modal from 'app/shared/components/Modal';
import ShareInviteDialog from '../ShareInviteDialog/ShareInviteDialog';
import { useTranslationContext } from 'app/i18n/provider/TranslationProvider';
import { ArrowLeft, CaretDown, Check, CheckCircle, Globe, Link, UserPlus, Users, X } from '@phosphor-icons/react';
import {
ArrowLeft,
CaretDown,
Check,
CheckCircle,
Globe,
Link,
Question,
UserPlus,
Users,
X,
} from '@phosphor-icons/react';
import Avatar from 'app/shared/components/Avatar';
import Spinner from 'app/shared/components/Spinner/Spinner';
import { sharedThunks } from '../../../store/slices/sharedLinks';
Expand All @@ -23,6 +34,12 @@ import { AdvancedSharedItem } from '../../../share/types';
import { DriveItemData } from '../../types';
import { TrackingPlan } from '../../../analytics/TrackingPlan';
import { trackPublicShared } from '../../../analytics/services/analytics.service';
import BaseCheckbox from 'app/shared/components/forms/BaseCheckbox/BaseCheckbox';
import { SharePasswordDisableDialog } from 'app/share/components/SharePasswordDisableDialog/SharePasswordDisableDialog';
import { SharingMeta } from '@internxt/sdk/dist/drive/share/types';
import { SharePasswordInputDialog } from 'app/share/components/SharePasswordInputDialog/SharePasswordInputDialog';
import { Tooltip } from 'react-tooltip';
import { DELAY_SHOW_MS } from 'app/shared/components/Tooltip/Tooltip';

type AccessMode = 'public' | 'restricted';
type UserRole = 'owner' | 'editor' | 'reader';
Expand Down Expand Up @@ -95,6 +112,10 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [invitedUsers, setInvitedUsers] = useState<InvitedUserProps[]>([]);
const [currentUserFolderRole, setCurrentUserFolderRole] = useState<string | undefined>('');
const [isPasswordProtected, setIsPasswordProtected] = useState(false);
const [openPasswordInput, setOpenPasswordInput] = useState(false);
const [openPasswordDisableDialog, setOpenPasswordDisableDialog] = useState(false);
const [sharingMeta, setSharingMeta] = useState<SharingMeta | null>(null);

const [accessRequests, setAccessRequests] = useState<RequestProps[]>([]);
const [userOptionsEmail, setUserOptionsEmail] = useState<InvitedUserProps>();
Expand All @@ -118,6 +139,8 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => {
setUserOptionsEmail(undefined);
setUserOptionsY(0);
setView('general');
setIsPasswordProtected(false);
setSharingMeta(null);
};

useEffect(() => {
Expand All @@ -135,8 +158,8 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => {
useEffect(() => {
if (roles.length === 0) dispatch(sharedThunks.getSharedFolderRoles());

if (roles.length > 0) loadShareInfo();
}, [roles]);
if (roles.length > 0 && isOpen) loadShareInfo();
}, [roles, isOpen]);

useEffect(() => {
const removeDeniedRequests = () => {
Expand Down Expand Up @@ -175,20 +198,26 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => {
}, [itemToShare, roles]);

const loadShareInfo = async () => {
if (!itemToShare?.item) return;

setIsLoading(true);
// Change object type of itemToShare to AdvancedSharedItem
let shareAccessMode: AccessMode = 'public';
let sharingType = 'public';
let isAlreadyPasswordProtected = false;

if (props.isDriveItem) {
sharingType = (itemToShare?.item as DriveItemData & { sharings: { type: string; id: string }[] }).sharings?.[0]
?.type;
} else {
const itemType = itemToShare?.item.isFolder ? 'folder' : 'file';
const itemId = itemToShare?.item.uuid ?? '';
const itemType = itemToShare?.item.isFolder ? 'folder' : 'file';
const itemId = itemToShare?.item.uuid ?? '';

const isItemNotSharedYet =
!isAdvanchedShareItem(itemToShare?.item) && !itemToShare.item.sharings?.length && !sharingMeta;

if (!isItemNotSharedYet) {
try {
const sharingData = await shareService.getSharingType(itemId, itemType);
sharingType = sharingData.type;
isAlreadyPasswordProtected = sharingData.encryptedPassword !== null;
setSharingMeta(sharingData);
} catch (error) {
errorService.reportError(error);
}
Expand All @@ -198,8 +227,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => {
shareAccessMode = 'restricted';
}
setAccessMode(shareAccessMode);

if (!itemToShare?.item) return;
setIsPasswordProtected(isAlreadyPasswordProtected);

try {
await getAndUpdateInvitedUsers();
Expand Down Expand Up @@ -275,11 +303,14 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => {

trackPublicShared(trackingPublicSharedProperties);
const encryptionKey = isAdvanchedShareItem(itemToShare.item) ? itemToShare?.item?.encryptionKey : undefined;
await shareService.getPublicShareLink(
const sharingInfo = await shareService.getPublicShareLink(
itemToShare?.item.uuid,
itemToShare.item.isFolder ? 'folder' : 'file',
encryptionKey,
);
if (sharingInfo) {
setSharingMeta(sharingInfo);
}
props.onShareItem?.();
closeSelectedUserPopover();
}
Expand Down Expand Up @@ -308,6 +339,51 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => {
closeSelectedUserPopover();
};

const onPasswordCheckboxChange = useCallback(() => {
if (!isPasswordProtected) {
setOpenPasswordInput(true);
} else {
setOpenPasswordDisableDialog(true);
}
}, [isPasswordProtected]);

const onSavePublicSharePassword = useCallback(
async (plainPassword: string) => {
try {
let sharingInfo = sharingMeta;

if (!sharingInfo?.encryptedCode) {
const itemType = itemToShare?.item.isFolder ? 'folder' : 'file';
const itemId = itemToShare?.item.uuid ?? '';
sharingInfo = await shareService.createPublicShareFromOwnerUser(itemId, itemType, plainPassword);
setSharingMeta(sharingInfo);
} else {
await shareService.saveSharingPassword(sharingInfo.id, plainPassword, sharingInfo.encryptedCode);
}

setIsPasswordProtected(true);
} catch (error) {
errorService.castError(error);
} finally {
setOpenPasswordInput(false);
}
},
[sharingMeta, itemToShare],
);

const onDisablePassword = useCallback(async () => {
try {
if (sharingMeta) {
await shareService.removeSharingPassword(sharingMeta.id);
setIsPasswordProtected(false);
}
} catch (error) {
errorService.castError(error);
} finally {
setOpenPasswordDisableDialog(false);
}
}, [sharingMeta]);

const changeAccess = async (mode: AccessMode) => {
closeSelectedUserPopover();
if (mode != accessMode) {
Expand All @@ -319,7 +395,9 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => {

await shareService.updateSharingType(itemId, itemType, sharingType);
if (sharingType === 'public') {
await shareService.createPublicShareFromOwnerUser(itemId, itemType);
const shareInfo = await shareService.createPublicShareFromOwnerUser(itemId, itemType);
setSharingMeta(shareInfo);
setIsPasswordProtected(false);
}
setAccessMode(mode);
} catch (error) {
Expand Down Expand Up @@ -494,6 +572,34 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => {

<div className="h-px w-full bg-gray-5" />

{accessMode === 'public' && !isLoading && isUserOwner && (
<div className="flex items-end justify-between align-middle">
<div className="flex flex-col space-y-2.5">
<div className="flex items-center">
<BaseCheckbox checked={isPasswordProtected} onClick={onPasswordCheckboxChange} />
<p className="ml-2 select-none text-base font-medium">
{translate('modals.shareModal.protectSharingModal.protect')}
</p>
<Question
size={14}
className="ml-2 flex items-center justify-center font-medium"
data-tooltip-id="uploadFolder-tooltip"
data-tooltip-place="top"
/>
<Tooltip id="uploadFolder-tooltip" delayShow={DELAY_SHOW_MS} className="z-40 rounded-md">
<p className="break-word w-60 text-center text-white">
{translate('modals.shareModal.protectSharingModal.protectTooltipText')}
</p>
</Tooltip>
</div>
</div>
{isPasswordProtected && (
<Button variant="secondary" onClick={() => setOpenPasswordInput(true)}>
<span>{translate('modals.shareModal.protectSharingModal.buttons.changePassword')}</span>
</Button>
)}
</div>
)}
<div className="flex items-end justify-between">
<div className="flex flex-col space-y-2.5">
<p className="font-medium">{translate('modals.shareModal.general.generalAccess')}</p>
Expand Down Expand Up @@ -603,6 +709,18 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => {
</Button>
</div>

<SharePasswordInputDialog
onClose={() => setOpenPasswordInput(false)}
isOpen={openPasswordInput}
onSavePassword={onSavePublicSharePassword}
isAlreadyProtected={isPasswordProtected}
/>
<SharePasswordDisableDialog
isOpen={openPasswordDisableDialog}
onClose={() => setOpenPasswordDisableDialog(false)}
onConfirmHandler={onDisablePassword}
/>

{/* Stop sharing confirmation dialog */}
<Modal
maxWidth="max-w-sm"
Expand Down
13 changes: 13 additions & 0 deletions src/app/i18n/locales/cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,19 @@
"change": "改变",
"requestSent": "请求已发送",
"confirmation": "如果与您共享文件/文件夹 您将收到一封电子邮件"
},
"protectSharingModal": {
"protectTooltipText": "仅对没有查看或编辑访问权限的用户需要密码。",
"protect": "使用密码保护",
"disablePasswordTitle": "禁用密码保护?",
"disablePasswordBody": "禁用后,拥有链接的人将能够访问内容。",
"editPasswordTitle": "编辑密码",
"buttons": {
"cancel": "取消",
"save": "保存",
"disable": "禁用",
"changePassword": "更改密码"
}
}
},
"sharedInvitationsModal": {
Expand Down
13 changes: 13 additions & 0 deletions src/app/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,19 @@
"change": "Ändern",
"requestSent": "Anfrage gesendet",
"confirmation": "Sie erhalten eine E-Mail, wenn die Datei/der Ordner für Sie freigegeben wurde"
},
"protectSharingModal": {
"protectTooltipText": "Ein Passwort ist nur für Benutzer erforderlich, die keine Viewer- oder Bearbeitungsberechtigungen für das freigegebene Element haben.",
"protect": "Mit Passwort schützen",
"disablePasswordTitle": "Passwortschutz deaktivieren?",
"disablePasswordBody": "Wenn deaktiviert, können Personen mit dem Link auf den Inhalt zugreifen.",
"editPasswordTitle": "Passwort bearbeiten",
"buttons": {
"cancel": "Abbrechen",
"save": "Speichern",
"disable": "Deaktivieren",
"changePassword": "Passwort ändern"
}
}
},
"deletePhotosModal": {
Expand Down
15 changes: 14 additions & 1 deletion src/app/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@
"putPwd": "Please enter the password provided",
"putPwd1": "by the sender for access.",
"password": "Password",
"pwdLayout": "password",
"pwdLayout": "Password",
"access": "Access"
},
"sideNav": {
Expand Down Expand Up @@ -767,6 +767,19 @@
"change": "Change",
"requestSent": "Request Sent",
"confirmation": "You will get an email if the file/folder is shared with you"
},
"protectSharingModal": {
"protectTooltipText": "Password is only required for users without viewer or editor access to the shared item.",
"protect": "Protect with password",
"disablePasswordTitle": "Disable password protection?",
"disablePasswordBody": "When disabled, people with the link will be able to access the content.",
"editPasswordTitle": "Edit password",
"buttons": {
"cancel": "Cancel",
"save": "Save",
"disable": "Disable",
"changePassword": "Change password"
}
}
},
"sharedInvitationsModal": {
Expand Down
13 changes: 13 additions & 0 deletions src/app/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,19 @@
"change": "Cambiar",
"requestSent": "Solicitud enviada",
"confirmation": "Recibirás un correo electrónico si el archivo/carpeta se comparte"
},
"protectSharingModal": {
"protectTooltipText": "La contraseña solo es necesaria para los usuarios sin acceso de visor o editor al elemento compartido.",
"protect": "Proteger con contraseña",
"disablePasswordTitle": "¿Desactivar la protección con contraseña?",
"disablePasswordBody": "Cuando está desactivada, las personas con el enlace podrán acceder al contenido.",
"editPasswordTitle": "Editar contraseña",
"buttons": {
"cancel": "Cancelar",
"save": "Guardar",
"disable": "Desactivar",
"changePassword": "Cambiar contraseña"
}
}
},
"sharedInvitationsModal": {
Expand Down
13 changes: 13 additions & 0 deletions src/app/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,19 @@
"change": "Changer",
"requestSent": "Demande envoyée",
"confirmation": "Vous recevrez un e-mail si le fichier/dossier est partagé avec vous"
},
"protectSharingModal": {
"protectTooltipText": "Le mot de passe est uniquement requis pour les utilisateurs sans accès en lecture ou en modification à l'élément partagé.",
"protect": "Protéger par mot de passe",
"disablePasswordTitle": "Désactiver la protection par mot de passe ?",
"disablePasswordBody": "Lorsqu'elle est désactivée, les personnes disposant du lien pourront accéder au contenu.",
"editPasswordTitle": "Modifier le mot de passe",
"buttons": {
"cancel": "Annuler",
"save": "Enregistrer",
"disable": "Désactiver",
"changePassword": "Changer le mot de passe"
}
}
},
"sharedInvitationsModal": {
Expand Down
Loading

1 comment on commit a5e9cb4

@vercel
Copy link

@vercel vercel bot commented on a5e9cb4 Jan 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.