1 import type { PrivateKeyReference } from '@proton/crypto';
2 import { CryptoProxy, toPublicKeyReference } from '@proton/crypto';
3 import { getMemberKeys } from '@proton/shared/lib/keys/memberKeys';
4 import noop from '@proton/utils/noop';
6 import { queryScopes } from '../api/auth';
7 import { getApiError, getIsConnectionIssue } from '../api/helpers/apiErrorHelper';
8 import { migrateAddressKeysRoute } from '../api/keys';
9 import { migrateMembersAddressKeysRoute } from '../api/memberKeys';
10 import { getAllMemberAddresses, getAllMembers, getMember } from '../api/members';
11 import { getOrganizationKeys } from '../api/organization';
12 import { MEMBER_PRIVATE, USER_ROLES } from '../constants';
13 import { ApiError } from '../fetch/ApiError';
18 KeyMigrationKTVerifier,
19 KeyTransparencyVerify,
26 } from '../interfaces';
27 import { generateAddressKeyTokens } from './addressKeys';
28 import { getDecryptedAddressKeysHelper } from './getDecryptedAddressKeys';
29 import { getDecryptedOrganizationKeyHelper } from './getDecryptedOrganizationKey';
30 import { getDecryptedUserKeysHelper } from './getDecryptedUserKeys';
31 import { getPrimaryKey } from './getPrimaryKey';
32 import type { OnSKLPublishSuccess } from './signedKeyList';
33 import { createSignedKeyListForMigration } from './signedKeyList';
35 export const getSentryError = (error: any): any => {
36 // Only interested in api errors where the API gave a valid error response, or run time errors.
37 if (error instanceof ApiError) {
38 const { message, code } = getApiError(error);
39 return message && code >= 400 && code < 500 ? message : null;
44 getIsConnectionIssue(error) ||
45 error.message === 'Failed to fetch' ||
46 error.message === 'Load failed' ||
47 error.message === 'Operation aborted' ||
48 error.name === 'AbortError'
55 export const getHasMigratedAddressKey = ({ Token, Signature }: { Token?: string; Signature?: string }): boolean => {
56 return !!Token && !!Signature;
59 export const getHasMigratedAddressKeys = (addresses?: Address[]) => {
60 return !!addresses?.some((address) => address.Keys?.some(getHasMigratedAddressKey));
63 export const getHasMemberMigratedAddressKeys = (memberAddresses: Address[], ownerAddresses: Address[]) => {
64 const primaryMemberAddress = memberAddresses[0];
65 return primaryMemberAddress?.Keys?.length > 0
66 ? getHasMigratedAddressKeys(memberAddresses)
67 : getHasMigratedAddressKeys(ownerAddresses);
70 export interface MigrateAddressKeyPayload {
77 export interface MigrateMemberAddressKeyPayload extends MigrateAddressKeyPayload {
81 interface MigrationResult<T extends MigrateAddressKeyPayload> {
82 SignedKeyLists: { [id: string]: SignedKeyList };
86 interface AddressKeyMigrationValue<T extends MigrateAddressKeyPayload> {
89 SignedKeyList: SignedKeyList | undefined;
90 onSKLPublishSuccess: OnSKLPublishSuccess | undefined;
93 interface AddressesKeys {
98 export function getAddressKeysMigration(data: {
100 addressesKeys: AddressesKeys[];
101 userKey: PrivateKeyReference;
102 keyTransparencyVerify: KeyTransparencyVerify;
103 keyMigrationKTVerifier: KeyMigrationKTVerifier;
104 organizationKey: PrivateKeyReference;
105 }): Promise<AddressKeyMigrationValue<MigrateMemberAddressKeyPayload>[]>;
106 export function getAddressKeysMigration(data: {
108 addressesKeys: AddressesKeys[];
109 userKey: PrivateKeyReference;
110 keyTransparencyVerify: KeyTransparencyVerify;
111 keyMigrationKTVerifier: KeyMigrationKTVerifier;
112 organizationKey?: PrivateKeyReference;
113 }): Promise<AddressKeyMigrationValue<MigrateAddressKeyPayload>[]>;
115 export function getAddressKeysMigration({
120 keyMigrationKTVerifier,
121 keyTransparencyVerify,
124 addressesKeys: AddressesKeys[];
125 userKey: PrivateKeyReference;
126 keyTransparencyVerify: KeyTransparencyVerify;
127 keyMigrationKTVerifier: KeyMigrationKTVerifier;
128 organizationKey?: PrivateKeyReference;
131 addressesKeys.map(async ({ address, keys }) => {
132 const migratedKeys = await Promise.all(
133 keys.map(async ({ ID, privateKey }) => {
134 const { token, encryptedToken, signature, organizationSignature } = await generateAddressKeyTokens(
138 const privateKeyArmored = await CryptoProxy.exportPrivateKey({
145 organizationSignature,
152 const migratedDecryptedKeys = await Promise.all(
153 migratedKeys.map(async ({ ID, privateKey }) => ({
156 publicKey: await toPublicKeyReference(privateKey),
159 const [signedKeyList, onSKLPublishSuccess] = await createSignedKeyListForMigration({
162 decryptedKeys: migratedDecryptedKeys,
163 keyTransparencyVerify,
164 keyMigrationKTVerifier,
168 SignedKeyList: signedKeyList,
169 onSKLPublishSuccess: onSKLPublishSuccess,
170 AddressKeys: migratedKeys.map((migratedKey) => {
173 PrivateKey: migratedKey.privateKeyArmored,
174 Token: migratedKey.encryptedToken,
175 Signature: migratedKey.signature,
176 ...(migratedKey.organizationSignature
177 ? { OrgSignature: migratedKey.organizationSignature }
186 export function getAddressKeysMigrationPayload<T extends MigrateAddressKeyPayload>(
187 addressKeysMigration: AddressKeyMigrationValue<T>[]
189 return addressKeysMigration.reduce<MigrationResult<T>>(
190 (acc, { AddressKeys, Address, SignedKeyList }) => {
191 // Some addresses may not have keys and thus won't have generated a signed key list
192 if (AddressKeys.length > 0) {
193 acc.AddressKeys = acc.AddressKeys.concat(AddressKeys);
195 acc.SignedKeyLists[Address.ID] = SignedKeyList;
200 { AddressKeys: [], SignedKeyLists: {} }
204 interface MigrateAddressKeysArguments {
208 addresses: Address[];
209 organizationKey?: OrganizationKey;
210 preAuthKTVerify: PreAuthKTVerify;
211 keyMigrationKTVerifier: KeyMigrationKTVerifier;
214 export async function migrateAddressKeys(
215 args: MigrateAddressKeysArguments & {
216 organizationKey: OrganizationKey;
218 ): Promise<AddressKeyMigrationValue<MigrateMemberAddressKeyPayload>[]>;
219 export async function migrateAddressKeys(
220 args: MigrateAddressKeysArguments
221 ): Promise<AddressKeyMigrationValue<MigrateAddressKeyPayload>[]>;
223 export async function migrateAddressKeys({
230 keyMigrationKTVerifier,
231 }: MigrateAddressKeysArguments) {
232 const userKeys = await getDecryptedUserKeysHelper(user, keyPassword);
234 const primaryUserKey = getPrimaryKey(userKeys)?.privateKey;
235 if (!primaryUserKey) {
236 throw new Error('Missing primary private user key');
239 const addressesKeys = await Promise.all(
240 addresses.map(async (address) => {
243 keys: await getDecryptedAddressKeysHelper(address.Keys, user, userKeys, keyPassword),
248 const keyTransparencyVerify = preAuthKTVerify(userKeys);
250 if (!organizationKey) {
251 return getAddressKeysMigration({
254 userKey: primaryUserKey,
255 keyTransparencyVerify,
256 keyMigrationKTVerifier,
260 const decryptedOrganizationKeyResult = await getDecryptedOrganizationKeyHelper({
262 Key: organizationKey,
265 if (!decryptedOrganizationKeyResult) {
266 const error = new Error('Failed to decrypt organization key');
267 (error as any).ignore = true;
270 return getAddressKeysMigration({
273 userKey: primaryUserKey,
274 keyTransparencyVerify,
275 keyMigrationKTVerifier,
276 organizationKey: decryptedOrganizationKeyResult.privateKey,
280 interface MigrateMemberAddressKeysArguments {
285 organization: Organization;
286 keyTransparencyVerify: KeyTransparencyVerify;
287 keyMigrationKTVerifier: KeyMigrationKTVerifier;
290 export async function migrateMemberAddressKeys({
296 keyTransparencyVerify,
297 keyMigrationKTVerifier,
298 }: MigrateMemberAddressKeysArguments) {
299 if (organization.ToMigrate !== 1) {
303 if (user.Role !== USER_ROLES.ADMIN_ROLE) {
307 // NOTE: The API following calls are done in a waterfall to lower the amount of unnecessary requests.
309 const { Scopes } = await api<{ Scopes: string[] }>(queryScopes());
310 if (!Scopes.includes('organization')) {
314 const organizationKey = await api<OrganizationKey>(getOrganizationKeys());
315 const userKeys = await getDecryptedUserKeysHelper(user, keyPassword);
316 // Ensure that the organization key can be decrypted...
317 const decryptedOrganizationKeyResult = await getDecryptedOrganizationKeyHelper({
319 Key: organizationKey,
322 if (!decryptedOrganizationKeyResult?.privateKey) {
326 // Ensure that there are members to migrate...
327 const members = await getAllMembers(api);
328 const membersToMigrate = members.filter(({ ToMigrate, Private, Self }) => {
329 return !Self && ToMigrate === 1 && Private === MEMBER_PRIVATE.READABLE;
331 if (!membersToMigrate.length) {
335 for (const member of membersToMigrate) {
336 // Some members might not be setup.
337 if (!member.Keys?.length) {
340 const memberAddresses = await getAllMemberAddresses(api, member.ID);
341 const { memberUserKeyPrimary, memberAddressesKeys } = await getMemberKeys({
344 organizationKey: decryptedOrganizationKeyResult,
347 // Some members might not have keys setup for the address.
348 if (!memberAddressesKeys.length) {
352 const migratedKeys = await getAddressKeysMigration({
354 addressesKeys: memberAddressesKeys,
355 userKey: memberUserKeyPrimary,
356 keyTransparencyVerify,
357 keyMigrationKTVerifier,
358 organizationKey: decryptedOrganizationKeyResult.privateKey,
360 const payload = await getAddressKeysMigrationPayload(migratedKeys);
362 await api({ ...migrateMembersAddressKeysRoute({ MemberID: member.ID, ...payload }), timeout });
364 migratedKeys.map(({ onSKLPublishSuccess }) =>
365 onSKLPublishSuccess ? onSKLPublishSuccess() : Promise.resolve()
372 export const migrateUser = async ({
379 keyMigrationKTVerifier,
383 addresses: Address[];
386 preAuthKTVerify: PreAuthKTVerify;
387 keyMigrationKTVerifier: KeyMigrationKTVerifier;
389 if (user.ToMigrate !== 1 || getHasMigratedAddressKeys(addresses)) {
393 if (user.Private === MEMBER_PRIVATE.READABLE && user.Role === USER_ROLES.MEMBER_ROLE) {
397 if (user.Private === MEMBER_PRIVATE.READABLE && user.Role === USER_ROLES.ADMIN_ROLE) {
398 const [selfMember, organizationKey] = await Promise.all([
399 api<{ Member: Member }>(getMember('me')).then(({ Member }) => Member),
400 api<OrganizationKey>(getOrganizationKeys()),
402 const migratedKeys = await migrateAddressKeys({
408 keyMigrationKTVerifier,
411 const payload = await getAddressKeysMigrationPayload(migratedKeys);
413 ...migrateMembersAddressKeysRoute({ MemberID: selfMember.ID, ...payload }),
417 migratedKeys.map(({ onSKLPublishSuccess }) =>
418 onSKLPublishSuccess ? onSKLPublishSuccess() : Promise.resolve()
424 const migratedKeys = await migrateAddressKeys({
430 keyMigrationKTVerifier,
432 const payload = await getAddressKeysMigrationPayload(migratedKeys);
433 await api({ ...migrateAddressKeysRoute(payload), timeout });
435 migratedKeys.map(({ onSKLPublishSuccess }) => (onSKLPublishSuccess ? onSKLPublishSuccess() : Promise.resolve()))