Skip to content

Commit

Permalink
AG-27707 do not make requests to whoami
Browse files Browse the repository at this point in the history
Merge in EXTENSIONS/vpn-extension from fix/AG-27707 to master

Squashed commit of the following:

commit db1f142
Author: Maxim Topciu <[email protected]>
Date:   Wed Nov 15 13:52:58 2023 +0200

    AG-27707 log errors as text

commit 5cc8e6c
Author: Maxim Topciu <[email protected]>
Date:   Wed Nov 15 12:06:03 2023 +0200

    AG-27707 do not make requests to whoami
  • Loading branch information
maximtop committed Nov 15, 2023
1 parent 43d528b commit f7548c2
Show file tree
Hide file tree
Showing 9 changed files with 26 additions and 112 deletions.
1 change: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ STAGE_ENV=test
VPN_API_URL=<vpn_api_url>
AUTH_API_URL=<auth_api_url>
FORWARDER_DOMAIN=<forwarder_domain>
WHOAMI_URL=<whoami_domain>
1 change: 0 additions & 1 deletion bamboo-specs/build-beta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ Build:
export VPN_API_URL="${bamboo.prodVpnApiUrl}"
export AUTH_API_URL="${bamboo.prodAuthApiUrl}"
export FORWARDER_DOMAIN="${bamboo.prodVpnForwarderDomain}"
export WHOAMI_URL="${bamboo.prodVpnWhoamiUrl}"
export CREDENTIALS_PASSWORD="${bamboo.extensionsPassphrase}"
# build with prod API endpoints edge.zip, chrome.zip, firefox.zip, opera.zip
Expand Down
1 change: 0 additions & 1 deletion bamboo-specs/build-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ Build:
export VPN_API_URL="${bamboo.prodVpnApiUrl}"
export AUTH_API_URL="${bamboo.prodAuthApiUrl}"
export FORWARDER_DOMAIN="${bamboo.prodVpnForwarderDomain}"
export WHOAMI_URL="${bamboo.prodVpnWhoamiUrl}"
yarn release
# build source code for uploading to amo
Expand Down
2 changes: 0 additions & 2 deletions bamboo-specs/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,13 @@ Test:
VPN_API_URL="${bamboo.devVpnApiUrl}" \
AUTH_API_URL="${bamboo.devAuthApiUrl}" \
FORWARDER_DOMAIN="${bamboo.devVpnForwarderDomain}" \
WHOAMI_URL="${bamboo.devVpnWhoamiUrl}" \
yarn dev
# build with prod API endpoints
STAGE_ENV=prod \
VPN_API_URL="${bamboo.prodVpnApiUrl}" \
AUTH_API_URL="${bamboo.prodAuthApiUrl}" \
FORWARDER_DOMAIN="${bamboo.prodVpnForwarderDomain}" \
WHOAMI_URL="${bamboo.prodVpnWhoamiUrl}" \
yarn dev
rm -rf node_modules
artifacts:
Expand Down
96 changes: 26 additions & 70 deletions src/background/api/fallbackApi.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import axios from 'axios';
import _ from 'lodash';

import { browserApi } from '../browserApi';

import {
AUTH_API_URL,
VPN_API_URL,
STAGE_ENV,
WHOAMI_URL,
} from '../config';
import { clearFromWrappingQuotes } from '../../lib/string-utils';
import { log } from '../../lib/logger';
Expand All @@ -16,7 +13,7 @@ import { getErrorMessage } from '../../common/utils/error';
import { stateStorage } from '../stateStorage';
import { authService } from '../authentication/authService';
import { credentialsService } from '../credentials/credentialsService';
import { CountryInfo, FallbackInfo, StorageKey } from '../schema';
import { FallbackInfo, StorageKey } from '../schema';

export const DEFAULT_CACHE_EXPIRE_TIME_MS = 1000 * 60 * 5; // 5 minutes

Expand All @@ -34,20 +31,19 @@ const stageSuffix = STAGE_ENV === 'test' ? '-dev' : '';
const BKP_API_HOSTNAME_PART = `bkp-api${stageSuffix}.adguard-vpn.online`;
const BKP_AUTH_HOSTNAME_PART = `bkp-auth${stageSuffix}.adguard-vpn.online`;

const BKP_KEY = 'bkp';

const EMPTY_BKP_URL = 'none';

const DEFAULT_COUNTRY_INFO = { country: 'none', bkp: true };

const REQUEST_TIMEOUT_MS = 3 * 1000;

