fix: Show iframe auth when login with token fails (#36919)

pull/36923/head^2
Yash Rajpal 4 months ago committed by GitHub
parent c5ee569cc3
commit feba290fc4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      .changeset/rich-parrots-lie.md
  2. 2
      apps/meteor/client/hooks/iframe/useIframe.ts
  3. 4
      apps/meteor/client/providers/AuthenticationProvider/AuthenticationProvider.tsx
  4. 37
      apps/meteor/tests/e2e/fixtures/files/iframe-login.html
  5. 151
      apps/meteor/tests/e2e/iframe-authentication.spec.ts
  6. 18
      apps/meteor/tests/e2e/page-objects/auth.ts
  7. 2
      packages/ui-contexts/src/AuthenticationContext.ts

@ -0,0 +1,6 @@
---
'@rocket.chat/ui-contexts': patch
'@rocket.chat/meteor': patch
---
Show iframe authentication page, when login through iframe authentication API token fails

@ -22,7 +22,7 @@ export const useIframe = () => {
};
}
if ('loginToken' in tokenData) {
tokenLogin(tokenData.loginToken);
tokenLogin(tokenData.loginToken, callback);
}
if ('token' in tokenData) {
iframeLogin(tokenData.token, callback);

@ -41,10 +41,12 @@ const AuthenticationProvider = ({ children }: AuthenticationProviderProps): Reac
const contextValue = useMemo(
(): ContextType<typeof AuthenticationContext> => ({
isLoggingIn,
loginWithToken: (token: string): Promise<void> =>
loginWithToken: (token: string, callback): Promise<void> =>
new Promise((resolve, reject) =>
Meteor.loginWithToken(token, (err) => {
if (err) {
console.error(err);
callback?.(err);
return reject(err);
}
resolve(undefined);

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Iframe Login</title>
</head>
<script>
function login() {
console.log('logging in');
window.parent.postMessage({
event: 'login-with-token',
loginToken: 'REPLACE_WITH_TOKEN',
}, '*');
}
window.addEventListener('message', (event) => {
if(event.data.event === 'login-error') {
document.getElementById('login-error').innerText = "Login failed";
}
});
</script>
<body>
<h1>Iframe Authentication Login Form</h1>
<form id="login-form">
<label for="username">Username:</label><br/>
<input type="text" id="username" name="username" placeholder="Enter username" /><br/><br/>
<label for="password">Password:</label><br/>
<input type="password" id="password" name="password" placeholder="Enter password" /><br/><br/>
<button id="submit" type="button"
onclick="login()">Login</button>
</form>
<div id="login-error"></div>
</body>
</html>

@ -0,0 +1,151 @@
import fs from 'fs';
import path from 'path';
import { Users } from './fixtures/userStates';
import { Utils, Registration } from './page-objects';
import { test, expect } from './utils/test';
const IFRAME_URL = 'http://iframe.rocket.chat';
const API_URL = 'http://auth.rocket.chat/api/login';
test.describe('iframe-authentication', () => {
let poRegistration: Registration;
let poUtils: Utils;
test.beforeAll(async ({ api }) => {
await api.post('/settings/Accounts_iframe_enabled', { value: true });
await api.post('/settings/Accounts_iframe_url', { value: IFRAME_URL });
await api.post('/settings/Accounts_Iframe_api_url', { value: API_URL });
await api.post('/settings/Accounts_Iframe_api_method', { value: 'POST' });
});
test.afterAll(async ({ api }) => {
await api.post('/settings/Accounts_iframe_enabled', { value: false });
await api.post('/settings/Accounts_iframe_url', { value: '' });
await api.post('/settings/Accounts_Iframe_api_url', { value: '' });
await api.post('/settings/Accounts_Iframe_api_method', { value: '' });
});
test.beforeEach(async ({ page }) => {
poRegistration = new Registration(page);
poUtils = new Utils(page);
await page.route(API_URL, async (route) => {
await route.fulfill({
status: 200,
});
});
const htmlContent = fs
.readFileSync(path.resolve(__dirname, 'fixtures/files/iframe-login.html'), 'utf-8')
.replace('REPLACE_WITH_TOKEN', Users.user1.data.loginToken);
await page.route(IFRAME_URL, async (route) => {
await route.fulfill({
status: 200,
contentType: 'text/html',
body: htmlContent,
});
});
});
test('should render iframe instead of login page', async ({ page }) => {
await page.goto('/home');
await expect(poRegistration.loginIframeForm).toBeVisible();
});
test('should render iframe login page if API returns error', async ({ page }) => {
await page.route(API_URL, async (route) => {
await route.fulfill({
status: 500,
});
});
await page.goto('/home');
await expect(poRegistration.loginIframeForm).toBeVisible();
});
test('should login with token when API returns valid token', async ({ page }) => {
await page.route(API_URL, async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ loginToken: Users.user1.data.loginToken }),
});
});
await page.goto('/home');
await expect(poUtils.mainContent).toBeVisible();
});
test('should show login page when API returns invalid token', async ({ page }) => {
await page.route(API_URL, async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ loginToken: 'invalid-token' }),
});
});
await page.goto('/home');
await expect(poRegistration.loginIframeForm).toBeVisible();
});
test('should login through iframe', async ({ page }) => {
await page.goto('/home');
await expect(poRegistration.loginIframeForm).toBeVisible();
await poRegistration.loginIframeSubmitButton.click();
await expect(poUtils.mainContent).toBeVisible();
});
test('should return error to iframe when login fails', async ({ page }) => {
const htmlContent = fs.readFileSync(path.resolve(__dirname, 'fixtures/files/iframe-login.html'), 'utf-8');
await page.route(IFRAME_URL, async (route) => {
await route.fulfill({
status: 200,
contentType: 'text/html',
body: htmlContent,
});
});
await page.goto('/home');
await expect(poRegistration.loginIframeForm).toBeVisible();
await poRegistration.loginIframeSubmitButton.click();
await expect(poRegistration.loginIframeError).toBeVisible();
});
test.describe('incomplete settings', () => {
test.beforeAll(async ({ api }) => {
await api.post('/settings/Accounts_Iframe_api_url', { value: '' });
});
test.afterAll(async ({ api }) => {
await api.post('/settings/Accounts_Iframe_api_url', { value: API_URL });
});
test('should render default login page, if settings are incomplete', async ({ page }) => {
const htmlContent = fs.readFileSync(path.resolve(__dirname, 'fixtures/files/iframe-login.html'), 'utf-8');
await page.route(IFRAME_URL, async (route) => {
await route.fulfill({
status: 200,
contentType: 'text/html',
body: htmlContent,
});
});
await page.goto('/home');
await expect(poRegistration.btnLogin).toBeVisible();
await expect(poRegistration.loginIframeForm).not.toBeVisible();
});
});
});

