Skip to content
Merged
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
2 changes: 2 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2955,6 +2955,8 @@ ${amount} für ${merchant} – ${date}`,
phoneOrEmail: 'Telefon oder E-Mail',
error: {
invalidFormatEmailLogin: 'Die eingegebene E-Mail-Adresse ist ungültig. Bitte korrigiere das Format und versuche es erneut.',
agentSignInBlocked:
'Agent-Konten können nicht direkt verwendet werden. Um ein Agent-Konto zu nutzen, melden Sie sich mit Ihrem eigenen Konto an und greifen Sie über Copilot darauf zu.',
},
cannotGetAccountDetails: 'Kontodetails konnten nicht abgerufen werden. Bitte melde dich erneut an.',
loginForm: 'Anmeldeformular',
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3022,6 +3022,7 @@ const translations = {
phoneOrEmail: 'Phone or email',
error: {
invalidFormatEmailLogin: 'The email entered is invalid. Please fix the format and try again.',
agentSignInBlocked: "Agent accounts can't be signed into directly. To use an agent, sign in with your own account and access it via Copilot.",
},
cannotGetAccountDetails: "Couldn't retrieve account details. Please try to sign in again.",
loginForm: 'Login form',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2831,6 +2831,8 @@ ${amount} para ${merchant} - ${date}`,
phoneOrEmail: 'Número de teléfono o correo electrónico',
error: {
invalidFormatEmailLogin: 'El correo electrónico introducido no es válido. Corrígelo e inténtalo de nuevo.',
agentSignInBlocked:
'No se puede iniciar sesión directamente en las cuentas de agente. Para usar un agente, inicia sesión con tu propia cuenta y accede a él a través de Copilot.',
},
cannotGetAccountDetails: 'No se pudieron cargar los detalles de tu cuenta. Por favor, intenta iniciar sesión de nuevo.',
loginForm: 'Formulario de inicio de sesión',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2962,6 +2962,8 @@ ${amount} pour ${merchant} - ${date}`,
loginForm: {
phoneOrEmail: 'Téléphone ou e-mail',
error: {
agentSignInBlocked:
'Les comptes d\u2019agent ne permettent pas de se connecter directement. Pour utiliser un agent, connectez-vous avec votre propre compte et accédez-y via Copilot.',
invalidFormatEmailLogin: 'L’adresse e-mail saisie est invalide. Veuillez corriger le format et réessayer.',
},
cannotGetAccountDetails: 'Impossible de récupérer les détails du compte. Veuillez essayer de vous reconnecter.',
Expand Down
1 change: 1 addition & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2950,6 +2950,7 @@ ${amount} per ${merchant} - ${date}`,
loginForm: {
phoneOrEmail: 'Telefono o email',
error: {
agentSignInBlocked: 'Non puoi accedere direttamente agli account agente. Per usare un agente, accedi con il tuo account e raggiungilo tramite Copilot.',
invalidFormatEmailLogin: 'L’email inserita non è valida. Correggi il formato e riprova.',
},
cannotGetAccountDetails: 'Impossibile recuperare i dettagli dell’account. Prova ad accedere di nuovo.',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2923,6 +2923,8 @@ ${date} の ${merchant} への ${amount}`,
phoneOrEmail: '電話番号またはメールアドレス',
error: {
invalidFormatEmailLogin: '入力されたメールアドレスが無効です。形式を修正して、もう一度お試しください。',
agentSignInBlocked:
'エージェントアカウントには直接サインインすることはできません。エージェントを利用するには、ご自身のアカウントでサインインし、Copilot 経由でアクセスしてください。',
},
cannotGetAccountDetails: 'アカウントの詳細を取得できませんでした。もう一度サインインしてください。',
loginForm: 'ログインフォーム',
Expand Down
1 change: 1 addition & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2947,6 +2947,7 @@ ${amount} voor ${merchant} - ${date}`,
phoneOrEmail: 'Telefoon of e-mail',
error: {
invalidFormatEmailLogin: 'Het ingevoerde e-mailadres is ongeldig. Corrigeer de notatie en probeer het opnieuw.',
agentSignInBlocked: 'Je kunt niet rechtstreeks inloggen op agent-accounts. Log in met je eigen account en gebruik de agent via Copilot.',
},
cannotGetAccountDetails: 'Accountgegevens konden niet worden opgehaald. Probeer opnieuw in te loggen.',
loginForm: 'Aanmeldformulier',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2941,6 +2941,7 @@ ${amount} dla ${merchant} - ${date}`,
phoneOrEmail: 'Telefon lub e-mail',
error: {
invalidFormatEmailLogin: 'Wprowadzony adres e-mail jest nieprawidłowy. Popraw jego format i spróbuj ponownie.',
agentSignInBlocked: 'Na konta agenta nie można logować się bezpośrednio. Żeby korzystać z agenta, zaloguj się na własne konto i uzyskaj do niego dostęp przez Copilota.',
},
cannotGetAccountDetails: 'Nie można pobrać szczegółów konta. Spróbuj zalogować się ponownie.',
loginForm: 'Formularz logowania',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2941,6 +2941,7 @@ ${amount} para ${merchant} - ${date}`,
phoneOrEmail: 'Telefone ou e-mail',
error: {
invalidFormatEmailLogin: 'O e-mail inserido é inválido. Corrija o formato e tente novamente.',
agentSignInBlocked: 'Contas de agente não podem ser acessadas diretamente. Para usar um agente, entre com a sua própria conta e acesse-o via Copilot.',
},
cannotGetAccountDetails: 'Não foi possível recuperar os detalhes da conta. Tente entrar novamente.',
loginForm: 'Formulário de login',
Expand Down
1 change: 1 addition & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2866,6 +2866,7 @@ ${amount},商户:${merchant} - 日期:${date}`,
phoneOrEmail: '电话或邮箱',
error: {
invalidFormatEmailLogin: '输入的邮箱无效。请修正格式后重试。',
agentSignInBlocked: '代理帐户无法直接登录。要使用代理,请先登录您自己的帐户,然后通过 Copilot 访问该代理。',
},
cannotGetAccountDetails: '无法获取账户详情。请尝试重新登录。',
loginForm: '登录表单',
Expand Down
2 changes: 1 addition & 1 deletion src/libs/SessionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ function checkIfShouldUseNewPartnerName(partnerUserID?: string): boolean {
return false;
}

const AGENT_EMAIL_REGEX = /^agent_\d+@expensify\.ai$/;
const AGENT_EMAIL_REGEX = /^agent_\d+@expensify\.ai$/i;

function isAgentEmail(email?: string): boolean {
if (!email) {
Expand Down
7 changes: 7 additions & 0 deletions src/pages/signin/LoginForm/BaseLoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {getLatestErrorMessage} from '@libs/ErrorUtils';
import isInputAutoFilled from '@libs/isInputAutoFilled';
import {appendCountryCode, getPhoneNumberWithoutSpecialChars} from '@libs/LoginUtils';
import {parsePhoneNumber} from '@libs/PhoneNumber';
import {isAgentEmail} from '@libs/SessionUtils';
import StringUtils from '@libs/StringUtils';
import {isNumericWithSpecialChars, isValidEmailWithTLD} from '@libs/ValidationUtils';
import Visibility from '@libs/Visibility';
Expand Down Expand Up @@ -140,6 +141,12 @@ function BaseLoginForm({submitBehavior = 'submit', isVisible, ref}: BaseLoginFor

const loginTrim = StringUtils.removeInvisibleCharacters(login.trim());

if (isAgentEmail(loginTrim)) {
setFormError('loginForm.error.agentSignInBlocked');
isLoading.current = false;
return;
}

const phoneLogin = appendCountryCode(getPhoneNumberWithoutSpecialChars(loginTrim), countryCode);
const parsedPhoneNumber = parsePhoneNumber(phoneLogin);

Expand Down
135 changes: 135 additions & 0 deletions tests/ui/BaseLoginFormTest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import type * as ReactNavigationNative from '@react-navigation/native';
import {fireEvent, render, screen, waitFor} from '@testing-library/react-native';
import React from 'react';
import Onyx from 'react-native-onyx';
import {LoginProvider} from '@pages/signin/SignInLoginContext';
import {beginSignIn} from '@userActions/Session';
import ONYXKEYS from '@src/ONYXKEYS';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';

jest.mock('@react-navigation/native', () => {
const actualNav = jest.requireActual<typeof ReactNavigationNative>('@react-navigation/native');
return {
...actualNav,
useIsFocused: () => true,
};
});

const AGENT_ERROR = "Agent accounts can't be signed into directly. To use an agent, sign in with your own account and access it via Copilot.";
const INVALID_EMAIL_ERROR = 'The email entered is invalid. Please fix the format and try again.';

jest.mock('@hooks/useLocalize', () =>
jest.fn(() => ({
translate: jest.fn((key: string) => {
switch (key) {
case 'loginForm.error.agentSignInBlocked':
return AGENT_ERROR;
case 'loginForm.error.invalidFormatEmailLogin':
return INVALID_EMAIL_ERROR;
case 'loginForm.phoneOrEmail':
return 'Phone or email';
case 'loginForm.loginForm':
return 'Login form';
case 'common.continue':
return 'Continue';
case 'common.signInWith':
return 'Sign in with';
case 'common.pleaseEnterEmailOrPhoneNumber':
return 'Please enter an email or phone number';
default:
return key;
}
}),
numberFormat: jest.fn(),
})),
);

jest.mock('@hooks/useResponsiveLayout', () =>
jest.fn(() => ({
shouldUseNarrowLayout: false,
isInNarrowPaneModal: false,
})),
);

jest.mock('@userActions/Session', () => ({
beginSignIn: jest.fn(),
clearAccountMessages: jest.fn(),
clearSignInData: jest.fn(),
}));

jest.mock('@userActions/CloseAccount', () => ({
setDefaultData: jest.fn(),
}));

// Use require to get the default export after all jest.mock calls are hoisted
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const BaseLoginForm = require('@pages/signin/LoginForm/BaseLoginForm').default;

const mockBeginSignIn = beginSignIn as jest.MockedFunction<typeof beginSignIn>;

function renderForm() {
return render(
<LoginProvider>
<BaseLoginForm isVisible />
</LoginProvider>,
);
}

describe('BaseLoginForm', () => {
beforeAll(() => {
Onyx.init({keys: ONYXKEYS});
});

beforeEach(async () => {
jest.clearAllMocks();
await Onyx.set(ONYXKEYS.ACCOUNT, {
isLoading: false,
errors: null,
});
await waitForBatchedUpdates();
});

it('shows agent sign-in blocked error when an agent email is entered', async () => {
renderForm();

const input = screen.getByTestId('username');
fireEvent.changeText(input, 'agent_123@expensify.ai');

const continueButton = screen.getByText('Continue');
fireEvent.press(continueButton);

await waitFor(() => {
expect(screen.getByText(AGENT_ERROR)).toBeTruthy();
});
expect(mockBeginSignIn).not.toHaveBeenCalled();
});

it('blocks agent email regardless of case', async () => {
renderForm();

const input = screen.getByTestId('username');
fireEvent.changeText(input, 'AGENT_123@EXPENSIFY.AI');

const continueButton = screen.getByText('Continue');
fireEvent.press(continueButton);

await waitFor(() => {
expect(screen.getByText(AGENT_ERROR)).toBeTruthy();
});
expect(mockBeginSignIn).not.toHaveBeenCalled();
});

it('proceeds with sign-in for a normal email', async () => {
renderForm();

const input = screen.getByTestId('username');
fireEvent.changeText(input, 'user@expensify.com');

const continueButton = screen.getByText('Continue');
fireEvent.press(continueButton);

await waitFor(() => {
expect(mockBeginSignIn).toHaveBeenCalledWith('user@expensify.com');
});
});
});
Loading