4 SerializedCryptoContext,
9 } from '@proton/pass/types';
10 import { ShareType } from '@proton/pass/types';
11 import { base64StringToUint8Array, uint8ArrayToBase64String } from '@proton/shared/lib/helpers/encoding';
13 import { importSymmetricKey } from './utils/crypto-helpers';
14 import { PassCryptoShareError, PassCryptoVaultError } from './utils/errors';
16 export const createShareManager = <T extends ShareType = ShareType>(
17 share: TypedOpenedShare<T>,
18 context?: ShareContext
19 ): ShareManager<T> => {
20 const shareContext: ShareContext = context ?? {
27 const shareManager: ShareManager<T> = {
28 getShare: () => shareContext.share as TypedOpenedShare<T>,
29 setShare: (share) => (shareContext.share = share),
31 setLatestRotation: (rotation) => (shareContext.latestRotation = rotation),
34 if (shareContext.latestRotation === -1) {
35 throw new PassCryptoShareError(`Share has not been hydrated`);
38 return shareContext.latestRotation;
41 hasVaultKey: (rotation) =>
42 shareManager.getShare().targetType === ShareType.Vault && shareContext.vaultKeys.has(rotation),
44 getVaultKey(rotation) {
45 if (shareManager.getShare().targetType !== ShareType.Vault) {
46 throw new PassCryptoVaultError(`Cannot resolve vault keys for non-vault share`);
49 if (!shareManager.hasVaultKey(rotation)) {
50 throw new PassCryptoVaultError(`Cannot find vault key for rotation ${rotation}`);
53 return shareContext.vaultKeys.get(rotation)!;
56 getVaultKeys: () => Array.from(shareContext.vaultKeys.values()),
58 addVaultKey(vaultKey) {
59 if (shareManager.getShare().targetType !== ShareType.Vault) {
60 throw new PassCryptoVaultError(`Cannot add vault key to non-vault share`);
63 const rotation = vaultKey.rotation;
64 shareContext.vaultKeys.set(rotation, vaultKey);
66 if (rotation > shareContext.latestRotation) {
67 shareManager.setLatestRotation(rotation);
71 /* a share is considered `active` if its latest rotation key
72 * was encrypted with an active user key - if the user key is
73 * inactive, then share should be ignored until it becomes
75 isActive(userKeys = []) {
77 const { targetType } = shareManager.getShare();
78 const latestRotation = shareManager.getLatestRotation();
80 if (targetType === ShareType.Vault) {
81 const vaultKey = shareManager.getVaultKey(latestRotation);
82 return userKeys.some(({ ID }) => ID === vaultKey.userKeyId);
94 switch (shareContext.share.targetType) {
95 case ShareType.Vault: {
97 ...shareContext.share,
98 content: uint8ArrayToBase64String(shareContext.share.content),
102 return shareContext.share;
105 vaultKeys: [...shareContext.vaultKeys.entries()].map(([rotation, vaultKey]) => [
108 rotation: vaultKey.rotation,
109 raw: uint8ArrayToBase64String(vaultKey.raw),
110 userKeyId: vaultKey.userKeyId,
117 return shareManager as ShareManager<T>;
120 createShareManager.fromSnapshot = async (snapshot: SerializedCryptoContext<ShareContext>): Promise<ShareManager> => {
122 * Item shares do not have a content property
123 * Only encode the vault content when dealing
126 const share: OpenedShare = (() => {
127 switch (snapshot.share.targetType) {
128 case ShareType.Vault: {
129 return { ...snapshot.share, content: base64StringToUint8Array(snapshot.share.content) };
131 case ShareType.Item: {
132 return snapshot.share;
137 const vaultKeys: [Rotation, VaultKey][] = await Promise.all(
138 snapshot.vaultKeys.map(async ([rotation, vaultKey]) => {
139 const rawKey = base64StringToUint8Array(vaultKey.raw);
143 rotation: vaultKey.rotation,
145 key: await importSymmetricKey(rawKey),
146 userKeyId: vaultKey.userKeyId,
152 return createShareManager(share, {
155 vaultKeys: new Map(vaultKeys),