const enum Prefix {
NotAuthenticatedUser = 'anon',
FreeUser = 'free',
PremiumUser = 'pro',
const enum UserType {
NotAuthenticated = 'anon',
Free = 'free',
Premium = 'pro',
}

const WHOAMI_VERSION = 'v1';
const APPLICATION_TYPE = 'at-ext';

export class FallbackApi {
/**
* Default fallback info, it may be used if doh returns none result
Expand All @@ -64,7 +60,6 @@ export class FallbackApi {
this.defaultFallbackInfo = {
vpnApiUrl,
authApiUrl,
countryInfo: DEFAULT_COUNTRY_INFO,
expiresInMs: Date.now() - 1,
};
}
Expand All @@ -88,29 +83,15 @@ export class FallbackApi {
}

private async updateFallbackInfo() {
const countryInfo = await this.getCountryInfo();
const localStorageBkp = await this.getLocalStorageBkp();

if (!countryInfo.bkp && !localStorageBkp) {
const fallbackInfo = this.fallbackInfo || this.defaultFallbackInfo;
// if bkp is disabled, we use previous fallback info, only update expiration time
this.fallbackInfo = {
...fallbackInfo,
expiresInMs: Date.now() + DEFAULT_CACHE_EXPIRE_TIME_MS,
};
return;
}

const [bkpVpnApiUrl, bkpAuthApiUrl] = await Promise.all([
this.getBkpVpnApiUrl(countryInfo.country),
this.getBkpAuthApiUrl(countryInfo.country),
this.getBkpVpnApiUrl(),
this.getBkpAuthApiUrl(),
]);

if (bkpVpnApiUrl && bkpAuthApiUrl) {
this.fallbackInfo = {
vpnApiUrl: bkpVpnApiUrl,
authApiUrl: bkpAuthApiUrl,
countryInfo,
expiresInMs: Date.now() + DEFAULT_CACHE_EXPIRE_TIME_MS,
};
}
Expand Down Expand Up @@ -159,38 +140,9 @@ export class FallbackApi {
GOOGLE_DOH_HOSTNAME,
CLOUDFLARE_DOH_HOSTNAME,
ALIDNS_DOH_HOSTNAME,
WHOAMI_URL,
].map((url) => `*${url}`);
};

/**
* Gets bkp flag value from local storage, used for testing purposes
*/
private getLocalStorageBkp = async (): Promise<boolean> => {
const storedBkp = await browserApi.storage.get(BKP_KEY);
let localStorageBkp = Number.parseInt(String(storedBkp), 10);

localStorageBkp = Number.isNaN(localStorageBkp) ? 0 : localStorageBkp;

return !!localStorageBkp;
};

private getCountryInfo = async (): Promise<CountryInfo> => {
try {
const { data: { country, bkp } } = await axios.get(
`https://${WHOAMI_URL}`,
{
timeout: REQUEST_TIMEOUT_MS,
...fetchConfig,
},
);
return { country, bkp };
} catch (e) {
log.error(e);
return DEFAULT_COUNTRY_INFO;
}
};

private getBkpUrlByGoogleDoh = async (name: string): Promise<string> => {
const { data } = await axios.get(`https://${GOOGLE_DOH_URL}`, {
headers: {
Expand Down Expand Up @@ -267,12 +219,14 @@ export class FallbackApi {
* @param fn Function to call
* @throws Error
*/
private logErrors = async (fn: () => Promise<string>): Promise<string> => {
private debugErrors = async (fn: () => Promise<string>): Promise<string> => {
try {
const res = await fn();
return res;
} catch (error) {
log.error(`Error in function ${fn.name}:`, getErrorMessage(error));
// Usually it's either a network error or response is empty. We don't want to spam logs with such errors,
// that's why we only print them for debugging.
log.debug(`Error in function ${fn.name}:`, getErrorMessage(error));
throw error; // Re-throwing the error to ensure that Promise.any receives it
}
};
Expand All @@ -282,9 +236,9 @@ export class FallbackApi {

try {
bkpUrl = await Promise.any([
this.logErrors(() => this.getBkpUrlByGoogleDoh(hostname)),
this.logErrors(() => this.getBkpUrlByCloudFlareDoh(hostname)),
this.logErrors(() => this.getBkpUrlByAliDnsDoh(hostname)),
this.debugErrors(() => this.getBkpUrlByGoogleDoh(hostname)),
this.debugErrors(() => this.getBkpUrlByCloudFlareDoh(hostname)),
this.debugErrors(() => this.getBkpUrlByAliDnsDoh(hostname)),
]);
bkpUrl = clearFromWrappingQuotes(bkpUrl);
} catch (e) {
Expand All @@ -296,34 +250,36 @@ export class FallbackApi {
};

getApiHostnamePrefix = async () => {
const basePrefix = `${WHOAMI_VERSION}.${APPLICATION_TYPE}`;

const isAuthenticated = await authService.isAuthenticated();
if (!isAuthenticated) {
return Prefix.NotAuthenticatedUser;
return `${basePrefix}.${UserType.NotAuthenticated}`;
}

const isPremiumUser = await credentialsService.isPremiumUser();
if (isPremiumUser) {
return Prefix.PremiumUser;
return `${basePrefix}.${UserType.Premium}`;
}

return Prefix.FreeUser;
return `${basePrefix}.${UserType.Free}`;
};

getBkpVpnApiUrl = async (country: string) => {
getBkpVpnApiUrl = async () => {
const prefix = await this.getApiHostnamePrefix();
// we use prefix for api hostname to recognize free, premium and not authenticated users
const hostname = `${country.toLowerCase()}.${prefix}.${BKP_API_HOSTNAME_PART}`;
const hostname = `${prefix}.${BKP_API_HOSTNAME_PART}`;
const bkpApiUrl = await this.getBkpUrl(hostname);
if (bkpApiUrl === EMPTY_BKP_URL) {
return this.defaultFallbackInfo.vpnApiUrl;
}
return bkpApiUrl;
};

getBkpAuthApiUrl = async (country: string) => {
getBkpAuthApiUrl = async () => {
const prefix = await this.getApiHostnamePrefix();
// we use prefix for auth api hostname to recognize free, premium and not authenticated users
const hostname = `${country.toLowerCase()}.${prefix}.${BKP_AUTH_HOSTNAME_PART}`;
const hostname = `${prefix}.${BKP_AUTH_HOSTNAME_PART}`;

const bkpAuthUrl = await this.getBkpUrl(hostname);
if (bkpAuthUrl === EMPTY_BKP_URL) {
Expand Down
1 change: 0 additions & 1 deletion src/background/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export const {
BROWSER,
BUILD_ENV,
STAGE_ENV,
WHOAMI_URL,
COMPARE_PAGE,
VPN_BLOCKED_GET_APP_LINK,
} = CONFIG;
8 changes: 0 additions & 8 deletions src/background/schema/fallbackApi/fallbackInfo.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
import zod from 'zod';

export const countryInfoScheme = zod.object({
country: zod.string(),
bkp: zod.boolean(),
}).strict();

export type CountryInfo = zod.infer<typeof countryInfoScheme>;

export const fallbackInfoScheme = zod.object({
vpnApiUrl: zod.string(),
authApiUrl: zod.string(),
countryInfo: countryInfoScheme,
expiresInMs: zod.number(),
}).strict();

Expand Down
1 change: 0 additions & 1 deletion tasks/appConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ const URLS_MAP: UrlsMap = {
const STAGE_CONF = {
VPN_API_URL: process.env.VPN_API_URL,
AUTH_API_URL: process.env.AUTH_API_URL,
WHOAMI_URL: process.env.WHOAMI_URL,
};

const COMMON = {
Expand Down
27 changes: 0 additions & 27 deletions tests/background/api/fallbackApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
CLOUDFLARE_DOH_URL,
DEFAULT_CACHE_EXPIRE_TIME_MS,
} from '../../../src/background/api/fallbackApi';
import { WHOAMI_URL } from '../../../src/background/config';
import { session } from '../../__mocks__';
// TODO: test mv3 after official switch to mv3
import { stateStorage } from '../../../src/background/stateStorage/mv2';
Expand Down Expand Up @@ -59,7 +58,6 @@ describe('FallbackApi', () => {
expect(vpnApiUrl).toBe(DEFAULT_VPN_API_URL);
expect(authApiUrl).toBe(DEFAULT_AUTH_API_URL);

expect(axios.get).toBeCalledWith(`https://${WHOAMI_URL}`, expect.anything());
expect(axios.get).toBeCalledWith(`https://${GOOGLE_DOH_URL}`, expect.anything());
expect(axios.get).toBeCalledWith(`https://${CLOUDFLARE_DOH_URL}`, expect.anything());
});
Expand Down Expand Up @@ -237,29 +235,4 @@ describe('FallbackApi', () => {
expect(vpnApiUrl).toBe(REMOTE_VPN_API_URL);
expect(authApiUrl).toBe(REMOTE_AUTH_API_URL);
});

it('doesnt sends redundant requests to country info url', async () => {
const DEFAULT_VPN_API_URL = 'vpn_api.com';
const DEFAULT_AUTH_API_URL = 'auth_api.com';

const fallbackApi = new FallbackApi(DEFAULT_VPN_API_URL, DEFAULT_AUTH_API_URL);
jest.spyOn(fallbackApi as any, 'getCountryInfo').mockResolvedValue({ country: 'none', bkp: false });

await fallbackApi.init();

const vpnApiUrl = await fallbackApi.getVpnApiUrl();
const authApiUrl = await fallbackApi.getAuthApiUrl();

expect(vpnApiUrl).toBe(DEFAULT_VPN_API_URL);
expect(authApiUrl).toBe(DEFAULT_AUTH_API_URL);

expect((fallbackApi as any).getCountryInfo).toHaveBeenCalledTimes(1);

jest.advanceTimersByTime(DEFAULT_CACHE_EXPIRE_TIME_MS + 100);

await fallbackApi.getVpnApiUrl();
await fallbackApi.getAuthApiUrl();

expect((fallbackApi as any).getCountryInfo).toHaveBeenCalledTimes(2);
});
});

0 comments on commit f7548c2

Please sign in to comment.