Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ jobs:
ENV_VARS=$(jq -n \
--arg byoa "${{ secrets.E2E_BYOA_AUTH_SECRET }}" \
--arg email "${{ secrets.E2E_MOCK_OAUTH_EMAIL }}" \
'[{"mapped_to":"DISABLE_NOTIFICATION_PROMPT","value":"true","is_expand":true},{"mapped_to":"E2E_MOCK_OAUTH","value":"true","is_expand":true},{"mapped_to":"E2E_BYOA_AUTH_SECRET","value":$byoa,"is_expand":true},{"mapped_to":"E2E_MOCK_OAUTH_EMAIL","value":$email,"is_expand":true}]')
'[{"mapped_to":"DISABLE_NOTIFICATION_PROMPT","value":"true","is_expand":true},{"mapped_to":"E2E_MOCK_OAUTH","value":"true","is_expand":true},{"mapped_to":"E2E_BYOA_AUTH_SECRET","value":$byoa,"is_expand":true},{"mapped_to":"E2E_MOCK_OAUTH_EMAIL","value":$email,"is_expand":true},{"mapped_to":"OAUTH_BUILD_TYPE","value":"main_uat","is_expand":true}]')
CUSTOM_ID="MetaMask-Android-Without-SRP-${{ github.run_id }}"

# Trigger Android workflow
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-ios-upload-to-browserstack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ jobs:
ENV_VARS=$(jq -n \
--arg byoa "${{ secrets.E2E_BYOA_AUTH_SECRET }}" \
--arg email "${{ secrets.E2E_MOCK_OAUTH_EMAIL }}" \
'[{"mapped_to":"DISABLE_NOTIFICATION_PROMPT","value":"true","is_expand":true},{"mapped_to":"E2E_MOCK_OAUTH","value":"true","is_expand":true},{"mapped_to":"E2E_BYOA_AUTH_SECRET","value":$byoa,"is_expand":true},{"mapped_to":"E2E_MOCK_OAUTH_EMAIL","value":$email,"is_expand":true}]')
'[{"mapped_to":"DISABLE_NOTIFICATION_PROMPT","value":"true","is_expand":true},{"mapped_to":"E2E_MOCK_OAUTH","value":"true","is_expand":true},{"mapped_to":"E2E_BYOA_AUTH_SECRET","value":$byoa,"is_expand":true},{"mapped_to":"E2E_MOCK_OAUTH_EMAIL","value":$email,"is_expand":true},{"mapped_to":"OAUTH_BUILD_TYPE","value":"main_uat","is_expand":true}]')
CUSTOM_ID="MetaMask-iOS-Without-SRP-${{ github.run_id }}"

# Trigger iOS workflow
Expand Down
9 changes: 7 additions & 2 deletions android/app/src/main/res/xml/react_native_config.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="${isDebug}">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">sslip.io</domain>
<domain includeSubdomains="true">sslip.io</domain>
<domain includeSubdomains="false">localhost</domain>
<domain includeSubdomains="false">10.0.2.2</domain>
<domain includeSubdomains="false">10.0.3.2</domain>
</domain-config>
<base-config cleartextTrafficPermitted="${isDebug}" />
</network-security-config>
2 changes: 1 addition & 1 deletion app/core/OAuthService/OAuthLoginHandlers/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface OAUTH_CONFIG_TYPE {
IOS_APPLE_AUTH_CONNECTION_ID: string;
}

