Skip to content

Commit

Permalink
feat: add two steps confirmation support in sharing component
Browse files Browse the repository at this point in the history
When Alice share a password to Bob and this sharing has been accepted
by Bob, then Alice should `confirm` the identity of Bob by checking his
fingerprint phrase

This commit implements this feature and is based on mechanisms from
cozy/cozy-libs#1340
  • Loading branch information
Ldoppea committed Sep 3, 2021
1 parent fb754aa commit 3b1b03a
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 2 deletions.
104 changes: 104 additions & 0 deletions src/cozy/wrappers/sharing/sharing.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Component, Input, ViewEncapsulation } from '@angular/core';
import { OrganizationUserStatusType } from 'jslib/enums/organizationUserStatusType';
import { OrganizationUserType } from 'jslib/enums/organizationUserType';
import { Utils } from 'jslib/misc/utils';
import { OrganizationUserConfirmRequest } from 'jslib/models/request/organizationUserConfirmRequest';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { AngularWrapperComponent, AngularWrapperProps } from '../angular-wrapper.component';
Expand All @@ -14,8 +18,24 @@ interface File {
_id: string;
}

interface ConfirmationMethods {
getRecipientsToBeConfirmed: (organizationId: string) => User[];
confirmRecipient: (organizationId: string) => Promise<void>;
rejectRecipient: (organizationId: string) => Promise<void>;
}

interface SharingProps extends AngularWrapperProps {
file: File;
confirmationMethods: ConfirmationMethods;
}

interface User {
name: string;
id: string;
email: string;
publicKey: string;
fingerprint: string[];
fingerprintPhrase: string;
}

@Component({
Expand Down Expand Up @@ -45,6 +65,7 @@ export class SharingComponent extends AngularWrapperComponent {
_type: 'com.bitwarden.organizations',
_id: currentCollection.organizationId,
},
confirmationMethods: this.getTwoStepsConfirmationMethods(),
};
}

Expand All @@ -58,4 +79,87 @@ export class SharingComponent extends AngularWrapperComponent {
this.getRootDomNode()
);
}

/************************/
/* Confirmation Methods */
/************************/

protected async loadOrganizationUsersToBeConfirmed(organizationId: string) {
const organizationUsers = await this.apiService.getOrganizationUsers(organizationId);

const currentUserId = await this.userService.getUserId();

const isOwner = organizationUsers.data.find(user => {
return user.type === OrganizationUserType.Owner
&& user.id === currentUserId;
});

if (!isOwner) {
return [];
}

const invitedUsers = organizationUsers.data.filter(user => {
return user.type === OrganizationUserType.User
&& user.status === OrganizationUserStatusType.Accepted
&& user.id !== currentUserId;
});

const finalUsers: User[] = [];
for (const user of invitedUsers) {
const publicKey = (await this.apiService.getUserPublicKey(user.id)).publicKey;

const fingerprint = await this.cryptoService.getFingerprint(user.id, Utils.fromB64ToArray(publicKey).buffer);

finalUsers.push({
name: user.name,
id: user.id,
email: user.email,
publicKey: publicKey,
fingerprint: fingerprint,
fingerprintPhrase: fingerprint.join('-'),
});
}

return finalUsers;
}

protected async confirmUser(user: User) {
const organizations = await this.userService.getAllOrganizations();

const organizationsWithoutCozy = organizations.filter(organization => organization.name !== 'Cozy');

for (const organization of organizationsWithoutCozy) {
const organizationUsers = await this.apiService.getOrganizationUsers(organization.id);

const userInOrganization = organizationUsers.data
.filter(organizationUser => organizationUser.status === OrganizationUserStatusType.Accepted)
.map(organizationUser => organizationUser.id)
.includes(user.id);

if (userInOrganization) {
const orgKey = await this.cryptoService.getOrgKey(organization.id);
const key = await this.cryptoService.rsaEncrypt(orgKey.key, Utils.fromB64ToArray(user.publicKey).buffer);
const request = new OrganizationUserConfirmRequest();
request.key = key.encryptedString;

await this.apiService.postOrganizationUserConfirm(organization.id, user.id, request);
}
}
}

protected async rejectUser(user: User) {
// console.log('rejectUser', user)
}

protected getTwoStepsConfirmationMethods(): ConfirmationMethods {
const loadOrganizationUsersToBeConfirmed = this.loadOrganizationUsersToBeConfirmed.bind(this);
const confirmUser = this.confirmUser.bind(this);
const rejectUser = this.rejectUser.bind(this);

return {
getRecipientsToBeConfirmed: loadOrganizationUsersToBeConfirmed,
confirmRecipient: confirmUser,
rejectRecipient: rejectUser,
};
}
}
13 changes: 11 additions & 2 deletions src/cozy/wrappers/sharing/sharing.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import React, { useState } from 'react'
import PropTypes from 'prop-types'

import SharingProvider, { ShareModal, ShareButton } from 'cozy-sharing'
import SharingProvider, { ShareModal, ShareButton, CozyPassFingerprintDialogContent } from 'cozy-sharing'

import ReactWrapper, { reactWrapperProps } from '../react-wrapper';

const Sharing = ({
file,
reactWrapperProps
reactWrapperProps,
confirmationMethods
}) => {
const [showShareModal, setShowShareModal] = useState(false)

const twoStepsConfirmationMethods = {
...confirmationMethods,
recipientConfirmationDialogContent: CozyPassFingerprintDialogContent,
}

return (
<ReactWrapper reactWrapperProps={reactWrapperProps}>
<SharingProvider doctype="com.bitwarden.organizations" documentType="Organizations" previewPath="">
Expand All @@ -21,6 +27,9 @@ const Sharing = ({
sharingDesc={file.name}
onClose={() => setShowShareModal(false)}
showShareOnlyByLink={false}

hasTwoStepsConfirmation={true}
twoStepsConfirmationMethods={twoStepsConfirmationMethods}
/>
)}
<ShareButton
Expand Down

0 comments on commit 3b1b03a

Please sign in to comment.