Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug/1258 if you log out you can click the back button and see the previous logged in page #1261

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
35 changes: 21 additions & 14 deletions frontend/src/hooks.server.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { loadI18n, pickBestLocale } from '$lib/i18n';
import { AUTH_COOKIE_NAME, getUser, isAuthn } from '$lib/user'
import { apiVersion } from '$lib/util/version';
import { redirect, type Handle, type HandleFetch, type HandleServerError, type RequestEvent, type ResolveOptions } from '@sveltejs/kit'
import { ensureErrorIsTraced, traceRequest, traceFetch } from '$lib/otel/otel.server'
import { env } from '$env/dynamic/private';
import { getErrorMessage, validateFetchResponse } from './hooks.shared';
import { setViewMode } from './routes/(authenticated)/shared';
import {loadI18n, pickBestLocale} from '$lib/i18n';
import {AUTH_COOKIE_NAME, getUser, isAuthn} from '$lib/user';
import {apiVersion} from '$lib/util/version';
import {redirect, type Handle, type HandleFetch, type HandleServerError, type RequestEvent, type ResolveOptions} from '@sveltejs/kit';
import {ensureErrorIsTraced, traceRequest, traceFetch} from '$lib/otel/otel.server';
import {env} from '$env/dynamic/private';
import {getErrorMessage, validateFetchResponse} from './hooks.shared';
import {setViewMode} from './routes/(authenticated)/shared';
import * as setCookieParser from 'set-cookie-parser';
import { AUTHENTICATED_ROOT, UNAUTHENTICATED_ROOT } from './routes';
import {AUTHENTICATED_ROOT, UNAUTHENTICATED_ROOT} from './routes';

const PUBLIC_ROUTE_ROOTS = [
UNAUTHENTICATED_ROOT,
Expand All @@ -29,7 +29,7 @@ async function initI18n(event: RequestEvent): Promise<void> {
}

// eslint-disable-next-line func-style
export const handle: Handle = ({ event, resolve }) => {
export const handle: Handle = ({event, resolve}) => {
console.log(`HTTP request: ${event.request.method} ${event.request.url}`);
event.locals.getUser = () => getUser(event.cookies);
return traceRequest(event, async () => {
Expand All @@ -39,22 +39,29 @@ export const handle: Handle = ({ event, resolve }) => {
filterSerializedResponseHeaders: () => true,
}

const { cookies, route: { id: routeId } } = event;
const {cookies, route: {id: routeId}} = event;
if (!routeId) {
redirect(307, '/');
} else if (PUBLIC_ROUTE_ROOTS.includes(getRoot(routeId))) {
return resolve(event, options);
const response = await resolve(event, options);
if (routeId.endsWith('/logout')) {
response.headers.set('Clear-Site-Data', '"cache"');
}
return response;
} else if (!isAuthn(cookies)) {
const relativePath = event.url.href.substring(event.url.origin.length);
redirect(307, `/login?ReturnUrl=${encodeURIComponent(relativePath)}`);
if (relativePath !== '/')
myieye marked this conversation as resolved.
Show resolved Hide resolved
redirect(307, `/login?ReturnUrl=${encodeURIComponent(relativePath)}`);
else
redirect(307, '/login');
}
//when at home
if (routeId == `/${AUTHENTICATED_ROOT}`) {
setViewMode(event.params, cookies);
}

return resolve(event, options);
})
});
};

// eslint-disable-next-line func-style
Expand Down
1 change: 1 addition & 0 deletions frontend/src/routes/(authenticated)/admin/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
$: tab = $queryParamValues.tab;

