diff --git a/apps/cast/sentry.client.config.js b/apps/cast/sentry.client.config.js index 666d32bcf..2dc983f53 100644 --- a/apps/cast/sentry.client.config.js +++ b/apps/cast/sentry.client.config.js @@ -1,53 +1,5 @@ -import * as Sentry from '@sentry/nextjs'; -import { getSentryUserID } from 'utils/user'; -import { getHasOptedOutOfCrashReports } from 'utils/storage/index'; -import { runningInBrowser } from '@ente/shared/apps/browser'; -import { getSentryTunnelURL } from '@ente/shared/network/api'; -import { - getSentryDSN, - getSentryENV, - getSentryRelease, - getIsSentryEnabled, -} from 'constants/sentry'; +import { setupSentry } from '@ente/shared/sentry/config/sentry.config.base'; -const HAS_OPTED_OUT_OF_CRASH_REPORTING = - runningInBrowser() && getHasOptedOutOfCrashReports(); - -if (!HAS_OPTED_OUT_OF_CRASH_REPORTING) { - const SENTRY_DSN = getSentryDSN(); - const SENTRY_ENV = getSentryENV(); - const SENTRY_RELEASE = getSentryRelease(); - const IS_ENABLED = getIsSentryEnabled(); - - Sentry.init({ - dsn: SENTRY_DSN, - enabled: IS_ENABLED, - environment: SENTRY_ENV, - release: SENTRY_RELEASE, - attachStacktrace: true, - autoSessionTracking: false, - tunnel: getSentryTunnelURL(), - beforeSend(event) { - event.request = event.request || {}; - const currentURL = new URL(document.location.href); - currentURL.hash = ''; - event.request.url = currentURL; - return event; - }, - integrations: function (i) { - return i.filter(function (i) { - return i.name !== 'Breadcrumbs'; - }); - }, - // ... - // Note: if you want to override the automatic release value, do not set a - // `release` value here - use the environment variable `SENTRY_RELEASE`, so - // that it will also get attached to your source maps - }); - - const main = async () => { - Sentry.setUser({ id: await getSentryUserID() }); - }; - - main(); -} +const DEFAULT_SENTRY_DSN = + 'https://bd3656fc40d74d5e8f278132817963a3@sentry.ente.io/2'; +setupSentry(DEFAULT_SENTRY_DSN); diff --git a/apps/cast/sentry.server.config.js b/apps/cast/sentry.server.config.js index 79b62e6bd..e69de29bb 100644 --- a/apps/cast/sentry.server.config.js +++ b/apps/cast/sentry.server.config.js @@ -1,28 +0,0 @@ -import * as Sentry from '@sentry/nextjs'; -import { - getSentryDSN, - getSentryENV, - getSentryRelease, - getIsSentryEnabled, -} from 'constants/sentry'; - -import { getSentryUserID } from 'utils/user'; - -const SENTRY_DSN = getSentryDSN(); -const SENTRY_ENV = getSentryENV(); -const SENTRY_RELEASE = getSentryRelease(); -const IS_ENABLED = getIsSentryEnabled(); - -Sentry.init({ - dsn: SENTRY_DSN, - enabled: IS_ENABLED, - environment: SENTRY_ENV, - release: SENTRY_RELEASE, - autoSessionTracking: false, -}); - -const main = async () => { - Sentry.setUser({ id: await getSentryUserID() }); -}; - -main(); diff --git a/apps/cast/src/pages/slideshow.tsx b/apps/cast/src/pages/slideshow.tsx index 2036caf1a..15459369a 100644 --- a/apps/cast/src/pages/slideshow.tsx +++ b/apps/cast/src/pages/slideshow.tsx @@ -7,6 +7,7 @@ import { getCollectionWithKey } from 'services/collectionService'; import { syncFiles } from 'services/fileService'; import { EnteFile } from 'types/file'; import { downloadFileAsBlob, isRawFileFromFileName } from 'utils/file'; +import { logError } from 'utils/sentry'; export const SlideshowContext = createContext<{ showNextSlide: () => void; @@ -36,9 +37,13 @@ export default function Slideshow() { 'targetCollectionKey' ); + const castToken = window.localStorage.getItem('token'); + const collection = await getCollectionWithKey( Number(requestedCollectionID), - requestedCollectionKey + requestedCollectionKey, + 'cast', + castToken ); const files = await syncFiles('normal', [collection], () => {}); @@ -48,7 +53,9 @@ export default function Slideshow() { files.filter((file) => isFileEligibleForCast(file)) ); } - } catch { + } catch (e) { + logError(e, 'error during sync'); + alert('error, redirect to home' + e); router.push('/'); } }; diff --git a/apps/cast/src/services/HTTPService.ts b/apps/cast/src/services/HTTPService.ts deleted file mode 100644 index b42b16286..000000000 --- a/apps/cast/src/services/HTTPService.ts +++ /dev/null @@ -1,241 +0,0 @@ -import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; -import { addLogLine } from 'utils/logging'; -import { logError } from 'utils/sentry'; -import { ApiError, CustomError, isApiErrorResponse } from 'utils/error'; - -interface IHTTPHeaders { - [headerKey: string]: any; -} - -interface IQueryPrams { - [paramName: string]: any; -} - -/** - * Service to manage all HTTP calls. - */ -class HTTPService { - constructor() { - axios.interceptors.response.use( - (response) => Promise.resolve(response), - (error) => { - const config = error.config as AxiosRequestConfig; - if (error.response) { - const response = error.response as AxiosResponse; - let apiError: ApiError; - // The request was made and the server responded with a status code - // that falls out of the range of 2xx - if (isApiErrorResponse(response.data)) { - const responseData = response.data; - logError(error, 'HTTP Service Error', { - url: config.url, - method: config.method, - xRequestId: response.headers['x-request-id'], - httpStatus: response.status, - errMessage: responseData.message, - errCode: responseData.code, - }); - apiError = new ApiError( - responseData.message, - responseData.code, - response.status - ); - } else { - if (response.status >= 400 && response.status < 500) { - apiError = new ApiError( - CustomError.CLIENT_ERROR, - '', - response.status - ); - } else { - apiError = new ApiError( - CustomError.ServerError, - '', - response.status - ); - } - } - logError(apiError, 'HTTP Service Error', { - url: config.url, - method: config.method, - cfRay: response.headers['cf-ray'], - xRequestId: response.headers['x-request-id'], - httpStatus: response.status, - }); - throw apiError; - } else if (error.request) { - // The request was made but no response was received - // `error.request` is an instance of XMLHttpRequest in the browser and an instance of - // http.ClientRequest in node.js - addLogLine( - 'request failed- no response', - `url: ${config.url}`, - `method: ${config.method}` - ); - return Promise.reject(error); - } else { - // Something happened in setting up the request that triggered an Error - addLogLine( - 'request failed- axios error', - `url: ${config.url}`, - `method: ${config.method}` - ); - return Promise.reject(error); - } - } - ); - } - - /** - * header object to be append to all api calls. - */ - private headers: IHTTPHeaders = { - 'content-type': 'application/json', - }; - - /** - * Sets the headers to the given object. - */ - public setHeaders(headers: IHTTPHeaders) { - this.headers = { - ...this.headers, - ...headers, - }; - } - - /** - * Adds a header to list of headers. - */ - public appendHeader(key: string, value: string) { - this.headers = { - ...this.headers, - [key]: value, - }; - } - - /** - * Removes the given header. - */ - public removeHeader(key: string) { - this.headers[key] = undefined; - } - - /** - * Returns axios interceptors. - */ - // eslint-disable-next-line class-methods-use-this - public getInterceptors() { - return axios.interceptors; - } - - /** - * Generic HTTP request. - * This is done so that developer can use any functionality - * provided by axios. Here, only the set headers are spread - * over what was sent in config. - */ - public async request(config: AxiosRequestConfig, customConfig?: any) { - // eslint-disable-next-line no-param-reassign - config.headers = { - ...this.headers, - ...config.headers, - }; - if (customConfig?.cancel) { - config.cancelToken = new axios.CancelToken( - (c) => (customConfig.cancel.exec = c) - ); - } - return await axios({ ...config, ...customConfig }); - } - - /** - * Get request. - */ - public get( - url: string, - params?: IQueryPrams, - headers?: IHTTPHeaders, - customConfig?: any - ) { - return this.request( - { - headers, - method: 'GET', - params, - url, - }, - customConfig - ); - } - - /** - * Post request - */ - public post( - url: string, - data?: any, - params?: IQueryPrams, - headers?: IHTTPHeaders, - customConfig?: any - ) { - return this.request( - { - data, - headers, - method: 'POST', - params, - url, - }, - customConfig - ); - } - - /** - * Put request - */ - public put( - url: string, - data: any, - params?: IQueryPrams, - headers?: IHTTPHeaders, - customConfig?: any - ) { - return this.request( - { - data, - headers, - method: 'PUT', - params, - url, - }, - customConfig - ); - } - - /** - * Delete request - */ - public delete( - url: string, - data: any, - params?: IQueryPrams, - headers?: IHTTPHeaders, - customConfig?: any - ) { - return this.request( - { - data, - headers, - method: 'DELETE', - params, - url, - }, - customConfig - ); - } -} - -// Creates a Singleton Service. -// This will help me maintain common headers / functionality -// at a central place. -export default new HTTPService(); diff --git a/apps/cast/src/services/collectionService.ts b/apps/cast/src/services/collectionService.ts index 0db6569fa..9939c69f6 100644 --- a/apps/cast/src/services/collectionService.ts +++ b/apps/cast/src/services/collectionService.ts @@ -1,11 +1,6 @@ -import { getData, LS_KEYS } from 'utils/storage/localStorage'; -import localForage from 'utils/storage/localForage'; import { getActualKey } from '@ente/shared/user'; import { batch } from '@ente/shared/batch'; -import { getPublicKey } from './userService'; -import HTTPService from './HTTPService'; import { EnteFile } from 'types/file'; -import { logError } from 'utils/sentry'; import { CustomError } from 'utils/error'; import { sortFiles, @@ -71,6 +66,10 @@ import { EncryptedMagicMetadata } from 'types/magicMetadata'; import { VISIBILITY_STATE } from 'types/magicMetadata'; import { getEndpoint } from '@ente/shared/network/api'; import { getToken } from '@ente/shared/storage/localStorage/helpers'; +import HTTPService from '@ente/shared/network/HTTPService'; +import { logError } from '@ente/shared/sentry'; +import { LS_KEYS, getData } from '@ente/shared/storage/localStorage'; +import localForage from '@ente/shared/storage/localForage'; const ENDPOINT = getEndpoint(); const COLLECTION_TABLE = 'collections'; @@ -347,10 +346,17 @@ export const getCollection = async ( export const getCollectionWithKey = async ( collectionID: number, - key: string + key: string, + tokenType: 'user' | 'cast' = 'user', + castToken?: string ): Promise => { try { - const token = getToken(); + let token; + if (tokenType === 'user') { + token = getToken(); + } else { + token = castToken!; + } if (!token) { return; } @@ -974,39 +980,6 @@ export const renameCollection = async ( ); }; -export const shareCollection = async ( - collection: Collection, - withUserEmail: string, - role: string -) => { - try { - const cryptoWorker = await ComlinkCryptoWorker.getInstance(); - const token = getToken(); - const publicKey: string = await getPublicKey(withUserEmail); - const encryptedKey = await cryptoWorker.boxSeal( - collection.key, - publicKey - ); - const shareCollectionRequest = { - collectionID: collection.id, - email: withUserEmail, - role: role, - encryptedKey, - }; - await HTTPService.post( - `${ENDPOINT}/collections/share`, - shareCollectionRequest, - null, - { - 'X-Auth-Token': token, - } - ); - } catch (e) { - logError(e, 'share collection failed '); - throw e; - } -}; - export const unshareCollection = async ( collection: Collection, withUserEmail: string diff --git a/apps/cast/src/services/downloadManager.ts b/apps/cast/src/services/downloadManager.ts index e0c5437df..ba9eef5ae 100644 --- a/apps/cast/src/services/downloadManager.ts +++ b/apps/cast/src/services/downloadManager.ts @@ -3,10 +3,8 @@ import { getRenderableFileURL, createTypedObjectURL, } from 'utils/file'; -import HTTPService from './HTTPService'; import { EnteFile } from 'types/file'; -import { logError } from 'utils/sentry'; import { FILE_TYPE } from 'constants/file'; import { CustomError } from 'utils/error'; import ComlinkCryptoWorker from 'utils/comlink/ComlinkCryptoWorker'; @@ -16,9 +14,10 @@ import { Remote } from 'comlink'; import { DedicatedCryptoWorker } from 'worker/crypto.worker'; import { LimitedCache } from 'types/cache'; import { retryAsyncFunction } from 'utils/network'; -import { addLogLine } from 'utils/logging'; import { getToken } from '@ente/shared/storage/localStorage/helpers'; import { getFileURL, getThumbnailURL } from '@ente/shared/network/api'; +import HTTPService from '@ente/shared/network/HTTPService'; +import { logError } from '@ente/shared/sentry'; class DownloadManager { private fileObjectURLPromise = new Map< diff --git a/apps/cast/src/services/fileService.ts b/apps/cast/src/services/fileService.ts index 096af1bd9..397e7b645 100644 --- a/apps/cast/src/services/fileService.ts +++ b/apps/cast/src/services/fileService.ts @@ -3,7 +3,7 @@ import localForage from 'utils/storage/localForage'; import { getToken } from '@ente/shared/storage/localStorage/helpers'; import { Collection } from 'types/collection'; -import HTTPService from './HTTPService'; + import { logError } from 'utils/sentry'; import { decryptFile, @@ -29,6 +29,7 @@ import { } from './collectionService'; import { REQUEST_BATCH_SIZE } from 'constants/api'; import { batch } from '@ente/shared/batch'; +import HTTPService from '@ente/shared/network/HTTPService'; const ENDPOINT = getEndpoint(); const FILES_TABLE = 'files'; diff --git a/apps/cast/src/services/kexService.ts b/apps/cast/src/services/kexService.ts deleted file mode 100644 index bb45d1462..000000000 --- a/apps/cast/src/services/kexService.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { logError } from 'utils/sentry'; -import HTTPService from './HTTPService'; -import { getEndpoint } from '@ente/shared/network/api'; - -const ENDPOINT = getEndpoint(); - -export const getKexValue = async (key: string) => { - let resp; - try { - resp = await HTTPService.get(`${ENDPOINT}/kex/get`, { - identifier: key, - }); - } catch (e) { - logError(e, 'failed to get kex value'); - throw e; - } - - return resp.data.wrappedKey; -}; - -export const setKexValue = async (key: string, value: string) => { - try { - await HTTPService.put(ENDPOINT + '/kex/add', { - customIdentifier: key, - wrappedKey: value, - }); - } catch (e) { - logError(e, 'failed to set kex value'); - throw e; - } -}; diff --git a/apps/cast/src/services/publicCollectionDownloadManager.ts b/apps/cast/src/services/publicCollectionDownloadManager.ts index b38fbe004..f679b8ab0 100644 --- a/apps/cast/src/services/publicCollectionDownloadManager.ts +++ b/apps/cast/src/services/publicCollectionDownloadManager.ts @@ -3,10 +3,8 @@ import { getRenderableFileURL, createTypedObjectURL, } from 'utils/file'; -import HTTPService from './HTTPService'; import { EnteFile } from 'types/file'; -import { logError } from 'utils/sentry'; import { FILE_TYPE } from 'constants/file'; import { CustomError } from 'utils/error'; import ComlinkCryptoWorker from 'utils/comlink/ComlinkCryptoWorker'; @@ -17,6 +15,8 @@ import { getPublicCollectionFileURL, getPublicCollectionThumbnailURL, } from '@ente/shared/network/api'; +import HTTPService from '@ente/shared/network/HTTPService'; +import { logError } from '@ente/shared/sentry'; class PublicCollectionDownloadManager { private fileObjectURLPromise = new Map< diff --git a/apps/cast/src/services/readerService.ts b/apps/cast/src/services/readerService.ts index c35122597..c738026e1 100644 --- a/apps/cast/src/services/readerService.ts +++ b/apps/cast/src/services/readerService.ts @@ -1,6 +1,6 @@ +import { logError } from '@ente/shared/sentry'; import { ElectronFile } from 'types/upload'; import { convertBytesToHumanReadable } from 'utils/file/size'; -import { logError } from 'utils/sentry'; export async function getUint8ArrayView( file: Blob | ElectronFile diff --git a/apps/cast/src/services/userService.ts b/apps/cast/src/services/userService.ts deleted file mode 100644 index 15a93a80d..000000000 --- a/apps/cast/src/services/userService.ts +++ /dev/null @@ -1,757 +0,0 @@ -import { PAGES } from 'constants/pages'; -import { clearKeys } from 'utils/storage/sessionStorage'; -import router from 'next/router'; -import { clearData, getData, LS_KEYS } from 'utils/storage/localStorage'; -import localForage from 'utils/storage/localForage'; -import { getToken } from '@ente/shared/storage/localStorage/helpers'; -import HTTPService from './HTTPService'; -import { - computeVerifierHelper, - generateLoginSubKey, - generateSRPClient, - getRecoveryKey, -} from 'utils/crypto'; -import { logError } from 'utils/sentry'; -import { eventBus, Events } from './events'; -import { - KeyAttributes, - RecoveryKey, - TwoFactorSecret, - TwoFactorVerificationResponse, - TwoFactorRecoveryResponse, - UserDetails, - DeleteChallengeResponse, - GetRemoteStoreValueResponse, - SetupSRPRequest, - CreateSRPSessionResponse, - UserVerificationResponse, - GetFeatureFlagResponse, - SetupSRPResponse, - CompleteSRPSetupRequest, - CompleteSRPSetupResponse, - SRPSetupAttributes, - SRPAttributes, - UpdateSRPAndKeysRequest, - UpdateSRPAndKeysResponse, - GetSRPAttributesResponse, -} from 'types/user'; -import { ApiError, CustomError } from 'utils/error'; -import isElectron from 'is-electron'; -// import safeStorageService from './electron/safeStorage'; -// import { deleteAllCache } from 'utils/storage/cache'; -import { B64EncryptionResult } from 'types/crypto'; -import { getLocalFamilyData, isPartOfFamily } from 'utils/user/family'; -import { AxiosResponse, HttpStatusCode } from 'axios'; -import { APPS, getAppName } from 'constants/apps'; -import { addLocalLog } from 'utils/logging'; -import { convertBase64ToBuffer, convertBufferToBase64 } from 'utils/user'; -import { setLocalMapEnabled } from 'utils/storage'; -import InMemoryStore, { MS_KEYS } from './InMemoryStore'; -import { - getEndpoint, - getFamilyPortalURL, - isDevDeployment, -} from '@ente/shared/network/api'; - -const ENDPOINT = getEndpoint(); - -const HAS_SET_KEYS = 'hasSetKeys'; - -export const sendOtt = (email: string) => { - const appName = getAppName(); - return HTTPService.post(`${ENDPOINT}/users/ott`, { - email, - client: appName === APPS.AUTH ? 'totp' : 'web', - }); -}; - -export const getPublicKey = async (email: string) => { - const token = getToken(); - - const resp = await HTTPService.get( - `${ENDPOINT}/users/public-key`, - { email }, - { - 'X-Auth-Token': token, - } - ); - return resp.data.publicKey; -}; - -export const getPaymentToken = async () => { - const token = getToken(); - - const resp = await HTTPService.get( - `${ENDPOINT}/users/payment-token`, - null, - { - 'X-Auth-Token': token, - } - ); - return resp.data['paymentToken']; -}; - -export const getFamiliesToken = async () => { - try { - const token = getToken(); - - const resp = await HTTPService.get( - `${ENDPOINT}/users/families-token`, - null, - { - 'X-Auth-Token': token, - } - ); - return resp.data['familiesToken']; - } catch (e) { - logError(e, 'failed to get family token'); - throw e; - } -}; - -export const getRoadmapRedirectURL = async () => { - try { - const token = getToken(); - - const resp = await HTTPService.get( - `${ENDPOINT}/users/roadmap/v2`, - null, - { - 'X-Auth-Token': token, - } - ); - return resp.data['url']; - } catch (e) { - logError(e, 'failed to get roadmap url'); - throw e; - } -}; - -export const verifyOtt = (email: string, ott: string) => - HTTPService.post(`${ENDPOINT}/users/verify-email`, { email, ott }); - -export const putAttributes = (token: string, keyAttributes: KeyAttributes) => - HTTPService.put(`${ENDPOINT}/users/attributes`, { keyAttributes }, null, { - 'X-Auth-Token': token, - }); - -export const setRecoveryKey = (token: string, recoveryKey: RecoveryKey) => - HTTPService.put(`${ENDPOINT}/users/recovery-key`, recoveryKey, null, { - 'X-Auth-Token': token, - }); - -export const logoutUser = async () => { - try { - try { - // ignore server logout result as logoutUser can be triggered before sign up or on token expiry - await _logout(); - } catch (e) { - //ignore - } - try { - InMemoryStore.clear(); - } catch (e) { - logError(e, 'clear InMemoryStore failed'); - } - try { - clearKeys(); - } catch (e) { - logError(e, 'clearKeys failed'); - } - try { - clearData(); - } catch (e) { - logError(e, 'clearData failed'); - } - try { - // await deleteAllCache(); - } catch (e) { - logError(e, 'deleteAllCache failed'); - } - try { - await clearFiles(); - } catch (e) { - logError(e, 'clearFiles failed'); - } - if (isElectron()) { - try { - // safeStorageService.clearElectronStore(); - } catch (e) { - logError(e, 'clearElectronStore failed'); - } - } - try { - eventBus.emit(Events.LOGOUT); - } catch (e) { - logError(e, 'Error in logout handlers'); - } - router.push(PAGES.ROOT); - } catch (e) { - logError(e, 'logoutUser failed'); - } -}; - -export const clearFiles = async () => { - await localForage.clear(); -}; - -export const isTokenValid = async (token: string) => { - try { - const resp = await HTTPService.get( - `${ENDPOINT}/users/session-validity/v2`, - null, - { - 'X-Auth-Token': token, - } - ); - try { - if (resp.data[HAS_SET_KEYS] === undefined) { - throw Error('resp.data.hasSetKey undefined'); - } - if (!resp.data['hasSetKeys']) { - try { - await putAttributes( - token, - getData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES) - ); - } catch (e) { - logError(e, 'put attribute failed'); - } - } - } catch (e) { - logError(e, 'hasSetKeys not set in session validity response'); - } - return true; - } catch (e) { - logError(e, 'session-validity api call failed'); - if ( - e instanceof ApiError && - e.httpStatusCode === HttpStatusCode.Unauthorized - ) { - return false; - } else { - return true; - } - } -}; - -export const setupTwoFactor = async () => { - const resp = await HTTPService.post( - `${ENDPOINT}/users/two-factor/setup`, - null, - null, - { - 'X-Auth-Token': getToken(), - } - ); - return resp.data as TwoFactorSecret; -}; - -export const enableTwoFactor = async ( - code: string, - recoveryEncryptedTwoFactorSecret: B64EncryptionResult -) => { - await HTTPService.post( - `${ENDPOINT}/users/two-factor/enable`, - { - code, - encryptedTwoFactorSecret: - recoveryEncryptedTwoFactorSecret.encryptedData, - twoFactorSecretDecryptionNonce: - recoveryEncryptedTwoFactorSecret.nonce, - }, - null, - { - 'X-Auth-Token': getToken(), - } - ); -}; - -export const verifyTwoFactor = async (code: string, sessionID: string) => { - const resp = await HTTPService.post( - `${ENDPOINT}/users/two-factor/verify`, - { - code, - sessionID, - }, - null - ); - return resp.data as TwoFactorVerificationResponse; -}; - -export const recoverTwoFactor = async (sessionID: string) => { - const resp = await HTTPService.get(`${ENDPOINT}/users/two-factor/recover`, { - sessionID, - }); - return resp.data as TwoFactorRecoveryResponse; -}; - -export const removeTwoFactor = async (sessionID: string, secret: string) => { - const resp = await HTTPService.post(`${ENDPOINT}/users/two-factor/remove`, { - sessionID, - secret, - }); - return resp.data as TwoFactorVerificationResponse; -}; - -export const disableTwoFactor = async () => { - await HTTPService.post(`${ENDPOINT}/users/two-factor/disable`, null, null, { - 'X-Auth-Token': getToken(), - }); -}; - -export const getTwoFactorStatus = async () => { - const resp = await HTTPService.get( - `${ENDPOINT}/users/two-factor/status`, - null, - { - 'X-Auth-Token': getToken(), - } - ); - return resp.data['status']; -}; - -export const _logout = async () => { - if (!getToken()) return true; - try { - await HTTPService.post(`${ENDPOINT}/users/logout`, null, null, { - 'X-Auth-Token': getToken(), - }); - return true; - } catch (e) { - logError(e, '/users/logout failed'); - return false; - } -}; - -export const sendOTTForEmailChange = async (email: string) => { - if (!getToken()) { - return null; - } - await HTTPService.post(`${ENDPOINT}/users/ott`, { - email, - client: 'web', - purpose: 'change', - }); -}; - -export const changeEmail = async (email: string, ott: string) => { - if (!getToken()) { - return null; - } - await HTTPService.post( - `${ENDPOINT}/users/change-email`, - { - email, - ott, - }, - null, - { - 'X-Auth-Token': getToken(), - } - ); -}; - -export const getUserDetailsV2 = async (): Promise => { - try { - const token = getToken(); - - const resp = await HTTPService.get( - `${ENDPOINT}/users/details/v2`, - null, - { - 'X-Auth-Token': token, - } - ); - return resp.data; - } catch (e) { - logError(e, 'failed to get user details v2'); - throw e; - } -}; - -export const getFamilyPortalRedirectURL = async () => { - try { - const jwtToken = await getFamiliesToken(); - const isFamilyCreated = isPartOfFamily(getLocalFamilyData()); - return `${getFamilyPortalURL()}?token=${jwtToken}&isFamilyCreated=${isFamilyCreated}&redirectURL=${ - window.location.origin - }/gallery`; - } catch (e) { - logError(e, 'unable to generate to family portal URL'); - throw e; - } -}; - -export const getAccountDeleteChallenge = async () => { - try { - const token = getToken(); - - const resp = await HTTPService.get( - `${ENDPOINT}/users/delete-challenge`, - null, - { - 'X-Auth-Token': token, - } - ); - return resp.data as DeleteChallengeResponse; - } catch (e) { - logError(e, 'failed to get account delete challenge'); - throw e; - } -}; - -export const deleteAccount = async ( - challenge: string, - reason: string, - feedback: string -) => { - try { - const token = getToken(); - if (!token) { - return; - } - - await HTTPService.delete( - `${ENDPOINT}/users/delete`, - { challenge, reason, feedback }, - null, - { - 'X-Auth-Token': token, - } - ); - } catch (e) { - logError(e, 'deleteAccount api call failed'); - throw e; - } -}; - -// Ensure that the keys in local storage are not malformed by verifying that the -// recoveryKey can be decrypted with the masterKey. -// Note: This is not bullet-proof. -export const validateKey = async () => { - try { - await getRecoveryKey(); - return true; - } catch (e) { - await logoutUser(); - return false; - } -}; - -export const getFaceSearchEnabledStatus = async () => { - try { - const token = getToken(); - const resp: AxiosResponse = - await HTTPService.get( - `${ENDPOINT}/remote-store`, - { - key: 'faceSearchEnabled', - defaultValue: false, - }, - { - 'X-Auth-Token': token, - } - ); - return resp.data.value === 'true'; - } catch (e) { - logError(e, 'failed to get face search enabled status'); - throw e; - } -}; - -export const updateFaceSearchEnabledStatus = async (newStatus: boolean) => { - try { - const token = getToken(); - await HTTPService.post( - `${ENDPOINT}/remote-store/update`, - { - key: 'faceSearchEnabled', - value: newStatus.toString(), - }, - null, - { - 'X-Auth-Token': token, - } - ); - } catch (e) { - logError(e, 'failed to update face search enabled status'); - throw e; - } -}; - -export const syncMapEnabled = async () => { - try { - const status = await getMapEnabledStatus(); - setLocalMapEnabled(status); - } catch (e) { - logError(e, 'failed to sync map enabled status'); - throw e; - } -}; - -export const getMapEnabledStatus = async () => { - try { - const token = getToken(); - const resp: AxiosResponse = - await HTTPService.get( - `${ENDPOINT}/remote-store`, - { - key: 'mapEnabled', - defaultValue: false, - }, - { - 'X-Auth-Token': token, - } - ); - return resp.data.value === 'true'; - } catch (e) { - logError(e, 'failed to get map enabled status'); - throw e; - } -}; - -export const updateMapEnabledStatus = async (newStatus: boolean) => { - try { - const token = getToken(); - await HTTPService.post( - `${ENDPOINT}/remote-store/update`, - { - key: 'mapEnabled', - value: newStatus.toString(), - }, - null, - { - 'X-Auth-Token': token, - } - ); - } catch (e) { - logError(e, 'failed to update map enabled status'); - throw e; - } -}; - -export async function getDisableCFUploadProxyFlag(): Promise { - try { - const disableCFUploadProxy = - process.env.NEXT_PUBLIC_DISABLE_CF_UPLOAD_PROXY; - if (isDevDeployment() && typeof disableCFUploadProxy !== 'undefined') { - return disableCFUploadProxy === 'true'; - } - const featureFlags = ( - await fetch('https://static.ente.io/feature_flags.json') - ).json() as GetFeatureFlagResponse; - return featureFlags.disableCFUploadProxy; - } catch (e) { - logError(e, 'failed to get feature flags'); - return false; - } -} - -export const getSRPAttributes = async ( - email: string -): Promise => { - try { - const resp = await HTTPService.get(`${ENDPOINT}/users/srp/attributes`, { - email, - }); - return (resp.data as GetSRPAttributesResponse).attributes; - } catch (e) { - logError(e, 'failed to get SRP attributes'); - return null; - } -}; - -export const configureSRP = async ({ - srpSalt, - srpUserID, - srpVerifier, - loginSubKey, -}: SRPSetupAttributes) => { - try { - const srpConfigureInProgress = InMemoryStore.get( - MS_KEYS.SRP_CONFIGURE_IN_PROGRESS - ); - if (srpConfigureInProgress) { - throw Error('SRP configure already in progress'); - } - InMemoryStore.set(MS_KEYS.SRP_CONFIGURE_IN_PROGRESS, true); - const srpClient = await generateSRPClient( - srpSalt, - srpUserID, - loginSubKey - ); - - const srpA = convertBufferToBase64(srpClient.computeA()); - - addLocalLog(() => `srp a: ${srpA}`); - const token = getToken(); - const { setupID, srpB } = await startSRPSetup(token, { - srpA, - srpUserID, - srpSalt, - srpVerifier, - }); - - srpClient.setB(convertBase64ToBuffer(srpB)); - - const srpM1 = convertBufferToBase64(srpClient.computeM1()); - - const { srpM2 } = await completeSRPSetup(token, { - srpM1, - setupID, - }); - - srpClient.checkM2(convertBase64ToBuffer(srpM2)); - } catch (e) { - logError(e, 'srp configure failed'); - throw e; - } finally { - InMemoryStore.set(MS_KEYS.SRP_CONFIGURE_IN_PROGRESS, false); - } -}; - -export const startSRPSetup = async ( - token: string, - setupSRPRequest: SetupSRPRequest -): Promise => { - try { - const resp = await HTTPService.post( - `${ENDPOINT}/users/srp/setup`, - setupSRPRequest, - null, - { - 'X-Auth-Token': token, - } - ); - - return resp.data as SetupSRPResponse; - } catch (e) { - logError(e, 'failed to post SRP attributes'); - throw e; - } -}; - -export const completeSRPSetup = async ( - token: string, - completeSRPSetupRequest: CompleteSRPSetupRequest -) => { - try { - const resp = await HTTPService.post( - `${ENDPOINT}/users/srp/complete`, - completeSRPSetupRequest, - null, - { - 'X-Auth-Token': token, - } - ); - return resp.data as CompleteSRPSetupResponse; - } catch (e) { - logError(e, 'failed to complete SRP setup'); - throw e; - } -}; - -export const loginViaSRP = async ( - srpAttributes: SRPAttributes, - kek: string -): Promise => { - try { - const loginSubKey = await generateLoginSubKey(kek); - const srpClient = await generateSRPClient( - srpAttributes.srpSalt, - srpAttributes.srpUserID, - loginSubKey - ); - const srpVerifier = computeVerifierHelper( - srpAttributes.srpSalt, - srpAttributes.srpUserID, - loginSubKey - ); - addLocalLog(() => `srp verifier: ${srpVerifier}`); - const srpA = srpClient.computeA(); - const { srpB, sessionID } = await createSRPSession( - srpAttributes.srpUserID, - convertBufferToBase64(srpA) - ); - srpClient.setB(convertBase64ToBuffer(srpB)); - - const m1 = srpClient.computeM1(); - addLocalLog(() => `srp m1: ${convertBufferToBase64(m1)}`); - const { srpM2, ...rest } = await verifySRPSession( - sessionID, - srpAttributes.srpUserID, - convertBufferToBase64(m1) - ); - addLocalLog(() => `srp verify session successful,srpM2: ${srpM2}`); - - srpClient.checkM2(convertBase64ToBuffer(srpM2)); - - addLocalLog(() => `srp server verify successful`); - - return rest; - } catch (e) { - logError(e, 'srp verify failed'); - throw e; - } -}; - -export const createSRPSession = async (srpUserID: string, srpA: string) => { - try { - const resp = await HTTPService.post( - `${ENDPOINT}/users/srp/create-session`, - { - srpUserID, - srpA, - } - ); - return resp.data as CreateSRPSessionResponse; - } catch (e) { - logError(e, 'createSRPSession failed'); - throw e; - } -}; - -export const verifySRPSession = async ( - sessionID: string, - srpUserID: string, - srpM1: string -) => { - try { - const resp = await HTTPService.post( - `${ENDPOINT}/users/srp/verify-session`, - { - sessionID, - srpUserID, - srpM1, - }, - null - ); - return resp.data as UserVerificationResponse; - } catch (e) { - logError(e, 'verifySRPSession failed'); - if ( - e instanceof ApiError && - e.httpStatusCode === HttpStatusCode.Forbidden - ) { - throw Error(CustomError.INCORRECT_PASSWORD); - } else { - throw e; - } - } -}; - -export const updateSRPAndKeys = async ( - token: string, - updateSRPAndKeyRequest: UpdateSRPAndKeysRequest -): Promise => { - const resp = await HTTPService.post( - `${ENDPOINT}/users/srp/update`, - updateSRPAndKeyRequest, - null, - { - 'X-Auth-Token': token, - } - ); - return resp.data as UpdateSRPAndKeysResponse; -}; diff --git a/apps/cast/src/services/wasmHeicConverter/wasmHEICConverterService.ts b/apps/cast/src/services/wasmHeicConverter/wasmHEICConverterService.ts index 3ff88a6fb..747c4d560 100644 --- a/apps/cast/src/services/wasmHeicConverter/wasmHEICConverterService.ts +++ b/apps/cast/src/services/wasmHeicConverter/wasmHEICConverterService.ts @@ -1,12 +1,12 @@ import QueueProcessor from 'services/queueProcessor'; import { CustomError } from 'utils/error'; import { retryAsyncFunction } from 'utils/network'; -import { logError } from 'utils/sentry'; import { addLogLine } from 'utils/logging'; import { DedicatedConvertWorker } from 'worker/convert.worker'; import { ComlinkWorker } from 'utils/comlink/comlinkWorker'; import { convertBytesToHumanReadable } from 'utils/file/size'; import { getDedicatedConvertWorker } from 'utils/comlink/ComlinkConvertWorker'; +import { logError } from '@ente/shared/sentry'; const WORKER_POOL_SIZE = 2; const MAX_CONVERSION_IN_PARALLEL = 1; diff --git a/apps/cast/src/utils/sentry/index.ts b/apps/cast/src/utils/sentry/index.ts deleted file mode 100644 index 43e2a472f..000000000 --- a/apps/cast/src/utils/sentry/index.ts +++ /dev/null @@ -1,75 +0,0 @@ -import * as Sentry from '@sentry/nextjs'; -import { addLocalLog, addLogLine } from 'utils/logging'; -import { getSentryUserID } from 'utils/user'; -import InMemoryStore, { MS_KEYS } from 'services/InMemoryStore'; -import { getHasOptedOutOfCrashReports } from 'utils/storage'; -import { ApiError } from 'utils/error'; - -export const logError = async ( - error: any, - msg: string, - info?: Record, - skipAddLogLine = false -) => { - const err = errorWithContext(error, msg); - if (!skipAddLogLine) { - if (error instanceof ApiError) { - addLogLine(`error: ${error?.name} ${error?.message} - msg: ${msg} errorCode: ${JSON.stringify(error?.errCode)} - httpStatusCode: ${JSON.stringify(error?.httpStatusCode)} ${ - info ? `info: ${JSON.stringify(info)}` : '' - } - ${error?.stack}`); - } else { - addLogLine( - `error: ${error?.name} ${error?.message} - msg: ${msg} ${info ? `info: ${JSON.stringify(info)}` : ''} - ${error?.stack}` - ); - } - } - if (!InMemoryStore.has(MS_KEYS.OPT_OUT_OF_CRASH_REPORTS)) { - const optedOutOfCrashReports = getHasOptedOutOfCrashReports(); - InMemoryStore.set( - MS_KEYS.OPT_OUT_OF_CRASH_REPORTS, - optedOutOfCrashReports - ); - } - if (InMemoryStore.get(MS_KEYS.OPT_OUT_OF_CRASH_REPORTS)) { - addLocalLog(() => `skipping sentry error: ${error?.name}`); - return; - } - if (isErrorUnnecessaryForSentry(error)) { - return; - } - - Sentry.captureException(err, { - level: 'info', - user: { id: await getSentryUserID() }, - contexts: { - ...(info && { - info: info, - }), - rootCause: { message: error?.message, completeError: error }, - }, - }); -}; - -// copy of errorWithContext to prevent importing error util -function errorWithContext(originalError: Error, context: string) { - const errorWithContext = new Error(context); - errorWithContext.stack = - errorWithContext.stack.split('\n').slice(2, 4).join('\n') + - '\n' + - originalError.stack; - return errorWithContext; -} - -function isErrorUnnecessaryForSentry(error: any) { - if (error?.message?.includes('Network Error')) { - return true; - } else if (error?.status === 401) { - return true; - } - return false; -} diff --git a/apps/cast/src/utils/storage/index.ts b/apps/cast/src/utils/storage/index.ts deleted file mode 100644 index 87e858d43..000000000 --- a/apps/cast/src/utils/storage/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Language } from 'constants/locale'; -import { getData, LS_KEYS, setData } from './localStorage'; - -export const isFirstLogin = () => - getData(LS_KEYS.IS_FIRST_LOGIN)?.status ?? false; - -export function setIsFirstLogin(status) { - setData(LS_KEYS.IS_FIRST_LOGIN, { status }); -} - -export const justSignedUp = () => - getData(LS_KEYS.JUST_SIGNED_UP)?.status ?? false; - -export function setJustSignedUp(status) { - setData(LS_KEYS.JUST_SIGNED_UP, { status }); -} - -export function getLivePhotoInfoShownCount() { - return getData(LS_KEYS.LIVE_PHOTO_INFO_SHOWN_COUNT)?.count ?? 0; -} - -export function setLivePhotoInfoShownCount(count) { - setData(LS_KEYS.LIVE_PHOTO_INFO_SHOWN_COUNT, { count }); -} - -export function getUserLocale(): Language { - return getData(LS_KEYS.LOCALE)?.value; -} - -export function getLocalMapEnabled(): boolean { - return getData(LS_KEYS.MAP_ENABLED)?.value ?? false; -} - -export function setLocalMapEnabled(value: boolean) { - setData(LS_KEYS.MAP_ENABLED, { value }); -} - -export function getHasOptedOutOfCrashReports(): boolean { - return getData(LS_KEYS.OPT_OUT_OF_CRASH_REPORTS)?.value ?? false; -} diff --git a/apps/cast/src/utils/storage/localForage.ts b/apps/cast/src/utils/storage/localForage.ts deleted file mode 100644 index 10e98cd19..000000000 --- a/apps/cast/src/utils/storage/localForage.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { runningInBrowser } from '@ente/shared/apps/browser'; -import localForage from 'localforage'; - -if (runningInBrowser()) { - localForage.config({ - name: 'ente-files', - version: 1.0, - storeName: 'files', - }); -} -export default localForage; diff --git a/apps/cast/src/utils/storage/localStorage.ts b/apps/cast/src/utils/storage/localStorage.ts deleted file mode 100644 index ef9d32efb..000000000 --- a/apps/cast/src/utils/storage/localStorage.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { logError } from 'utils/sentry'; - -export enum LS_KEYS { - USER = 'user', - SESSION = 'session', - KEY_ATTRIBUTES = 'keyAttributes', - ORIGINAL_KEY_ATTRIBUTES = 'originalKeyAttributes', - SUBSCRIPTION = 'subscription', - FAMILY_DATA = 'familyData', - PLANS = 'plans', - IS_FIRST_LOGIN = 'isFirstLogin', - JUST_SIGNED_UP = 'justSignedUp', - SHOW_BACK_BUTTON = 'showBackButton', - EXPORT = 'export', - AnonymizedUserID = 'anonymizedUserID', - THUMBNAIL_FIX_STATE = 'thumbnailFixState', - LIVE_PHOTO_INFO_SHOWN_COUNT = 'livePhotoInfoShownCount', - LOGS = 'logs', - USER_DETAILS = 'userDetails', - COLLECTION_SORT_BY = 'collectionSortBy', - THEME = 'theme', - WAIT_TIME = 'waitTime', - API_ENDPOINT = 'apiEndpoint', - LOCALE = 'locale', - MAP_ENABLED = 'mapEnabled', - SRP_SETUP_ATTRIBUTES = 'srpSetupAttributes', - SRP_ATTRIBUTES = 'srpAttributes', - OPT_OUT_OF_CRASH_REPORTS = 'optOutOfCrashReports', - CF_PROXY_DISABLED = 'cfProxyDisabled', -} - -export const setData = (key: LS_KEYS, value: object) => { - if (typeof localStorage === 'undefined') { - return null; - } - localStorage.setItem(key, JSON.stringify(value)); -}; - -export const removeData = (key: LS_KEYS) => { - if (typeof localStorage === 'undefined') { - return null; - } - localStorage.removeItem(key); -}; - -export const getData = (key: LS_KEYS) => { - try { - if ( - typeof localStorage === 'undefined' || - typeof key === 'undefined' || - typeof localStorage.getItem(key) === 'undefined' || - localStorage.getItem(key) === 'undefined' - ) { - return null; - } - const data = localStorage.getItem(key); - return data && JSON.parse(data); - } catch (e) { - logError(e, 'Failed to Parse JSON for key ' + key); - } -}; - -export const clearData = () => { - if (typeof localStorage === 'undefined') { - return null; - } - localStorage.clear(); -}; diff --git a/apps/cast/src/utils/storage/sessionStorage.ts b/apps/cast/src/utils/storage/sessionStorage.ts deleted file mode 100644 index bad871f76..000000000 --- a/apps/cast/src/utils/storage/sessionStorage.ts +++ /dev/null @@ -1,32 +0,0 @@ -export enum SESSION_KEYS { - ENCRYPTION_KEY = 'encryptionKey', - KEY_ENCRYPTION_KEY = 'keyEncryptionKey', -} - -export const setKey = (key: SESSION_KEYS, value: object) => { - if (typeof sessionStorage === 'undefined') { - return null; - } - sessionStorage.setItem(key, JSON.stringify(value)); -}; - -export const getKey = (key: SESSION_KEYS) => { - if (typeof sessionStorage === 'undefined') { - return null; - } - return JSON.parse(sessionStorage.getItem(key)); -}; - -export const removeKey = (key: SESSION_KEYS) => { - if (typeof sessionStorage === 'undefined') { - return null; - } - sessionStorage.removeItem(key); -}; - -export const clearKeys = () => { - if (typeof sessionStorage === 'undefined') { - return null; - } - sessionStorage.clear(); -}; diff --git a/apps/cast/src/utils/user/family.ts b/apps/cast/src/utils/user/family.ts deleted file mode 100644 index 95f51eeea..000000000 --- a/apps/cast/src/utils/user/family.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { FamilyData, FamilyMember, User } from 'types/user'; -import { logError } from 'utils/sentry'; -import { getData, LS_KEYS } from 'utils/storage/localStorage'; - -export function getLocalFamilyData(): FamilyData { - return getData(LS_KEYS.FAMILY_DATA); -} - -// isPartOfFamily return true if the current user is part of some family plan -export function isPartOfFamily(familyData: FamilyData): boolean { - return Boolean( - familyData && familyData.members && familyData.members.length > 0 - ); -} - -// hasNonAdminFamilyMembers return true if the admin user has members in his family -export function hasNonAdminFamilyMembers(familyData: FamilyData): boolean { - return Boolean(isPartOfFamily(familyData) && familyData.members.length > 1); -} - -export function isFamilyAdmin(familyData: FamilyData): boolean { - const familyAdmin: FamilyMember = getFamilyPlanAdmin(familyData); - const user: User = getData(LS_KEYS.USER); - return familyAdmin.email === user.email; -} - -export function getFamilyPlanAdmin(familyData: FamilyData): FamilyMember { - if (isPartOfFamily(familyData)) { - return familyData.members.find((x) => x.isAdmin); - } else { - logError( - Error( - 'verify user is part of family plan before calling this method' - ), - 'invalid getFamilyPlanAdmin call' - ); - } -} - -export function getTotalFamilyUsage(familyData: FamilyData): number { - return familyData.members.reduce( - (sum, currentMember) => sum + currentMember.usage, - 0 - ); -} diff --git a/apps/cast/src/utils/user/index.ts b/apps/cast/src/utils/user/index.ts deleted file mode 100644 index bfc75ca94..000000000 --- a/apps/cast/src/utils/user/index.ts +++ /dev/null @@ -1,52 +0,0 @@ -import isElectron from 'is-electron'; -import { UserDetails } from 'types/user'; -import { getData, LS_KEYS, setData } from 'utils/storage/localStorage'; -// import ElectronService from 'services/electron/common'; -import { Buffer } from 'buffer'; - -export function makeID(length) { - let result = ''; - const characters = - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - const charactersLength = characters.length; - for (let i = 0; i < length; i++) { - result += characters.charAt( - Math.floor(Math.random() * charactersLength) - ); - } - return result; -} - -export async function getSentryUserID() { - if (isElectron()) { - // return await ElectronService.getSentryUserID(); - } else { - let anonymizeUserID = getData(LS_KEYS.AnonymizedUserID)?.id; - if (!anonymizeUserID) { - anonymizeUserID = makeID(6); - setData(LS_KEYS.AnonymizedUserID, { id: anonymizeUserID }); - } - return anonymizeUserID; - } -} - -export function getLocalUserDetails(): UserDetails { - return getData(LS_KEYS.USER_DETAILS)?.value; -} - -export const isInternalUser = () => { - const userEmail = getData(LS_KEYS.USER)?.email; - if (!userEmail) return false; - - return ( - userEmail.endsWith('@ente.io') || userEmail === 'kr.anand619@gmail.com' - ); -}; - -export const convertBufferToBase64 = (buffer: Buffer) => { - return buffer.toString('base64'); -}; - -export const convertBase64ToBuffer = (base64: string) => { - return Buffer.from(base64, 'base64'); -};