Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / shared / lib / authentication / fork / produce.ts
blob5fce807edd3e652872b927d7c6845d537bc7125b
1 import { importKey } from '@proton/crypto/lib/subtle/aesGcm';
3 import { pushForkSession } from '../../api/auth';
4 import { getAppHref, getClientID } from '../../apps/helper';
5 import type { PushForkResponse } from '../../authentication/interface';
6 import type { OfflineKey } from '../../authentication/offlineKey';
7 import type { APP_NAMES } from '../../constants';
8 import { SSO_PATHS } from '../../constants';
9 import { withUIDHeaders } from '../../fetch/headers';
10 import { replaceUrl } from '../../helpers/browser';
11 import { encodeBase64URL, uint8ArrayToString } from '../../helpers/encoding';
12 import type { Api, User } from '../../interfaces';
13 import { getForkEncryptedBlob } from './blob';
14 import type { ForkType } from './constants';
15 import { ForkSearchParameters } from './constants';
16 import {
17     getEmailSessionForkSearchParameter,
18     getLocalIDForkSearchParameter,
19     getValidatedApp,
20     getValidatedForkType,
21 } from './validation';
23 export interface ProduceForkPayload {
24     selector: string;
25     state: string;
26     key: string;
27     persistent: boolean;
28     trusted: boolean;
29     forkType: ForkType | undefined;
30     forkVersion: number;
31     app: APP_NAMES;
32     encryptedPayload: { payloadVersion: 1 | 2; payloadType: 'offline' | 'default' };
35 interface ProduceForkArguments {
36     api: Api;
37     session: {
38         UID: string;
39         keyPassword?: string;
40         offlineKey: OfflineKey | undefined;
41         persistent: boolean;
42         trusted: boolean;
43     };
44     forkParameters: ProduceForkParameters;
47 export const produceFork = async ({
48     api,
49     session: { UID, keyPassword, offlineKey, persistent, trusted },
50     forkParameters: { state, app, independent, forkType, forkVersion, payloadType, payloadVersion },
51 }: ProduceForkArguments): Promise<ProduceForkPayload> => {
52     const rawKey = crypto.getRandomValues(new Uint8Array(32));
53     const base64StringKey = encodeBase64URL(uint8ArrayToString(rawKey));
54     const encryptedPayload = await (async () => {
55         const forkData = (() => {
56             if (payloadType === 'offline' && offlineKey && offlineKey.salt && offlineKey.password) {
57                 return {
58                     type: 'offline',
59                     keyPassword: keyPassword || '',
60                     offlineKeyPassword: offlineKey.password,
61                     offlineKeySalt: offlineKey.salt,
62                 } as const;
63             }
64             return { type: 'default', keyPassword: keyPassword || '' } as const;
65         })();
66         return {
67             blob: await getForkEncryptedBlob(await importKey(rawKey), forkData, payloadVersion),
68             payloadType: forkData.type,
69             payloadVersion,
70         };
71     })();
73     const childClientID = getClientID(app);
74     const { Selector: selector } = await api<PushForkResponse>(
75         withUIDHeaders(
76             UID,
77             pushForkSession({
78                 Payload: encryptedPayload.blob,
79                 ChildClientID: childClientID,
80                 Independent: independent ? 1 : 0,
81             })
82         )
83     );
85     return {
86         selector,
87         state,
88         key: base64StringKey,
89         persistent,
90         trusted,
91         forkType,
92         forkVersion,
93         app,
94         encryptedPayload,
95     };
98 export const produceForkConsumption = (
99     { selector, state, key, persistent, trusted, forkType, forkVersion, encryptedPayload, app }: ProduceForkPayload,
100     searchParameters?: URLSearchParams
101 ) => {
102     const fragmentSearchParams = new URLSearchParams();
103     fragmentSearchParams.append(ForkSearchParameters.Selector, selector);
104     fragmentSearchParams.append(ForkSearchParameters.State, state);
105     fragmentSearchParams.append(ForkSearchParameters.Base64Key, key);
106     fragmentSearchParams.append(ForkSearchParameters.Version, `${forkVersion}`);
107     if (persistent) {
108         fragmentSearchParams.append(ForkSearchParameters.Persistent, '1');
109     }
110     if (trusted) {
111         fragmentSearchParams.append(ForkSearchParameters.Trusted, '1');
112     }
113     if (forkType !== undefined) {
114         fragmentSearchParams.append(ForkSearchParameters.ForkType, forkType);
115     }
116     if (encryptedPayload.payloadVersion !== undefined) {
117         fragmentSearchParams.append(ForkSearchParameters.PayloadVersion, `${encryptedPayload.payloadVersion}`);
118     }
119     if (encryptedPayload.payloadType !== undefined) {
120         fragmentSearchParams.append(ForkSearchParameters.PayloadType, `${encryptedPayload.payloadType}`);
121     }
123     const searchParamsString = searchParameters?.toString() || '';
124     const search = searchParamsString ? `?${searchParamsString}` : '';
125     const fragment = `#${fragmentSearchParams.toString()}`;
127     replaceUrl(getAppHref(`${SSO_PATHS.FORK}${search}${fragment}`, app));
130 export interface ProduceForkParameters {
131     state: string;
132     app: APP_NAMES;
133     plan?: string;
134     independent: boolean;
135     forkType?: ForkType;
136     forkVersion: number;
137     prompt: 'login' | undefined;
138     promptType: 'offline-bypass' | 'offline' | 'default';
139     payloadType: 'offline' | 'default';
140     payloadVersion: 1 | 2;
141     email?: string;
144 export interface ProduceForkParametersFull extends ProduceForkParameters {
145     localID: number;
148 export const getProduceForkParameters = (
149     searchParams: URLSearchParams
150 ): Omit<ProduceForkParametersFull, 'localID' | 'app'> & Partial<Pick<ProduceForkParametersFull, 'localID' | 'app'>> => {
151     const app = searchParams.get(ForkSearchParameters.App) || '';
152     const state = searchParams.get(ForkSearchParameters.State) || '';
153     const localID = getLocalIDForkSearchParameter(searchParams);
154     const forkType = searchParams.get(ForkSearchParameters.ForkType) || '';
155     const prompt = searchParams.get(ForkSearchParameters.Prompt) || '';
156     const plan = searchParams.get(ForkSearchParameters.Plan) || '';
157     const forkVersion = Number(searchParams.get(ForkSearchParameters.Version) || '1');
158     const independent = searchParams.get(ForkSearchParameters.Independent) || '0';
159     const payloadType = (() => {
160         const value = searchParams.get(ForkSearchParameters.PayloadType) || '';
161         if (value === 'offline') {
162             return value;
163         }
164         return 'default';
165     })();
166     const payloadVersion = (() => {
167         const value = Number(searchParams.get(ForkSearchParameters.PayloadVersion) || '1');
168         if (value === 1 || value === 2) {
169             return value;
170         }
171         return 1;
172     })();
173     const promptType = (() => {
174         const value = searchParams.get(ForkSearchParameters.PromptType) || '';
175         if (value === 'offline' || value === 'offline-bypass') {
176             return value;
177         }
178         return 'default';
179     })();
180     const email = getEmailSessionForkSearchParameter(searchParams);
182     return {
183         state: state.slice(0, 100),
184         localID,
185         app: getValidatedApp(app),
186         forkType: getValidatedForkType(forkType),
187         prompt: prompt === 'login' ? 'login' : undefined,
188         promptType,
189         plan,
190         independent: independent === '1' || independent === 'true',
191         payloadType,
192         payloadVersion,
193         email,
194         forkVersion,
195     };
198 export const getRequiredForkParameters = (
199     forkParameters: ReturnType<typeof getProduceForkParameters>
200 ): forkParameters is ProduceForkParametersFull => {
201     return Boolean(forkParameters.app && forkParameters.state);
204 export const getCanUserReAuth = (user: User) => {
205     return !user.Flags.sso && !user.OrganizationPrivateKey;
208 export const getShouldReAuth = (
209     forkParameters: Pick<ProduceForkParameters, 'prompt' | 'promptType'>,
210     authSession: {
211         User: User;
212         offlineKey: OfflineKey | undefined;
213     }
214 ) => {
215     const shouldReAuth = forkParameters.prompt === 'login';
216     if (!shouldReAuth) {
217         return false;
218     }
219     if (!getCanUserReAuth(authSession.User)) {
220         return false;
221     }
222     if (forkParameters.promptType === 'offline-bypass' && authSession.offlineKey) {
223         return false;
224     }
225     return true;