1 import type { PrivateKeyReference, SessionKey } from '@proton/crypto';
2 import { CryptoProxy } from '@proton/crypto';
3 import { queryShareMeta } from '@proton/shared/lib/api/drive/share';
4 import type { ShareMeta } from '@proton/shared/lib/interfaces/drive/share';
6 import { sendErrorReport } from '../../utils/errorHandling';
7 import { EnrichedError } from '../../utils/errorHandling/EnrichedError';
8 import { shareMetaToShareWithKey, useDebouncedRequest } from '../_api';
9 import { integrityMetrics, useDriveCrypto } from '../_crypto';
10 import { useIsPaid } from '../_user';
11 import { useDebouncedFunction } from '../_utils';
12 import type { Share, ShareWithKey } from './interface';
13 import { getShareTypeString } from './shareType';
14 import type { ShareKeys } from './useSharesKeys';
15 import useSharesKeys from './useSharesKeys';
16 import useSharesState from './useSharesState';
18 export default function useShare() {
19 const isPaid = useIsPaid();
20 const debouncedFunction = useDebouncedFunction();
21 const debouncedRequest = useDebouncedRequest();
22 const driveCrypto = useDriveCrypto();
23 const sharesKeys = useSharesKeys();
24 const sharesState = useSharesState();
26 const fetchShare = async (abortSignal: AbortSignal, shareId: string): Promise<ShareWithKey> => {
27 const Share = await debouncedRequest<ShareMeta>({
28 ...queryShareMeta(shareId),
31 return shareMetaToShareWithKey(Share);
35 * getShareWithKey returns share with keys. That is not available after
36 * listing user's shares and thus needs extra API call. Use wisely.
38 const getShareWithKey = async (abortSignal: AbortSignal, shareId: string): Promise<ShareWithKey> => {
39 return debouncedFunction(
40 async (abortSignal: AbortSignal) => {
41 const cachedShare = sharesState.getShare(shareId);
42 if (cachedShare && 'key' in cachedShare) {
46 const share = await fetchShare(abortSignal, shareId);
47 sharesState.setShares([share]);
50 ['getShareWithKey', shareId],
56 * getShare returns share from cache or it fetches the full share from API.
58 const getShare = async (abortSignal: AbortSignal, shareId: string): Promise<Share> => {
59 const cachedShare = sharesState.getShare(shareId);
63 return getShareWithKey(abortSignal, shareId);
66 const getShareKeys = async (
67 abortSignal: AbortSignal,
69 linkPrivateKey?: PrivateKeyReference
70 ): Promise<ShareKeys> => {
71 const keys = sharesKeys.get(shareId);
76 const share = await getShareWithKey(abortSignal, shareId);
79 * Decrypt the share with linkPrivateKey (NodeKey) if provided and fallback if it failed
80 * Fallback will use user's privateKey (retrieved in driveCrypto.decryptSharePassphrase function)
82 const decryptSharePassphrase = async (
83 fallback: boolean = false
84 ): ReturnType<typeof driveCrypto.decryptSharePassphrase> => {
85 // TODO: Change the logic when we will migrate to encryption with only link's privateKey
86 // If the share passphrase was encrypted with multiple KeyPacket,
87 // that mean it was encrypted with link's privateKey and user's privateKey
88 const haveMultipleEncryptionKey = await CryptoProxy.getMessageInfo({
89 armoredMessage: share.passphrase,
90 }).then((messageInfo) => messageInfo.encryptionKeyIDs.length > 1);
91 const decryptWithLinkPrivateKey = !!linkPrivateKey && !fallback && haveMultipleEncryptionKey;
93 return await driveCrypto.decryptSharePassphrase(
95 decryptWithLinkPrivateKey ? [linkPrivateKey] : undefined
98 if (decryptWithLinkPrivateKey) {
100 new EnrichedError('Failed to decrypt share passphrase with link privateKey', {
101 tags: { keyId: linkPrivateKey.getKeyID(), shareId },
105 return decryptSharePassphrase(true);
108 const shareType = getShareTypeString(share);
111 createTime: share.createTime,
113 integrityMetrics.shareDecryptionError(shareId, shareType, options);
115 throw new EnrichedError('Failed to decrypt share passphrase', {
126 const { decryptedPassphrase, sessionKey } = await decryptSharePassphrase();
127 const privateKey = await CryptoProxy.importPrivateKey({
128 armoredKey: share.key,
129 passphrase: decryptedPassphrase,
132 new EnrichedError('Failed to import share private key', {
141 sharesKeys.set(shareId, privateKey, sessionKey);
150 * getSharePrivateKey returns private key used for link private key encryption.
152 const getSharePrivateKey = async (abortSignal: AbortSignal, shareId: string): Promise<PrivateKeyReference> => {
153 const keys = await getShareKeys(abortSignal, shareId);
154 return keys.privateKey;
158 * getShareSessionKey returns session key used for sharing links.
160 const getShareSessionKey = async (
161 abortSignal: AbortSignal,
163 linkPrivateKey?: PrivateKeyReference
164 ): Promise<SessionKey> => {
165 const keys = await getShareKeys(abortSignal, shareId, linkPrivateKey);
166 if (!keys.sessionKey) {
167 // This should not happen. All shares have session key, only
168 // publicly shared link will not have it, but it is bug if
170 throw new Error('Share is missing session key');
172 return keys.sessionKey;
176 * getShareCreatorKeys returns the share creator address' keys
177 * TODO: Change this function name as it doesn't fetch creator key but your own member keys for that share
178 * Also share.adressId can be null
180 const getShareCreatorKeys = async (abortSignal: AbortSignal, shareId: string) => {
181 const share = await getShareWithKey(abortSignal, shareId);
182 const keys = await driveCrypto.getOwnAddressAndPrimaryKeys(share.addressId);
193 removeShares: sharesState.removeShares,