diff --git a/src/db/tables/featureFlags.ts b/src/db/tables/featureFlags.ts index 136b396c14..af03ea7ee1 100644 --- a/src/db/tables/featureFlags.ts +++ b/src/db/tables/featureFlags.ts @@ -5,6 +5,7 @@ import createDbConnection from "../connect"; import { logger } from "../../app/functions/server/logging"; import { FeatureFlagRow } from "knex/types/tables"; +import { headers } from "next/headers"; const knex = createDbConnection(); @@ -76,11 +77,26 @@ export async function getEnabledFeatureFlags( return void subQuery; }); - const enabledFlagNames = await query; - - return enabledFlagNames.map( + const enabledFlagNames = (await query).map( (row: { name: string }) => row.name as FeatureFlagName, ); + + // Force feature flags for E2E tests via URL query params + if (process.env.E2E_TEST_ENV === "local") { + const forcedFeatureFlags = headers().get("x-forced-feature-flags"); + if (forcedFeatureFlags) { + const forcedFeatureFlagsFiltered = forcedFeatureFlags + .split(",") + .filter((forcedFeatureFlag) => + featureFlagNames.includes(forcedFeatureFlag as FeatureFlagName), + ); + return [ + ...new Set([...enabledFlagNames, ...forcedFeatureFlagsFiltered]), + ] as FeatureFlagName[]; + } + } + + return enabledFlagNames; } /** diff --git a/src/e2e/pages/authPage.ts b/src/e2e/pages/authPage.ts index 4831b776dd..b61e27a4aa 100644 --- a/src/e2e/pages/authPage.ts +++ b/src/e2e/pages/authPage.ts @@ -71,4 +71,37 @@ export class AuthPage { expect(verificationCode).toBeDefined(); await this.enterVerificationCode(verificationCode as string); } + + async signInToFxA(email: string, password: string) { + await this.page.goto(process.env.FXA_SETTINGS_URL as string); + await this.page.context().clearCookies(); + await this.page + .locator("//input[@type='password'] | //div/input[@type='email']") + .waitFor({ state: "visible" }); + const visible = await this.useDifferentEmailButton.isVisible(); + if (visible) { + await this.useDifferentEmailButton.click(); + await this.page.waitForURL(/^(?!.*signin).*/); + } + + // enter email + await this.emailInputField.fill(email); + await this.continueButton.click(); + await this.page.waitForURL(/^(?!.*signin).*/); + + // enter password + await this.passwordInputField.fill(password); + await this.continue({ waitForURL: process.env.FXA_SETTINGS_URL }); + } + + async initSilentAuth() { + await this.page.setExtraHTTPHeaders({ + "x-forced-feature-flags": "PromptNoneAuthFlow", + }); + await this.page.goto( + `${process.env.E2E_TEST_BASE_URL as string}/?utm_source=moz-account&utm_campaign=settings-promo&utm_content=monitor-free`, + ); + // FxA can take a while to load on stage: + await this.page.waitForURL("**/authorization?**"); + } } diff --git a/src/e2e/specs/auth.spec.ts b/src/e2e/specs/auth.spec.ts index deeb03664b..9dae4de91f 100644 --- a/src/e2e/specs/auth.spec.ts +++ b/src/e2e/specs/auth.spec.ts @@ -45,7 +45,7 @@ test.describe(`${process.env.E2E_TEST_ENV} - Authentication flow verification @s landingPage, dashboardPage, }, testInfo) => { - // speed up test by ignore non necessary requests + // speed up test by ignoring non-necessary requests await page.route(/(analytics)/, async (route) => { await route.abort(); }); @@ -68,4 +68,62 @@ test.describe(`${process.env.E2E_TEST_ENV} - Authentication flow verification @s }, ); }); + + test("Verify successful silent authentication with existing user", async ({ + page, + authPage, + }, testInfo) => { + // speed up test by ignoring non-necessary requests + await page.route(/(analytics)/, async (route) => { + await route.abort(); + }); + + // login to FxA + await authPage.signInToFxA( + process.env.E2E_TEST_ACCOUNT_EMAIL as string, + process.env.E2E_TEST_ACCOUNT_PASSWORD as string, + ); + + // start authentication flow + await authPage.initSilentAuth(); + + // assert successful login + await page.waitForURL("**/user/dashboard/**"); + + await testInfo.attach( + `${process.env.E2E_TEST_ENV}-silent-authentication-monitor-dashboard.png`, + { + body: await page.screenshot(), + contentType: "image/png", + }, + ); + }); + + test("Verify failed silent authentication with existing user", async ({ + page, + authPage, + landingPage, + }, testInfo) => { + // speed up test by ignoring non-necessary requests + await page.route(/(analytics)/, async (route) => { + await route.abort(); + }); + + // start authentication flow + await authPage.initSilentAuth(); + + // assert failed login + await expect(landingPage.monitorLandingHeader).toBeVisible({ + // The header can take a while to show in Playwright. + timeout: 5000, + }); + + await testInfo.attach( + `${process.env.E2E_TEST_ENV}-silent-authentication-monitor-dashboard.png`, + { + body: await page.screenshot(), + contentType: "image/png", + }, + ); + }); }); diff --git a/src/middleware.ts b/src/middleware.ts index 81771ac3f9..4a805f5277 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -12,6 +12,7 @@ export function middleware(request: NextRequest) { const requestHeaders = new Headers(request.headers); requestHeaders.set("x-nonce", nonce); + // Add the CSP to the request headers - that will make Next.js detect it and // add it to the inline `