const loadingUsers = derived(navigating, (nav) => {
if (!nav?.to?.route.id?.endsWith('/admin')) return false;
const fromUrl = nav?.from?.url;
return fromUrl && userFilterKeys.some((key) =>
(fromUrl.searchParams.get(key) ?? defaultQueryParamValues[key])?.toString() !== $queryParamValues[key]);
Expand Down
9 changes: 0 additions & 9 deletions frontend/src/routes/(authenticated)/logout/+server.ts

This file was deleted.

9 changes: 9 additions & 0 deletions frontend/src/routes/(unauthenticated)/logout/+server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type {RequestEvent} from './$types';
import {logout} from '$lib/user';
import {redirect} from '@sveltejs/kit';

export function GET({cookies}: RequestEvent): void {
logout(cookies);

redirect(303, '/login');
}
19 changes: 19 additions & 0 deletions frontend/tests/components/authenticatedDrawer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {type Locator, type Page} from '@playwright/test';
import {BaseComponent} from './baseComponent';
import {LoginPage} from '../pages/loginPage';

export class AuthenticatedDrawer extends BaseComponent {

get logoutLink(): Locator {
return this.componentLocator.getByRole('link', { name: 'Log out' });
}

constructor(page: Page) {
super(page, page.locator(`.drawer .drawer-side:has-text("Log out")`));
}

async logout(): Promise<LoginPage> {
await this.logoutLink.click();
return new LoginPage(this.page).waitFor();
}
}
4 changes: 2 additions & 2 deletions frontend/tests/emailWorkflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const userIdsToDelete: string[] = [];

test.afterEach(async ({ page }) => {
if (userIdsToDelete.length > 0) {
await loginAs(page.request, 'admin', defaultPassword);
await loginAs(page.request, 'admin');
for (const userId of userIdsToDelete) {
await deleteUser(page.request, userId);
}
Expand Down Expand Up @@ -110,7 +110,7 @@ test('forgot password', async ({ page, tempUser }) => {
test('register via new-user invitation email', async ({ page, mailboxFactory }) => {
test.setTimeout(TEST_TIMEOUT_2X);

await loginAs(page.request, 'admin', defaultPassword);
await loginAs(page.request, 'admin');
const adminPage = await new AdminDashboardPage(page).goto();
const projectPage = await adminPage.openProject('Sena 3', 'sena-3');

Expand Down
10 changes: 5 additions & 5 deletions frontend/tests/errorHandling.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ test('catch fetch 500 and error dialog', async ({ page }) => {

//we want to verify that once we get the 500 in GQL we can still navigate to another page
test('client-side gql 500 does not break the application', async ({ page }) => {
await loginAs(page.request, 'admin', testEnv.defaultPassword);
await loginAs(page.request, 'admin');
await new SandboxPage(page).goto();
// Create promise first before triggering the action
const responsePromise = page.waitForResponse('/api/graphql');
Expand All @@ -72,7 +72,7 @@ test('client-side gql 500 does not break the application', async ({ page }) => {
});

test('server-side gql 500 does not kill the server', async ({ page }) => {
await loginAs(page.request, 'admin', testEnv.defaultPassword);
await loginAs(page.request, 'admin');
await new SandboxPage(page).goto({ urlEnd: '?ssr-gql-500', expectErrorResponse: true });
await expect(page.locator(':text-matches("Unexpected Execution Error", "g")').first()).toBeVisible();
// we've verified that a 500 occured, now we verify that the server is still alive
Expand All @@ -89,7 +89,7 @@ test('server page load 401 is redirected to login', async ({ context }) => {

test('client page load 401 is redirected to login', async ({ page }) => {
// TODO: Move this to a setup script as recommended by https://playwright.dev/docs/auth
await loginAs(page.request, 'admin', testEnv.defaultPassword);
await loginAs(page.request, 'admin');
const adminDashboardPage = await new AdminDashboardPage(page).goto();

// Now mess up the login cookie and watch the redirect
Expand Down Expand Up @@ -122,14 +122,14 @@ test('can catch 403 errors from goto in new tab', async ({ page, context }) => {
});

test('page load 403 is redirected to home', async ({ page }) => {
await loginAs(page.request, 'manager', testEnv.defaultPassword);
await loginAs(page.request, 'manager');
await new SandboxPage(page).goto();
await page.getByText('Goto page load 403', {exact: true}).click();
await new UserDashboardPage(page).waitFor();
});

test('page load 403 in new tab is redirected to home', async ({ page }) => {
await loginAs(page.request, 'manager', testEnv.defaultPassword);
await loginAs(page.request, 'manager');
await new SandboxPage(page).goto();
const pagePromise = page.context().waitForEvent('page');
await page.getByText('Goto page load 403 new tab').click();
Expand Down
2 changes: 1 addition & 1 deletion frontend/tests/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export const test = base.extend<Fixtures>({
});
await use(tempUser);
const context = await browser.newContext();
await loginAs(context.request, 'admin', testEnv.defaultPassword);
await loginAs(context.request, 'admin');
await deleteUser(context.request, tempUser.id);
await context.close();
},
Expand Down
12 changes: 12 additions & 0 deletions frontend/tests/logout.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {AdminDashboardPage} from './pages/adminDashboardPage';
import {loginAs} from './utils/authHelpers';
import {test} from './fixtures';

test('Back button after logout redirects back to login page', async ({page}) => {
await loginAs(page.request, 'admin');
const adminPage = await new AdminDashboardPage(page).goto();
const drawer = await adminPage.openDrawer();
const loginPage = await drawer.logout();
await page.goBack();
await loginPage.waitFor();
});
21 changes: 16 additions & 5 deletions frontend/tests/pages/authenticatedBasePage.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import type { Locator, Page } from '@playwright/test';
import { BasePage } from './basePage';
import { EmailVerificationAlert } from '../components/emailVerificationAlert';
import type {Locator, Page} from '@playwright/test';

import {AuthenticatedDrawer} from '../components/authenticatedDrawer';
import {BasePage} from './basePage';
import {EmailVerificationAlert} from '../components/emailVerificationAlert';

export class AuthenticatedBasePage extends BasePage {
readonly emailVerificationAlert: EmailVerificationAlert;

private drawerToggle: Locator;

constructor(page: Page, locator: Locator | Locator[], url?: string) {
const drawerToggle = page.locator('label .i-mdi-account-circle');
if (Array.isArray(locator)) {
locator = [page.locator('label .i-mdi-account-circle'), ...locator];
locator = [drawerToggle, ...locator];
} else {
locator = [page.locator('label .i-mdi-account-circle'), locator];
locator = [drawerToggle, locator];
}
super(page, locator, url);
this.drawerToggle = drawerToggle;
this.emailVerificationAlert = new EmailVerificationAlert(page);
}

clickHome(): Promise<void> {
return this.page.locator('.breadcrumbs').getByRole('link', {name: 'Home'}).click();
}

async openDrawer(): Promise<AuthenticatedDrawer> {
await this.drawerToggle.click();
return new AuthenticatedDrawer(this.page).waitFor();
}
}
2 changes: 1 addition & 1 deletion frontend/tests/recreateProject.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { expect } from '@playwright/test';

test('delete and recreate project', async ({ page, uniqueTestId }) => {
// Step 1: Login
await loginAs(page.request, 'admin', testEnv.defaultPassword);
await loginAs(page.request, 'admin');
const adminDashboard = await new AdminDashboardPage(page).goto();

// Step 2: Create a new project
Expand Down
2 changes: 1 addition & 1 deletion frontend/tests/resetProject.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ test('reset project and upload .zip file', async ({ page, tempProject, tempDir }
const allZeroHash = '0000000000000000000000000000000000000000';

// Step 1: Populate project with known initial state
await loginAs(page.request, 'admin', testEnv.defaultPassword);
await loginAs(page.request, 'admin');
const adminDashboardPage = await new AdminDashboardPage(page).goto();
await adminDashboardPage.clickProject(tempProject.name);
const projectPage = await new ProjectPage(page, tempProject.name, tempProject.code).waitFor();
Expand Down
16 changes: 8 additions & 8 deletions frontend/tests/utils/authHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { expect, type APIRequestContext, type Page } from '@playwright/test';
import { serverBaseUrl } from '../envVars';
import { RegisterPage } from '../pages/registerPage';
import { UserDashboardPage } from '../pages/userDashboardPage';
import type { UUID } from 'crypto';
import { executeGql } from './gqlHelpers';
import { LoginPage } from '../pages/loginPage';
import {expect, type APIRequestContext, type Page} from '@playwright/test';
import {defaultPassword, serverBaseUrl} from '../envVars';
import {RegisterPage} from '../pages/registerPage';
import {UserDashboardPage} from '../pages/userDashboardPage';
import type {UUID} from 'crypto';
import {executeGql} from './gqlHelpers';
import {LoginPage} from '../pages/loginPage';

export async function loginAs(api: APIRequestContext, emailOrUsername: string, password: string): Promise<void> {
export async function loginAs(api: APIRequestContext, emailOrUsername: string, password: string = defaultPassword): Promise<void> {
const loginData = {
emailOrUsername: emailOrUsername,
password: password,
Expand Down
8 changes: 3 additions & 5 deletions frontend/tests/viewerPage.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import * as testEnv from './envVars';

import {UserDashboardPage} from './pages/userDashboardPage';
import {ViewerPage} from './pages/viewerPage';
import {expect} from '@playwright/test';
Expand All @@ -10,7 +8,7 @@ test.describe('Viewer Page', () => {

test('navigate to viewer', async ({page}) => {
// Step 1: Login
await loginAs(page.request, 'editor', testEnv.defaultPassword);
await loginAs(page.request, 'editor');
const userDashboard = await new UserDashboardPage(page).goto();

// Step 2: Click through to viewer
Expand All @@ -21,7 +19,7 @@ test.describe('Viewer Page', () => {

test('find entry', async ({page}) => {
// Step 1: Login to viewer
await loginAs(page.request, 'editor', testEnv.defaultPassword);
await loginAs(page.request, 'editor');
const viewerPage = await new ViewerPage(page, 'Sena 3', 'sena-3').goto();
await viewerPage.dismissAboutDialog();

Expand Down Expand Up @@ -51,7 +49,7 @@ test.describe('Viewer Page', () => {

test('entry details', async ({page}) => {
// Step 1: Login to viewer at entry "thembe"
await loginAs(page.request, 'editor', testEnv.defaultPassword);
await loginAs(page.request, 'editor');
const viewerPage = await new ViewerPage(page, 'Sena 3', 'sena-3')
.goto({urlEnd: '?entryId=49cc9257-90c7-4fe0-a9e0-2c8d72aa5e2b&search=animal'});
await viewerPage.dismissAboutDialog();
Expand Down