Merge branch 'IDTEAM-1.26.0' into 'main'
[ProtonMail-WebClient.git] / packages / pass / lib / auth / store.ts
blob924f8bb8fa41e6fd27835cc641277df40efb3efe
1 import { decodeUtf8Base64, encodeUtf8Base64 } from '@proton/crypto/lib/utils';
2 import type { OfflineConfig } from '@proton/pass/lib/cache/crypto';
3 import { AuthMode, type Maybe, type Store } from '@proton/pass/types';
4 import { deobfuscate, obfuscate } from '@proton/pass/utils/obfuscate/xor';
5 import { isObject } from '@proton/pass/utils/object/is-object';
6 import { encodedGetter, encodedSetter } from '@proton/pass/utils/store';
8 import { AUTH_MODE } from './flags';
9 import { LockMode } from './lock/types';
10 import type { AuthSessionVersion, EncryptedAuthSession } from './session';
11 import { type AuthSession, SESSION_VERSION } from './session';
13 export type AuthStore = ReturnType<typeof createAuthStore>;
14 export type AuthStoreOptions = { cookies: boolean };
16 const PASS_ACCESS_TOKEN_KEY = 'pass:access_token';
17 const PASS_COOKIE_AUTH_KEY = 'pass:auth_cookies';
18 const PASS_CLIENT_KEY = 'pass:client_key';
19 const PASS_EXTRA_PWD_KEY = 'pass:extra_password';
20 const PASS_LOCAL_ID_KEY = 'pass:local_id';
21 const PASS_LOCK_EXTEND_TIME_KEY = 'pass:lock_extend_time';
22 const PASS_LOCK_MODE_KEY = 'pass:lock_mode';
23 const PASS_LOCK_STATE_KEY = 'pass:lock_state';
24 const PASS_LOCK_TOKEN_KEY = 'pass:lock_token';
25 const PASS_LOCK_TTL_KEY = 'pass:lock_ttl';
26 const PASS_MAILBOX_PWD_KEY = 'pass:mailbox_pwd';
27 const PASS_OFFLINE_CONFIG_KEY = 'pass:offline_config';
28 const PASS_OFFLINE_KD_KEY = 'pass:offline_kd';
29 const PASS_OFFLINE_VERIFIER_KEY = 'pass:offline_verifier';
30 const PASS_PERSISTENT_SESSION_KEY = 'pass:persistent';
31 const PASS_REFRESH_TIME_KEY = 'pass:refresh_time';
32 const PASS_REFRESH_TOKEN_KEY = 'pass:refresh_token';
33 const PASS_SESSION_VERSION_KEY = 'pass:session_version';
34 const PASS_UID_KEY = 'pass:uid';
35 const PASS_UNLOCK_RETRY_KEY = 'pass:unlock_retry_count';
36 const PASS_USER_ID_KEY = 'pass:user_id';
37 const PASS_ENCRYPTED_OFFLINE_KD = 'pass:encrypted_offline_kd';
38 const PASS_LAST_USED_AT = 'pass:last_used_at';
39 const PASS_USER_DISPLAY_NAME = 'pass:user_display_name';
40 const PASS_USER_EMAIL = 'pass:user_email';
42 export const encodeUserData = (email: string = '', displayName: string = '') => {
43     const encodedEmail = JSON.stringify(obfuscate(email));
44     const encodedDisplayName = JSON.stringify(obfuscate(displayName));
45     return encodeUtf8Base64(`${encodedEmail}.${encodedDisplayName}`);
48 export const decodeUserData = (userData: string): { PrimaryEmail?: string; DisplayName?: string } => {
49     try {
50         const [encodedEmail, encodedDisplayName] = decodeUtf8Base64(userData).split('.');
51         return {
52             PrimaryEmail: deobfuscate(JSON.parse(encodedEmail)),
53             DisplayName: deobfuscate(JSON.parse(encodedDisplayName)),
54         };
55     } catch {
56         return {};
57     }
60 export const createAuthStore = (store: Store) => {
61     const authStore = {
62         clear: () => store.reset(),
64         hasSession: (localID?: number) =>
65             Boolean(authStore.getUID() && (localID === undefined || authStore.getLocalID() === localID)),
67         hasOfflinePassword: () =>
68             Boolean(authStore.getOfflineConfig() && authStore.getOfflineKD() && authStore.getOfflineVerifier()),
70         getSession: (): AuthSession => ({
71             AccessToken: authStore.getAccessToken() ?? '',
72             cookies: authStore.getCookieAuth() ?? false,
73             encryptedOfflineKD: authStore.getEncryptedOfflineKD(),
74             extraPassword: authStore.getExtraPassword(),
75             keyPassword: authStore.getPassword() ?? '',
76             lastUsedAt: authStore.getLastUsedAt(),
77             LocalID: authStore.getLocalID(),
78             lockMode: authStore.getLockMode(),
79             lockTTL: authStore.getLockTTL(),
80             offlineConfig: authStore.getOfflineConfig(),
81             offlineKD: authStore.getOfflineKD(),
82             offlineVerifier: authStore.getOfflineVerifier(),
83             payloadVersion: authStore.getSessionVersion(),
84             persistent: authStore.getPersistent(),
85             RefreshTime: authStore.getRefreshTime(),
86             RefreshToken: authStore.getRefreshToken() ?? '',
87             sessionLockToken: authStore.getLockToken(),
88             UID: authStore.getUID() ?? '',
89             unlockRetryCount: authStore.getUnlockRetryCount(),
90             userData: authStore.getUserData(),
91             UserID: authStore.getUserID() ?? '',
92         }),
94         shouldCookieUpgrade: (data: Partial<AuthSession>) => AUTH_MODE === AuthMode.COOKIE && !data.cookies,
96         validSession: (data: Partial<AuthSession>): data is AuthSession =>
97             Boolean(
98                 data.UID &&
99                     data.UserID &&
100                     data.keyPassword &&
101                     (!data.offlineConfig || data.offlineKD) &&
102                     (data.cookies || (data.AccessToken && data.RefreshToken))
103             ),
105         /** Checks wether a parsed persisted session object is
106          * a valid `EncryptedAuthSession` in order to resume */
107         validPersistedSession: (data: any): data is EncryptedAuthSession =>
108             isObject(data) &&
109             Boolean('UID' in data && data.UID) &&
110             Boolean('UserID' in data && data.UserID) &&
111             Boolean('blob' in data && data.blob) &&
112             (Boolean('cookies' in data && data.cookies) ||
113                 (Boolean('AccessToken' in data && data.AccessToken) &&
114                     Boolean('RefreshToken' in data && data.RefreshToken))),
116         setSession: (session: Partial<AuthSession>) => {
117             if (session.AccessToken) authStore.setAccessToken(session.AccessToken);
118             if (session.cookies) authStore.setCookieAuth(session.cookies);
119             if (session.encryptedOfflineKD) authStore.setEncryptedOfflineKD(session.encryptedOfflineKD);
120             if (session.extraPassword) authStore.setExtraPassword(true);
121             if (session.keyPassword) authStore.setPassword(session.keyPassword);
122             if (session.lastUsedAt !== undefined) authStore.setLastUsedAt(session.lastUsedAt);
123             if (session.LocalID !== undefined) authStore.setLocalID(session.LocalID);
124             if (session.lockMode) authStore.setLockMode(session.lockMode);
125             if (session.lockTTL) authStore.setLockTTL(session.lockTTL);
126             if (session.offlineConfig) authStore.setOfflineConfig(session.offlineConfig);
127             if (session.offlineKD) authStore.setOfflineKD(session.offlineKD);
128             if (session.offlineVerifier) authStore.setOfflineVerifier(session.offlineVerifier);
129             if (session.payloadVersion !== undefined) authStore.setSessionVersion(session.payloadVersion);
130             if (session.persistent) authStore.setPersistent(session.persistent);
131             if (session.userData !== undefined) authStore.setUserData(session.userData);
132             if (session.RefreshTime) authStore.setRefreshTime(session.RefreshTime);
133             if (session.RefreshToken) authStore.setRefreshToken(session.RefreshToken);
134             if (session.sessionLockToken) authStore.setLockToken(session.sessionLockToken);
135             if (session.UID) authStore.setUID(session.UID);
136             if (session.unlockRetryCount !== undefined) authStore.setUnlockRetryCount(session.unlockRetryCount);
137             if (session.UserID) authStore.setUserID(session.UserID);
138         },
140         setAccessToken: (accessToken: Maybe<string>): void => store.set(PASS_ACCESS_TOKEN_KEY, accessToken),
141         getAccessToken: (): Maybe<string> => store.get(PASS_ACCESS_TOKEN_KEY),
142         setRefreshToken: (refreshToken: Maybe<string>): void => store.set(PASS_REFRESH_TOKEN_KEY, refreshToken),
143         getRefreshToken: (): Maybe<string> => store.get(PASS_REFRESH_TOKEN_KEY),
144         setRefreshTime: (refreshTime: Maybe<number>) => store.set(PASS_REFRESH_TIME_KEY, refreshTime),
145         getRefreshTime: (): Maybe<number> => store.get(PASS_REFRESH_TIME_KEY),
147         setUID: (UID: Maybe<string>): void => store.set(PASS_UID_KEY, UID),
148         getUID: (): Maybe<string> => store.get(PASS_UID_KEY),
149         setUserID: (UserID: Maybe<string>): void => store.set(PASS_USER_ID_KEY, UserID),
150         getUserID: (): Maybe<string> => store.get(PASS_USER_ID_KEY),
151         setUserEmail: encodedSetter(store)(PASS_USER_EMAIL),
152         getUserEmail: encodedGetter(store)(PASS_USER_EMAIL),
153         setUserDisplayName: encodedSetter(store)(PASS_USER_DISPLAY_NAME),
154         getUserDisplayName: encodedGetter(store)(PASS_USER_DISPLAY_NAME),
156         getUserData: () => encodeUserData(authStore.getUserEmail(), authStore.getUserDisplayName()),
157         setUserData: (userData: string) => {
158             const data = decodeUserData(userData);
159             if (data) {
160                 authStore.setUserEmail(data.PrimaryEmail);
161                 authStore.setUserDisplayName(data.DisplayName);
162             }
163         },
165         setPassword: encodedSetter(store)(PASS_MAILBOX_PWD_KEY),
166         getPassword: encodedGetter(store)(PASS_MAILBOX_PWD_KEY),
167         setLocalID: (LocalID: Maybe<number>): void => store.set(PASS_LOCAL_ID_KEY, LocalID),
168         getLocalID: (): Maybe<number> => store.get(PASS_LOCAL_ID_KEY),
170         setOfflineKD: encodedSetter(store)(PASS_OFFLINE_KD_KEY),
171         getOfflineKD: encodedGetter(store)(PASS_OFFLINE_KD_KEY),
172         setOfflineConfig: (config: Maybe<OfflineConfig>) => store.set(PASS_OFFLINE_CONFIG_KEY, config),
173         getOfflineConfig: (): Maybe<OfflineConfig> => store.get(PASS_OFFLINE_CONFIG_KEY),
174         setOfflineVerifier: encodedSetter(store)(PASS_OFFLINE_VERIFIER_KEY),
175         getOfflineVerifier: encodedGetter(store)(PASS_OFFLINE_VERIFIER_KEY),
176         setEncryptedOfflineKD: (enryptedKD: Maybe<string>) => store.set(PASS_ENCRYPTED_OFFLINE_KD, enryptedKD),
177         getEncryptedOfflineKD: (): Maybe<string> => store.get(PASS_ENCRYPTED_OFFLINE_KD),
179         setLockMode: (mode: LockMode): void => store.set(PASS_LOCK_MODE_KEY, mode),
180         getLockMode: (): LockMode => store.get(PASS_LOCK_MODE_KEY) ?? LockMode.NONE,
181         setLocked: (status: boolean): void => store.set(PASS_LOCK_STATE_KEY, status),
182         getLocked: (): Maybe<boolean> => store.get(PASS_LOCK_STATE_KEY),
183         setLockToken: encodedSetter(store)(PASS_LOCK_TOKEN_KEY),
184         getLockToken: encodedGetter(store)(PASS_LOCK_TOKEN_KEY),
185         setLockTTL: (ttl: Maybe<number>) => store.set(PASS_LOCK_TTL_KEY, ttl),
186         getLockTTL: (): Maybe<number> => store.get(PASS_LOCK_TTL_KEY),
187         setLockLastExtendTime: (extendTime: Maybe<number>): void => store.set(PASS_LOCK_EXTEND_TIME_KEY, extendTime),
188         getLockLastExtendTime: (): Maybe<number> => store.get(PASS_LOCK_EXTEND_TIME_KEY),
190         setLastUsedAt: (lastUsedAt: number): void => store.set(PASS_LAST_USED_AT, lastUsedAt),
191         getLastUsedAt: (): number => store.get(PASS_LAST_USED_AT) ?? 0,
193         setUnlockRetryCount: (count: number): void => store.set(PASS_UNLOCK_RETRY_KEY, count),
194         getUnlockRetryCount: (): number => store.get(PASS_UNLOCK_RETRY_KEY) ?? 0,
196         setExtraPassword: (enabled: boolean): void => store.set(PASS_EXTRA_PWD_KEY, enabled),
197         getExtraPassword: () => store.get(PASS_EXTRA_PWD_KEY) ?? false,
199         getSessionVersion: (): AuthSessionVersion => store.get(PASS_SESSION_VERSION_KEY) ?? SESSION_VERSION,
200         setSessionVersion: (version: AuthSessionVersion) => store.set(PASS_SESSION_VERSION_KEY, version),
202         getClientKey: encodedGetter(store)(PASS_CLIENT_KEY),
203         setClientKey: encodedSetter(store)(PASS_CLIENT_KEY),
205         getPersistent: (): Maybe<boolean> => store.get(PASS_PERSISTENT_SESSION_KEY),
206         setPersistent: (persistent: boolean): void => store.set(PASS_PERSISTENT_SESSION_KEY, persistent),
208         setCookieAuth: (enabled: boolean): void => store.set(PASS_COOKIE_AUTH_KEY, enabled),
209         getCookieAuth: (): boolean => store.get(PASS_COOKIE_AUTH_KEY) ?? false,
210     };
212     return authStore;
215 export let authStore: AuthStore;
216 export const exposeAuthStore = (value: AuthStore) => (authStore = value);