@ -1,4 +1,4 @@
import type { Locator, Page } from '@playwright/test';
import type { FrameLocator, Locator, Page } from '@playwright/test';
export class Registration {
private readonly page: Page;
@ -78,4 +78,20 @@ export class Registration {
get registrationDisabledCallout(): Locator {
return this.page.locator('role=status >> text=/New user registration is currently disabled/');
}
get loginIframe(): FrameLocator {
return this.page.frameLocator('iframe[title="Login"]');
}
get loginIframeForm(): Locator {
return this.loginIframe.locator('#login-form');
}
get loginIframeSubmitButton(): Locator {
return this.loginIframe.locator('#submit');
}
get loginIframeError(): Locator {
return this.loginIframe.locator('#login-error', { hasText: 'Login failed' });
}
}

@ -9,7 +9,7 @@ export type LoginService = LoginServiceConfiguration & {
export type AuthenticationContextValue = {
readonly isLoggingIn: boolean;
loginWithPassword: (user: string | { username: string } | { email: string } | { id: string }, password: string) => Promise<void>;
loginWithToken: (user: string) => Promise<void>;
loginWithToken: (user: string, callback?: (error: Error | null | undefined) => void) => Promise<void>;
loginWithService<T extends LoginServiceConfiguration>(service: T): () => Promise<true>;
loginWithIframe: (token: string, callback?: (error: Error | null | undefined) => void) => Promise<void>;
loginWithTokenRoute: (token: string, callback?: (error: Error | null | undefined) => void) => Promise<void>;

Loading…
Cancel
Save