enum BUILD_TYPE {
export enum BUILD_TYPE {
development = 'development',
main_prod = 'main_prod',
main_uat = 'main_uat',
Expand Down
43 changes: 3 additions & 40 deletions app/core/OAuthService/OAuthLoginHandlers/constants.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ACTIONS, PREFIXES, PROTOCOLS } from '../../../constants/deeplinks';
import Device from '../../../util/device';
import ReduxService from '../../redux';
import { isQa } from '../../../util/test/utils';
import AppConstants from '../../AppConstants';
import { AuthConnection } from '../OAuthInterface';
import { OAUTH_CONFIG } from './config';
import { resolveOAuthConfigKey } from './oauthBuildType';
import {
DEFAULT_LEGACY_IOS_GOOGLE_CONFIG_ENABLED,
selectLegacyIosGoogleConfigEnabled,
Expand All @@ -13,45 +13,8 @@ import {
export const SEEDLESS_ONBOARDING_ENABLED =
process.env.SEEDLESS_ONBOARDING_ENABLED === 'true';

/**
* Mapping of old Build Type to new BuildType formatting for oauth config
* Main -> main_prod
* QA -> main_uat
* Debug -> main_dev
* flask -> flask_prod
* flask QA -> flask_uat
* flask Debug -> flask_dev
*
* new build types
* main_beta -> main_prod
* main_rc -> main_prod
*
* @param buildType - The build type to map
* @param isDev - Whether the build is a development build
* @returns The mapped build type
*/
const buildTypeMapping = (buildType: string, isDev: boolean) => {
// use development config for now
if (process.env.DEV_OAUTH_CONFIG === 'true' && isDev) {
return 'development';
}

switch (buildType) {
case 'qa':
return 'main_uat';
case 'main':
return isQa ? 'main_uat' : isDev ? 'main_dev' : 'main_prod';
case 'flask':
return isQa ? 'flask_uat' : isDev ? 'flask_dev' : 'flask_prod';
default:
return 'development';
}
};

const BuildType = buildTypeMapping(
AppConstants.METAMASK_BUILD_TYPE || 'main',
AppConstants.IS_DEV,
);
/** OAuth config key: env override or build-type mapping */
const BuildType = resolveOAuthConfigKey();
const CURRENT_OAUTH_CONFIG = OAUTH_CONFIG[BuildType];

export const web3AuthNetwork = CURRENT_OAUTH_CONFIG.WEB3AUTH_NETWORK;
Expand Down
83 changes: 83 additions & 0 deletions app/core/OAuthService/OAuthLoginHandlers/oauthBuildType.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { BUILD_TYPE, OAUTH_CONFIG } from './config';
import { buildTypeMapping } from './oauthBuildType';

describe('buildTypeMapping', () => {
const originalDevOAuth = process.env.DEV_OAUTH_CONFIG;

afterEach(() => {
if (originalDevOAuth === undefined) {
delete process.env.DEV_OAUTH_CONFIG;
} else {
process.env.DEV_OAUTH_CONFIG = originalDevOAuth;
}
});

it('returns development when DEV_OAUTH_CONFIG is true and isDev', () => {
process.env.DEV_OAUTH_CONFIG = 'true';
expect(buildTypeMapping('main', true, false)).toBe(BUILD_TYPE.development);
});

it('maps qa to main_uat', () => {
expect(buildTypeMapping('qa', false, false)).toBe(BUILD_TYPE.main_uat);
});

it('maps main with QA channel to main_uat', () => {
expect(buildTypeMapping('main', false, true)).toBe(BUILD_TYPE.main_uat);
});

it('maps main without QA to main_dev when isDev', () => {
expect(buildTypeMapping('main', true, false)).toBe(BUILD_TYPE.main_dev);
});

it('maps main without QA to main_prod when not isDev', () => {
expect(buildTypeMapping('main', false, false)).toBe(BUILD_TYPE.main_prod);
});

it('maps flask with QA channel to flask_uat', () => {
expect(buildTypeMapping('flask', false, true)).toBe(BUILD_TYPE.flask_uat);
});

it('maps flask without QA to flask_dev when isDev', () => {
expect(buildTypeMapping('flask', true, false)).toBe(BUILD_TYPE.flask_dev);
});

it('maps flask without QA to flask_prod when not isDev', () => {
expect(buildTypeMapping('flask', false, false)).toBe(BUILD_TYPE.flask_prod);
});

it('returns development for unknown build type', () => {
expect(buildTypeMapping('unknown', false, false)).toBe(
BUILD_TYPE.development,
);
});
});

describe('resolveOAuthConfigKey', () => {
const originalOauthBuildType = process.env.OAUTH_BUILD_TYPE;

afterEach(() => {
if (originalOauthBuildType === undefined) {
delete process.env.OAUTH_BUILD_TYPE;
} else {
process.env.OAUTH_BUILD_TYPE = originalOauthBuildType;
}
});

it('returns OAUTH_BUILD_TYPE when set to a valid config key', () => {
jest.resetModules();
process.env.OAUTH_BUILD_TYPE = BUILD_TYPE.main_prod;
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires -- Jest reload after resetModules; dynamic import needs experimental-vm-modules
const { resolveOAuthConfigKey } = require('./oauthBuildType');
expect(resolveOAuthConfigKey()).toBe(BUILD_TYPE.main_prod);
});

it('ignores OAUTH_BUILD_TYPE when not a key of OAUTH_CONFIG', () => {
jest.resetModules();
process.env.OAUTH_BUILD_TYPE = 'not_a_real_key';
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
const { resolveOAuthConfigKey } = require('./oauthBuildType');
const key = resolveOAuthConfigKey();
expect(key).not.toBe('not_a_real_key');
expect(key in OAUTH_CONFIG).toBe(true);
});
});
56 changes: 56 additions & 0 deletions app/core/OAuthService/OAuthLoginHandlers/oauthBuildType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import AppConstants from '../../AppConstants';
import { isQa } from '../../../util/test/utils';
import { BUILD_TYPE, OAUTH_CONFIG } from './config';

/**
* Maps MetaMask build type + dev/QA flags to OAuth config keys.
* @param buildType - e.g. main, qa, flask
* @param isDev - development build
* @param isQaChannel - QA / e2e / exp channel
*/
export function buildTypeMapping(
buildType: string,
isDev: boolean,
isQaChannel: boolean,
): BUILD_TYPE {
if (process.env.DEV_OAUTH_CONFIG === 'true' && isDev) {
return BUILD_TYPE.development;
}

switch (buildType) {
case 'qa':
return BUILD_TYPE.main_uat;
case 'main': {
if (isQaChannel) return BUILD_TYPE.main_uat;
if (isDev) return BUILD_TYPE.main_dev;
return BUILD_TYPE.main_prod;
}
case 'flask': {
if (isQaChannel) return BUILD_TYPE.flask_uat;
if (isDev) return BUILD_TYPE.flask_dev;
return BUILD_TYPE.flask_prod;
}
default:
return BUILD_TYPE.development;
}
}

/**
* Resolves which {@link OAUTH_CONFIG} entry applies (env override or build mapping).
*/
export function resolveOAuthConfigKey(): keyof typeof OAUTH_CONFIG {
const fromEnv = process.env.OAUTH_BUILD_TYPE;
if (
typeof fromEnv === 'string' &&
fromEnv.length > 0 &&
fromEnv in OAUTH_CONFIG
) {
return fromEnv as keyof typeof OAUTH_CONFIG;
}

return buildTypeMapping(
AppConstants.METAMASK_BUILD_TYPE || 'main',
AppConstants.IS_DEV,
isQa,
);
}
43 changes: 25 additions & 18 deletions app/core/OAuthService/OAuthService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { OAuthError, OAuthErrorType } from './error';
import { Web3AuthNetwork } from '@metamask/seedless-onboarding-controller';
import { TraceName, TraceOperation } from '../../util/trace';
import { signOut as acmSignOut } from '@metamask/react-native-acm';
import { SET_SEEDLESS_ONBOARDING } from '../../actions/onboarding';
const MOCK_GOOGLE_OAUTH_CLIENT_ID = 'abc.apps.googleusercontent.com';

const MOCK_JWT_TOKEN =
'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InN3bmFtOTA5QGdtYWlsLmNvbSIsInN1YiI6InN3bmFtOTA5QGdtYWlsLmNvbSIsImlzcyI6Im1ldGFtYXNrIiwiYXVkIjoibWV0YW1hc2siLCJpYXQiOjE3NDUyMDc1NjYsImVhdCI6MTc0NTIwNzg2NiwiZXhwIjoxNzQ1MjA3ODY2fQ.nXRRLB7fglRll7tMzFFCU0u7Pu6EddqEYf_DMyRgOENQ6tJ8OLtVknNf83_5a67kl_YKHFO-0PEjvJviPID6xg';
Expand Down Expand Up @@ -123,7 +123,7 @@ const mockGetAuthTokens = jest.fn().mockImplementation(() => ({
const mockCreateLoginHandler = jest.fn().mockImplementation(() => ({
authConnection: AuthConnection.Google,
options: {
clientId: 'e2e-mock-google-client-id',
clientId: MOCK_GOOGLE_OAUTH_CLIENT_ID,
authServerUrl: 'https://auth.example.com',
web3AuthNetwork: 'sapphire_mainnet',
},
Expand Down Expand Up @@ -221,7 +221,7 @@ describe('OAuth login service', () => {
}),
);
expect(mockDispatch).toHaveBeenCalledWith({
type: SET_SEEDLESS_ONBOARDING,
type: 'SET_SEEDLESS_ONBOARDING',
clientId: 'clientId',
authConnection: AuthConnection.Google,
});
Expand Down Expand Up @@ -675,7 +675,7 @@ describe('OAuth login service', () => {
delete process.env.E2E_MOCK_OAUTH_EMAIL;
});

it('exchanges QA mock tokens and returns mock success without seedless authenticate', async () => {
it('exchanges QA mock tokens, calls seedless authenticate, and dispatches seedless onboarding', async () => {
const loginHandler = mockCreateLoginHandler();

const result = await OAuthLoginService.handleOAuthLogin(
Expand All @@ -700,15 +700,18 @@ describe('OAuth login service', () => {
const body = JSON.parse(
(fetchSpy.mock.calls[0][1] as RequestInit).body as string,
);
expect(body).toMatchObject({
email_id: 'newuser+e2e@web3auth.io',
client_id: 'e2e-mock-google-client-id',
login_provider: AuthConnection.Google,
access_type: 'offline',
});
expect(mockAuthenticate).not.toHaveBeenCalled();
expect(body.client_id).toBe(MOCK_GOOGLE_OAUTH_CLIENT_ID);
expect(body.login_provider).toBe(AuthConnection.Google);
expect(body.access_type).toBe('offline');
expect(body.email_id).toMatch(/^[a-f0-9]+\d+\+e2e@web3auth\.io$/);
expect(mockAuthenticate).toHaveBeenCalledTimes(1);
Comment thread
grvgoel81 marked this conversation as resolved.
expect(mockLoginHandlerResponse).not.toHaveBeenCalled();
expect(mockGetAuthTokens).not.toHaveBeenCalled();
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_SEEDLESS_ONBOARDING',
clientId: MOCK_GOOGLE_OAUTH_CLIENT_ID,
authConnection: AuthConnection.Google,
});
});

it('uses E2E_MOCK_OAUTH_EMAIL for email_id when set', async () => {
Expand Down Expand Up @@ -739,7 +742,7 @@ describe('OAuth login service', () => {
expect(mockAuthenticate).not.toHaveBeenCalled();
});

it('succeeds when QA mock response omits refresh_token', async () => {
it('rejects when QA mock response omits refresh_token (seedless authenticate requires it)', async () => {
fetchSpy.mockResolvedValueOnce({
ok: true,
status: 200,
Expand All @@ -757,23 +760,27 @@ describe('OAuth login service', () => {
} as Response);
const loginHandler = mockCreateLoginHandler();

const result = await OAuthLoginService.handleOAuthLogin(
loginHandler,
false,
await expectOAuthError(
OAuthLoginService.handleOAuthLogin(loginHandler, false),
OAuthErrorType.LoginError,
);

expect(result.type).toBe('success');
expect(mockAuthenticate).not.toHaveBeenCalled();
});

it('does not call provider login, getAuthTokens, or seedless authenticate', async () => {
it('does not call provider login or getAuthTokens but does call seedless authenticate', async () => {
const loginHandler = mockCreateLoginHandler();

await OAuthLoginService.handleOAuthLogin(loginHandler, false);

expect(mockLoginHandlerResponse).not.toHaveBeenCalled();
expect(mockGetAuthTokens).not.toHaveBeenCalled();
expect(mockAuthenticate).not.toHaveBeenCalled();
expect(mockAuthenticate).toHaveBeenCalledTimes(1);
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_SEEDLESS_ONBOARDING',
clientId: MOCK_GOOGLE_OAUTH_CLIENT_ID,
authConnection: AuthConnection.Google,
});
});
});
});
Expand Down
12 changes: 8 additions & 4 deletions app/core/OAuthService/OAuthService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,6 @@ export class OAuthService {
throw new Error('No user id found');
}

if (isE2EMockOAuth()) {
return QAMockOAuthService.mockSeedlessHandleResult(accountName);
}

Comment thread
grvgoel81 marked this conversation as resolved.
const authConnectionConfig = getAuthConnectionIdFromClientId({
clientId,
authConnection,
Expand Down Expand Up @@ -214,6 +210,14 @@ export class OAuthService {
);

this.#dispatchPostLogin(result);

ReduxService.store.dispatch(
setSeedlessOnboarding({
clientId: loginHandler.options.clientId,
authConnection: loginHandler.authConnection,
}),
);

return result;
};

Expand Down
Loading
Loading