Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / components / containers / login / loginActions.ts
blob6920d2753deeabd25843108c90c9c236797413a6
1 import { c } from 'ttag';
3 import { serverTime, wasServerTimeEverUpdated } from '@proton/crypto';
4 import { auth2FA, getInfo } from '@proton/shared/lib/api/auth';
5 import { queryAvailableDomains } from '@proton/shared/lib/api/domains';
6 import { getApiErrorMessage } from '@proton/shared/lib/api/helpers/apiErrorHelper';
7 import type { ProductParam } from '@proton/shared/lib/apps/product';
8 import type { AuthResponse, AuthVersion, Fido2Data, InfoResponse } from '@proton/shared/lib/authentication/interface';
9 import loginWithFallback from '@proton/shared/lib/authentication/loginWithFallback';
10 import { persistSession } from '@proton/shared/lib/authentication/persistedSessionHelper';
11 import type { APP_NAMES } from '@proton/shared/lib/constants';
12 import { HTTP_ERROR_CODES } from '@proton/shared/lib/errors';
13 import { wait } from '@proton/shared/lib/helpers/promise';
14 import { captureMessage } from '@proton/shared/lib/helpers/sentry';
15 import type {
16     Api,
17     KeyTransparencyActivation,
18     VerifyOutboundPublicKeys,
19     KeySalt as tsKeySalt,
20     User as tsUser,
21 } from '@proton/shared/lib/interfaces';
22 import { createKeyMigrationKTVerifier, createPreAuthKTVerifier } from '@proton/shared/lib/keyTransparency';
23 import { getRequiresPasswordSetup, getSentryError, migrateUser } from '@proton/shared/lib/keys';
24 import { handleSetupAddressKeys } from '@proton/shared/lib/keys/setupAddressKeys';
25 import { getHasV2KeysToUpgrade, upgradeV2KeysHelper } from '@proton/shared/lib/keys/upgradeKeysV2';
27 import type { ChallengeResult } from '../challenge/interface';
28 import { finalizeLogin } from './finalizeLogin';
29 import type { AuthActionResponse, AuthCacheResult, AuthSession } from './interface';
30 import { AuthStep, AuthType } from './interface';
31 import { getAuthTypes, getUnlockError, handleUnlockKey } from './loginHelper';
32 import { handlePrepareSSOData } from './ssoLoginHelper';
33 import { syncAddresses, syncSalts, syncUser } from './syncCache';
35 const handleKeyUpgrade = async ({
36     cache,
37     loginPassword,
38     clearKeyPassword,
39     keyPassword: maybeKeyPassword,
40     isOnePasswordMode,
41 }: {
42     cache: AuthCacheResult;
43     loginPassword: string;
44     clearKeyPassword: string;
45     keyPassword: string;
46     isOnePasswordMode?: boolean;
47 }) => {
48     const { api, preAuthKTVerifier, keyMigrationKTVerifier } = cache;
49     let keyPassword = maybeKeyPassword;
51     let [user, addresses] = await Promise.all([
52         cache.data.user || syncUser(cache),
53         cache.data.addresses || syncAddresses(cache),
54     ]);
56     const { preAuthKTVerify } = preAuthKTVerifier;
58     if (getHasV2KeysToUpgrade(user, addresses)) {
59         const newKeyPassword = await upgradeV2KeysHelper({
60             user,
61             addresses,
62             loginPassword,
63             keyPassword,
64             clearKeyPassword,
65             isOnePasswordMode,
66             api,
67             preAuthKTVerify,
68             keyMigrationKTVerifier,
69         }).catch((e) => {
70             const error = getSentryError(e);
71             if (error) {
72                 captureMessage('Key upgrade error', { extra: { error } });
73             }
74             return undefined;
75         });
76         if (newKeyPassword !== undefined) {
77             cache.data.user = undefined;
78             cache.data.addresses = undefined;
79             [user, addresses] = await Promise.all([syncUser(cache), syncAddresses(cache)]);
80             keyPassword = newKeyPassword;
81         }
82     }
84     const hasDoneMigration = await migrateUser({
85         api,
86         keyPassword,
87         user,
88         addresses,
89         preAuthKTVerify,
90         keyMigrationKTVerifier,
91     }).catch((e) => {
92         const error = getSentryError(e);
93         if (error) {
94             captureMessage('Key migration error', {
95                 extra: { error, serverTime: serverTime(), isServerTime: wasServerTimeEverUpdated() },
96             });
97         }
98         return false;
99     });
101     if (hasDoneMigration) {
102         cache.data.user = undefined;
103         cache.data.addresses = undefined;
104     }
106     return finalizeLogin({ cache, loginPassword, keyPassword, clearKeyPassword });
110  * Step 3. Handle unlock.
111  * Attempt to decrypt the primary private key with the password.
112  */
113 export const handleUnlock = async ({
114     cache,
115     clearKeyPassword,
116     isOnePasswordMode,
117 }: {
118     cache: AuthCacheResult;
119     clearKeyPassword: string;
120     isOnePasswordMode: boolean;
121 }) => {
122     const {
123         data: { salts, user },
124         loginPassword,
125     } = cache;
127     if (!salts || !user) {
128         throw new Error('Invalid state');
129     }
131     await wait(500);
133     const unlockResult = await handleUnlockKey(user, salts, clearKeyPassword).catch(() => undefined);
134     if (!unlockResult) {
135         throw getUnlockError();
136     }
138     return handleKeyUpgrade({
139         cache,
140         loginPassword,
141         clearKeyPassword,
142         keyPassword: unlockResult.keyPassword,
143         isOnePasswordMode,
144     });
147 export const handleReAuthKeyPassword = async ({
148     authSession,
149     User,
150     clearKeyPassword,
151     salts,
152     api,
153 }: {
154     authSession: AuthSession;
155     User: tsUser;
156     clearKeyPassword: string;
157     salts: tsKeySalt[];
158     api: Api;
159 }) => {
160     const unlockResult = await handleUnlockKey(User, salts, clearKeyPassword).catch(() => undefined);
161     if (!unlockResult) {
162         throw getUnlockError();
163     }
164     const newAuthSession = {
165         ...authSession,
166         User,
167         keyPassword: unlockResult.keyPassword,
168     };
169     const { clientKey, offlineKey } = await persistSession({
170         ...newAuthSession,
171         clearKeyPassword,
172         api,
173     });
174     return { ...newAuthSession, clientKey, offlineKey, prompt: null };
177 export const handleSetupPassword = async ({ cache, newPassword }: { cache: AuthCacheResult; newPassword: string }) => {
178     const { api, username, preAuthKTVerifier } = cache;
180     const [domains, addresses] = await Promise.all([
181         api<{ Domains: string[] }>(queryAvailableDomains('signup')).then(({ Domains }) => Domains),
182         cache.data.addresses || (await syncAddresses(cache)),
183     ]);
185     const keyPassword = await handleSetupAddressKeys({
186         api,
187         username,
188         password: newPassword,
189         addresses,
190         domains,
191         preAuthKTVerify: preAuthKTVerifier.preAuthKTVerify,
192         productParam: cache.productParam,
193     });
195     cache.data.user = undefined;
196     cache.data.addresses = undefined;
198     return finalizeLogin({
199         cache,
200         loginPassword: newPassword,
201         keyPassword,
202         clearKeyPassword: newPassword,
203     });
206 const next = async ({ cache, from }: { cache: AuthCacheResult; from: AuthStep }): Promise<AuthActionResponse> => {
207     const { authType, authTypes, ignoreUnlock, authResponse, loginPassword } = cache;
209     if (from === AuthStep.LOGIN) {
210         if (authTypes.fido2 || authTypes.totp) {
211             return {
212                 cache,
213                 to: AuthStep.TWO_FA,
214             };
215         }
216     }
218     // Special case for the admin panel (or sso login), return early since it can not get key salts or should setup keys.
219     if (ignoreUnlock) {
220         return finalizeLogin({ cache, loginPassword });
221     }
223     const [user] = await Promise.all([cache.data.user || syncUser(cache), cache.data.salts || syncSalts(cache)]);
225     if (authType === AuthType.ExternalSSO) {
226         return handlePrepareSSOData({ cache });
227     }
229     if (user.Keys.length === 0) {
230         if (authResponse.TemporaryPassword) {
231             return { cache, to: AuthStep.NEW_PASSWORD };
232         }
233         if (getRequiresPasswordSetup(user, cache.setupVPN)) {
234             return handleSetupPassword({ cache, newPassword: loginPassword });
235         }
236         return finalizeLogin({ cache, loginPassword });
237     }
239     if (authTypes.unlock) {
240         return {
241             cache,
242             to: AuthStep.UNLOCK,
243         };
244     }
246     return handleUnlock({ cache, clearKeyPassword: loginPassword, isOnePasswordMode: true });
249 export const handleFido2 = async ({
250     cache,
251     payload,
252 }: {
253     cache: AuthCacheResult;
254     payload: Fido2Data;
255 }): Promise<AuthActionResponse> => {
256     const { api } = cache;
258     await api(auth2FA({ FIDO2: payload }));
260     return next({ cache, from: AuthStep.TWO_FA });
264  * Step 2. Handle TOTP.
265  * Unless there is another auth type active, the flow will continue until it's logged in.
266  */
267 export const handleTotp = async ({
268     cache,
269     totp,
270 }: {
271     cache: AuthCacheResult;
272     totp: string;
273 }): Promise<AuthActionResponse> => {
274     const { api } = cache;
276     await api(auth2FA({ TwoFactorCode: totp })).catch((e) => {
277         if (e.status === HTTP_ERROR_CODES.UNPROCESSABLE_ENTITY) {
278             const error: any = new Error(
279                 getApiErrorMessage(e) || c('Error').t`Incorrect login credentials. Please try again.`
280             );
281             error.name = 'TOTPError';
282             error.trace = false;
283             throw error;
284         }
285         throw e;
286     });
288     return next({ cache, from: AuthStep.TWO_FA });
291 export const handleLogin = async ({
292     username,
293     password,
294     payload,
295     persistent,
296     api,
297 }: {
298     username: string;
299     password: string;
300     payload: ChallengeResult;
301     persistent: boolean;
302     api: Api;
303 }) => {
304     const infoResult = await api<InfoResponse>(getInfo({ username }));
305     const authResult = await loginWithFallback({
306         api,
307         credentials: { username, password },
308         initialAuthInfo: infoResult,
309         payload,
310         persistent,
311     });
312     return { infoResult, authResult };
315 export const handleNextLogin = async ({
316     authType,
317     authResponse,
318     authVersion,
319     username,
320     password,
321     persistent,
322     api,
323     ignoreUnlock,
324     appName,
325     toApp,
326     setupVPN,
327     ktActivation,
328     productParam,
329     verifyOutboundPublicKeys,
330 }: {
331     authType: AuthType;
332     authVersion: AuthVersion;
333     authResponse: AuthResponse;
334     username: string;
335     password: string;
336     persistent: boolean;
337     api: Api;
338     ignoreUnlock: boolean;
339     appName: APP_NAMES;
340     toApp: APP_NAMES | undefined;
341     setupVPN: boolean;
342     ktActivation: KeyTransparencyActivation;
343     productParam: ProductParam;
344     verifyOutboundPublicKeys: VerifyOutboundPublicKeys | null;
345 }): Promise<AuthActionResponse> => {
346     const cache: AuthCacheResult = {
347         authType,
348         authResponse,
349         authVersion,
350         appName,
351         toApp,
352         productParam,
353         data: {},
354         api,
355         verifyOutboundPublicKeys,
356         authTypes: getAuthTypes(authResponse, appName),
357         username,
358         persistent,
359         loginPassword: password,
360         ignoreUnlock,
361         setupVPN,
362         preAuthKTVerifier: createPreAuthKTVerifier(ktActivation),
363         keyMigrationKTVerifier: createKeyMigrationKTVerifier(ktActivation),
364     };
365     return next({ cache, from: AuthStep.LOGIN });