diff --git a/playwright/.env.template b/playwright/.env.template index 5b6c0c9e..e7b1b4aa 100644 --- a/playwright/.env.template +++ b/playwright/.env.template @@ -40,6 +40,7 @@ DUMMY_AUTHORITY=http://${KC_HTTP_HOST}:${KC_HTTP_PORT}/realms/${DUMMY_REALM} ROCKET_ADDRESS=0.0.0.0 ROCKET_PORT=8000 DOMAIN=http://127.0.0.1:${ROCKET_PORT} +LOG_LEVEL=info,oidcwarden::sso=debug I_REALLY_WANT_VOLATILE_STORAGE=true SSO_ENABLED=true diff --git a/playwright/compose/vaultwarden/Dockerfile b/playwright/compose/vaultwarden/Dockerfile index 4606ae36..2772dc0a 100644 --- a/playwright/compose/vaultwarden/Dockerfile +++ b/playwright/compose/vaultwarden/Dockerfile @@ -2,8 +2,8 @@ FROM playwright_oidc_vaultwarden_prebuilt AS vaultwarden FROM node:18-bookworm AS build -arg REPO_URL -arg COMMIT_HASH +ARG REPO_URL +ARG COMMIT_HASH ENV REPO_URL=$REPO_URL ENV COMMIT_HASH=$COMMIT_HASH diff --git a/playwright/docker-compose.yml b/playwright/docker-compose.yml index 72736eb5..2adeb61a 100644 --- a/playwright/docker-compose.yml +++ b/playwright/docker-compose.yml @@ -24,10 +24,12 @@ services: environment: - DATABASE_URL - I_REALLY_WANT_VOLATILE_STORAGE + - LOG_LEVEL - LOGIN_RATELIMIT_MAX_BURST - SMTP_HOST - SMTP_FROM - SMTP_DEBUG + - SSO_DEBUG_TOKENS - SSO_FRONTEND - SSO_ENABLED - SSO_ONLY diff --git a/playwright/global-utils.ts b/playwright/global-utils.ts index 317a69b4..ae723c7f 100644 --- a/playwright/global-utils.ts +++ b/playwright/global-utils.ts @@ -189,7 +189,7 @@ export async function startVaultwarden(browser: Browser, testInfo: TestInfo, env console.log(`Starting Vaultwarden`); execSync(`docker compose --profile playwright --env-file test.env up -d Vaultwarden`, { - env: { LOGIN_RATELIMIT_MAX_BURST: 100, ...env, ...dbConfig(testInfo) }, + env: { ...env, ...dbConfig(testInfo) }, }); await waitFor("/", browser); console.log(`Vaultwarden running on: ${process.env.DOMAIN}`); @@ -210,3 +210,14 @@ export async function checkNotification(page: Page, hasText: string) { await page.locator('bit-toast').filter({ hasText }).getByRole('button').click(); await expect(page.locator('bit-toast').filter({ hasText })).toHaveCount(0); } + +export async function cleanLanding(page: Page) { + await page.goto('/', { waitUntil: 'domcontentloaded' }); + await expect(page.getByRole('button').nth(0)).toBeVisible(); + + const logged = await page.getByRole('button', { name: 'Log out' }).count(); + if( logged > 0 ){ + await page.getByRole('button', { name: 'Log out' }).click(); + await page.getByRole('button', { name: 'Log out' }).click(); + } +} diff --git a/playwright/test.env b/playwright/test.env index 1faefb90..d5a38f3e 100644 --- a/playwright/test.env +++ b/playwright/test.env @@ -53,6 +53,9 @@ DUMMY_AUTHORITY=http://${KC_HTTP_HOST}:${KC_HTTP_PORT}/realms/${DUMMY_REALM} ###################### ROCKET_PORT=8003 DOMAIN=http://127.0.0.1:${ROCKET_PORT} +LOG_LEVEL=info,oidcwarden::sso=debug +LOGIN_RATELIMIT_MAX_BURST=100 + SMTP_SECURITY=off SMTP_PORT=${MAILDEV_SMTP_PORT} SMTP_FROM_NAME=Vaultwarden @@ -61,6 +64,7 @@ SMTP_TIMEOUT=5 SSO_CLIENT_ID=VaultWarden SSO_CLIENT_SECRET=VaultWarden SSO_AUTHORITY=http://${KC_HTTP_HOST}:${KC_HTTP_PORT}/realms/${TEST_REALM} +SSO_DEBUG_TOKENS=true ########################### # Docker MariaDb container# diff --git a/playwright/tests/login.smtp.spec.ts b/playwright/tests/login.smtp.spec.ts index 6261fd89..3005df71 100644 --- a/playwright/tests/login.smtp.spec.ts +++ b/playwright/tests/login.smtp.spec.ts @@ -3,6 +3,7 @@ import { MailDev } from 'maildev'; const utils = require('../global-utils'); import { createAccount, logUser } from './setups/user'; +import { activateEmail, retrieveEmailCode, disableEmail } from './setups/2fa'; let users = utils.loadEnv(); @@ -67,24 +68,7 @@ test('Activaite 2fa', async ({ context, page }) => { await logUser(test, page, users.user1); - await page.getByRole('button', { name: users.user1.name }).click(); - await page.getByRole('menuitem', { name: 'Account settings' }).click(); - await page.getByRole('link', { name: 'Security' }).click(); - await page.getByRole('link', { name: 'Two-step login' }).click(); - await page.locator('li').filter({ hasText: 'Email' }).getByRole('button').click(); - await page.getByLabel('Master password (required)').fill(users.user1.password); - await page.getByRole('button', { name: 'Continue' }).click(); - await page.getByRole('button', { name: 'Send email' }).click(); - - const codeMail = await emails.next((mail) => mail.subject === "Vaultwarden Login Verification Code"); - const page2 = await context.newPage(); - await page2.setContent(codeMail.html); - const code = await page2.getByTestId("2fa").innerText(); - await page2.close(); - - await page.getByLabel('2. Enter the resulting 6').fill(code); - await page.getByRole('button', { name: 'Turn on' }).click(); - await page.getByRole('heading', { name: 'Turned on', exact: true }); + await activateEmail(test, page, users.user1, emails); emails.close(); }); @@ -112,19 +96,7 @@ test('2fa', async ({ context, page }) => { await expect(page).toHaveTitle(/Vaultwarden Web/); }) - await test.step('disable', async () => { - await page.getByRole('button', { name: 'Test' }).click(); - await page.getByRole('menuitem', { name: 'Account settings' }).click(); - await page.getByRole('link', { name: 'Security' }).click(); - await page.getByRole('link', { name: 'Two-step login' }).click(); - await page.locator('li').filter({ hasText: 'Email' }).getByRole('button').click(); - await page.getByLabel('Master password (required)').click(); - await page.getByLabel('Master password (required)').fill(users.user1.password); - await page.getByRole('button', { name: 'Continue' }).click(); - await page.getByRole('button', { name: 'Turn off' }).click(); - await page.getByRole('button', { name: 'Yes' }).click(); - await utils.checkNotification(page, 'Two-step login provider turned off'); - }); + await disableEmail(test, page, users.user1); emails.close(); }); diff --git a/playwright/tests/login.spec.ts b/playwright/tests/login.spec.ts index 59c314b5..fe9d20ae 100644 --- a/playwright/tests/login.spec.ts +++ b/playwright/tests/login.spec.ts @@ -3,6 +3,7 @@ import * as OTPAuth from "otpauth"; import * as utils from "../global-utils"; import { createAccount, logUser } from './setups/user'; +import { activateTOTP, disableTOTP } from './setups/2fa'; let users = utils.loadEnv(); let totp; @@ -24,33 +25,14 @@ test('Master password login', async ({ page }) => { }); test('Authenticator 2fa', async ({ context, page }) => { - let totp; - - await test.step('Login', async () => { - await logUser(test, page, users.user1); - }); - - await test.step('Activate', async () => { - await page.getByRole('button', { name: users.user1.name }).click(); - await page.getByRole('menuitem', { name: 'Account settings' }).click(); - await page.getByRole('link', { name: 'Security' }).click(); - await page.getByRole('link', { name: 'Two-step login' }).click(); - await page.locator('li').filter({ hasText: 'TOTP Authenticator' }).getByRole('button').click(); - await page.getByLabel('Master password (required)').fill(users.user1.password); - await page.getByRole('button', { name: 'Continue' }).click(); - - const secret = await page.getByLabel('Key').innerText(); - totp = new OTPAuth.TOTP({ secret, period: 30 }); + await logUser(test, page, users.user1); - await page.getByLabel('Verification code (required)').fill(totp.generate()); - await page.getByRole('button', { name: 'Turn on' }).click(); - await page.getByRole('heading', { name: 'Turned on', exact: true }); - await page.getByLabel('Close').click(); - }) + let totp = await activateTOTP(test, page, users.user1); await test.step('logout', async () => { await page.getByRole('button', { name: users.user1.name }).click(); await page.getByRole('menuitem', { name: 'Log out' }).click(); + await expect(page.getByRole('heading', { name: 'Log in' })).toBeVisible(); }); await test.step('login', async () => { @@ -68,17 +50,5 @@ test('Authenticator 2fa', async ({ context, page }) => { await expect(page).toHaveTitle(/Vaultwarden Web/); }); - await test.step('disable', async () => { - await page.getByRole('button', { name: 'Test' }).click(); - await page.getByRole('menuitem', { name: 'Account settings' }).click(); - await page.getByRole('link', { name: 'Security' }).click(); - await page.getByRole('link', { name: 'Two-step login' }).click(); - await page.locator('li').filter({ hasText: 'TOTP Authenticator' }).getByRole('button').click(); - await page.getByLabel('Master password (required)').click(); - await page.getByLabel('Master password (required)').fill(users.user1.password); - await page.getByRole('button', { name: 'Continue' }).click(); - await page.getByRole('button', { name: 'Turn off' }).click(); - await page.getByRole('button', { name: 'Yes' }).click(); - await utils.checkNotification(page, 'Two-step login provider turned off'); - }); + await disableTOTP(test, page, users.user1); }); diff --git a/playwright/tests/organization.smtp.spec.ts b/playwright/tests/organization.smtp.spec.ts index 547213d4..b9070196 100644 --- a/playwright/tests/organization.smtp.spec.ts +++ b/playwright/tests/organization.smtp.spec.ts @@ -57,7 +57,7 @@ test('invited with new account', async ({ page }) => { await expect(page).toHaveTitle(/Create account | Vaultwarden Web/); //await page.getByLabel('Name').fill(users.user2.name); - await page.getByLabel('Master password (required)', { exact: true }).fill(users.user2.password); + await page.getByLabel('New master password (required)', { exact: true }).fill(users.user2.password); await page.getByLabel('Confirm master password (').fill(users.user2.password); await page.getByRole('button', { name: 'Create account' }).click(); await utils.checkNotification(page, 'Your new account has been created'); diff --git a/playwright/tests/organization.spec.ts b/playwright/tests/organization.spec.ts index 5c4dcd39..c2b9d69d 100644 --- a/playwright/tests/organization.spec.ts +++ b/playwright/tests/organization.spec.ts @@ -11,8 +11,8 @@ test.beforeAll('Setup', async ({ browser }, testInfo: TestInfo) => { await utils.startVaultwarden(browser, testInfo); }); -test.afterAll('Teardown', async ({}, testInfo: TestInfo) => { - utils.stopVaultwarden(testInfo); +test.afterAll('Teardown', async ({}) => { + utils.stopVaultwarden(); }); test('Invite', async ({ page }) => { diff --git a/playwright/tests/setups/2fa.ts b/playwright/tests/setups/2fa.ts new file mode 100644 index 00000000..e8702636 --- /dev/null +++ b/playwright/tests/setups/2fa.ts @@ -0,0 +1,92 @@ +import { expect, type Page, Test } from '@playwright/test'; +import { type MailBuffer } from 'maildev'; +import * as OTPAuth from "otpauth"; + +import * as utils from '../../global-utils'; + +export async function activateTOTP(test: Test, page: Page, user: { name: string, password: string }): OTPAuth.TOTP { + return await test.step('Activate TOTP 2FA', async () => { + await page.getByRole('button', { name: user.name }).click(); + await page.getByRole('menuitem', { name: 'Account settings' }).click(); + await page.getByRole('link', { name: 'Security' }).click(); + await page.getByRole('link', { name: 'Two-step login' }).click(); + await page.locator('bit-item').filter({ hasText: /Authenticator app/ }).getByRole('button').click(); + await page.getByLabel('Master password (required)').fill(user.password); + await page.getByRole('button', { name: 'Continue' }).click(); + + const secret = await page.getByLabel('Key').innerText(); + let totp = new OTPAuth.TOTP({ secret, period: 30 }); + + await page.getByLabel(/Verification code/).fill(totp.generate()); + await page.getByRole('button', { name: 'Turn on' }).click(); + await page.getByRole('heading', { name: 'Turned on', exact: true }); + await page.getByLabel('Close').click(); + + return totp; + }) +} + +export async function disableTOTP(test: Test, page: Page, user: { password: string }) { + await test.step('Disable TOTP 2FA', async () => { + await page.getByRole('button', { name: 'Test' }).click(); + await page.getByRole('menuitem', { name: 'Account settings' }).click(); + await page.getByRole('link', { name: 'Security' }).click(); + await page.getByRole('link', { name: 'Two-step login' }).click(); + await page.locator('bit-item').filter({ hasText: /Authenticator app/ }).getByRole('button').click(); + await page.getByLabel('Master password (required)').click(); + await page.getByLabel('Master password (required)').fill(user.password); + await page.getByRole('button', { name: 'Continue' }).click(); + await page.getByRole('button', { name: 'Turn off' }).click(); + await page.getByRole('button', { name: 'Yes' }).click(); + await utils.checkNotification(page, 'Two-step login provider turned off'); + }); +} + +export async function activateEmail(test: Test, page: Page, user: { name: string, password: string }, mailBuffer: MailBuffer) { + await test.step('Activate Email 2FA', async () => { + await page.getByRole('button', { name: user.name }).click(); + await page.getByRole('menuitem', { name: 'Account settings' }).click(); + await page.getByRole('link', { name: 'Security' }).click(); + await page.getByRole('link', { name: 'Two-step login' }).click(); + await page.locator('bit-item').filter({ hasText: 'Email Email Enter a code sent' }).getByRole('button').click(); + await page.getByLabel('Master password (required)').fill(user.password); + await page.getByRole('button', { name: 'Continue' }).click(); + await page.getByRole('button', { name: 'Send email' }).click(); + }); + + let code = await retrieveEmailCode(test, page, mailBuffer); + + await test.step('input code', async () => { + await page.getByLabel('2. Enter the resulting 6').fill(code); + await page.getByRole('button', { name: 'Turn on' }).click(); + await page.getByRole('heading', { name: 'Turned on', exact: true }); + }); +} + +export async function retrieveEmailCode(test: Test, page: Page, mailBuffer: MailBuffer): string { + return await test.step('retrieve code', async () => { + const codeMail = await mailBuffer.next((mail) => mail.subject.includes("Login Verification Code")); + const page2 = await page.context().newPage(); + await page2.setContent(codeMail.html); + const code = await page2.getByTestId("2fa").innerText(); + await page2.close(); + return code; + }); +} + +export async function disableEmail(test: Test, page: Page, user: { password: string }) { + await test.step('Disable Email 2FA', async () => { + await page.getByRole('button', { name: 'Test' }).click(); + await page.getByRole('menuitem', { name: 'Account settings' }).click(); + await page.getByRole('link', { name: 'Security' }).click(); + await page.getByRole('link', { name: 'Two-step login' }).click(); + await page.locator('bit-item').filter({ hasText: 'Email' }).getByRole('button').click(); + await page.getByLabel('Master password (required)').click(); + await page.getByLabel('Master password (required)').fill(user.password); + await page.getByRole('button', { name: 'Continue' }).click(); + await page.getByRole('button', { name: 'Turn off' }).click(); + await page.getByRole('button', { name: 'Yes' }).click(); + + await utils.checkNotification(page, 'Two-step login provider turned off'); + }); +} diff --git a/playwright/tests/setups/sso.ts b/playwright/tests/setups/sso.ts index faf9e742..6cb5c917 100644 --- a/playwright/tests/setups/sso.ts +++ b/playwright/tests/setups/sso.ts @@ -1,7 +1,9 @@ import { expect, type Page, Test } from '@playwright/test'; import { type MailBuffer, MailServer } from 'maildev'; +import * as OTPAuth from "otpauth"; import * as utils from '../../global-utils'; +import { retrieveEmailCode } from './2fa'; /** * If a MailBuffer is passed it will be used and consume the expected emails @@ -10,50 +12,52 @@ export async function logNewUser( test: Test, page: Page, user: { email: string, name: string, password: string }, - options: { mailBuffer?: MailBuffer, mailServer?: MailServer } = {} + options: { mailBuffer?: MailBuffer, override?: boolean } = {} ) { - let mailBuffer = options.mailBuffer ?? options.mailServer?.buffer(user.email); - try { - await test.step('Create user', async () => { - await page.context().clearCookies(); + await test.step(`Create user ${user.name}`, async () => { + await page.context().clearCookies(); - await test.step('Landing page', async () => { - await page.goto('/'); + await test.step('Landing page', async () => { + await utils.cleanLanding(page); + + if( options.override ) { + await page.getByRole('button', { name: 'Continue' }).click(); + } else { await page.getByLabel(/Email address/).fill(user.email); await page.getByRole('button', { name: /Use single sign-on/ }).click(); - }); - - await test.step('Keycloak login', async () => { - await expect(page.getByRole('heading', { name: 'Sign in to your account' })).toBeVisible(); - await page.getByLabel(/Username/).fill(user.name); - await page.getByLabel('Password', { exact: true }).fill(user.password); - await page.getByRole('button', { name: 'Sign In' }).click(); - }); + } + }); - await test.step('Create Vault account', async () => { - await expect(page.getByRole('heading', { name: 'Join organisation' })).toBeVisible(); - await page.getByLabel('Master password (required)', { exact: true }).fill(user.password); - await page.getByLabel('Confirm master password (').fill(user.password); - await page.getByRole('button', { name: 'Create account' }).click(); - }); + await test.step('Keycloak login', async () => { + await expect(page.getByRole('heading', { name: 'Sign in to your account' })).toBeVisible(); + await page.getByLabel(/Username/).fill(user.name); + await page.getByLabel('Password', { exact: true }).fill(user.password); + await page.getByRole('button', { name: 'Sign In' }).click(); + }); - await test.step('Default vault page', async () => { - await expect(page).toHaveTitle(/Vaultwarden Web/); - await expect(page.getByTitle('All vaults', { exact: true })).toBeVisible(); - }); + await test.step('Create Vault account', async () => { + await expect(page.getByRole('heading', { name: 'Join organisation' })).toBeVisible(); + await page.getByLabel('New master password (required)', { exact: true }).fill(user.password); + await page.getByLabel('Confirm master password (').fill(user.password); + await page.getByRole('button', { name: 'Create account' }).click(); + }); - if( mailBuffer ){ - await test.step('Check emails', async () => { - await expect(mailBuffer.next((m) => m.subject === "Welcome")).resolves.toBeDefined(); - await expect(mailBuffer.next((m) => m.subject.includes("New Device Logged"))).resolves.toBeDefined(); - }); - } + await test.step('Default vault page', async () => { + await expect(page).toHaveTitle(/Vaultwarden Web/); + await expect(page.getByTitle('All vaults', { exact: true })).toBeVisible(); }); - } finally { - if( options.mailServer ){ - mailBuffer.close(); + + await utils.checkNotification(page, 'Account successfully created!'); + await utils.checkNotification(page, 'Invitation accepted'); + + if( options.mailBuffer ){ + let mailBuffer = options.mailBuffer; + await test.step('Check emails', async () => { + await expect(mailBuffer.next((m) => m.subject === "Welcome")).resolves.toBeDefined(); + await expect(mailBuffer.next((m) => m.subject.includes("New Device Logged"))).resolves.toBeDefined(); + }); } - } + }); } /** @@ -63,47 +67,72 @@ export async function logUser( test: Test, page: Page, user: { email: string, password: string }, - options: { mailBuffer ?: MailBuffer, mailServer?: MailServer} = {} + options: { + mailBuffer ?: MailBuffer, + override?: boolean, + totp?: OTPAuth.TOTP, + mail2fa?: boolean, + } = {} ) { - let mailBuffer = options.mailBuffer ?? options.mailServer?.buffer(user.email); - try { - await test.step('Log user', async () => { - await page.context().clearCookies(); + let mailBuffer = options.mailBuffer; + + await test.step(`Log user ${user.email}`, async () => { + await page.context().clearCookies(); - await test.step('Landing page', async () => { - await page.goto('/'); + await test.step('Landing page', async () => { + await utils.cleanLanding(page); + + if( options.override ) { + await page.getByRole('button', { name: 'Continue' }).click(); + } else { await page.getByLabel(/Email address/).fill(user.email); await page.getByRole('button', { name: /Use single sign-on/ }).click(); - }); + } + }); - await test.step('Keycloak login', async () => { - await expect(page.getByRole('heading', { name: 'Sign in to your account' })).toBeVisible(); - await page.getByLabel(/Username/).fill(user.name); - await page.getByLabel('Password', { exact: true }).fill(user.password); - await page.getByRole('button', { name: 'Sign In' }).click(); - }); + await test.step('Keycloak login', async () => { + await expect(page.getByRole('heading', { name: 'Sign in to your account' })).toBeVisible(); + await page.getByLabel(/Username/).fill(user.name); + await page.getByLabel('Password', { exact: true }).fill(user.password); + await page.getByRole('button', { name: 'Sign In' }).click(); + }); - await test.step('Unlock vault', async () => { - await expect(page).toHaveTitle('Vaultwarden Web'); - await expect(page.getByRole('heading', { name: 'Your vault is locked' })).toBeVisible(); - await page.getByLabel('Master password').fill(user.password); - await page.getByRole('button', { name: 'Unlock' }).click(); - }); + if( options.totp || options.mail2fa ){ + let code; + + await test.step('2FA check', async () => { + await expect(page.getByRole('heading', { name: 'Verify your Identity' })).toBeVisible(); - await test.step('Default vault page', async () => { - await expect(page).toHaveTitle(/Vaultwarden Web/); - await expect(page.getByTitle('All vaults', { exact: true })).toBeVisible(); + if( options.totp ) { + const totp = options.totp; + let timestamp = Date.now(); // Needed to use the next token + timestamp = timestamp + (totp.period - (Math.floor(timestamp / 1000) % totp.period) + 1) * 1000; + code = totp.generate({timestamp}); + } else if( options.mail2fa ){ + code = await retrieveEmailCode(test, page, mailBuffer); + } + + await page.getByLabel(/Verification code/).fill(code); + await page.getByRole('button', { name: 'Continue' }).click(); }); + } - if( options.emails ){ - await test.step('Check email', async () => { - await expect(mailBuffer.next((m) => m.subject.includes("New Device Logged"))).resolves.toBeDefined(); - }); - } + await test.step('Unlock vault', async () => { + await expect(page).toHaveTitle('Vaultwarden Web'); + await expect(page.getByRole('heading', { name: 'Your vault is locked' })).toBeVisible(); + await page.getByLabel('Master password').fill(user.password); + await page.getByRole('button', { name: 'Unlock' }).click(); }); - } finally { - if( options.mailServer ){ - mailBuffer.close(); + + await test.step('Default vault page', async () => { + await expect(page).toHaveTitle(/Vaultwarden Web/); + await expect(page.getByTitle('All vaults', { exact: true })).toBeVisible(); + }); + + if( mailBuffer ){ + await test.step('Check email', async () => { + await expect(mailBuffer.next((m) => m.subject.includes("New Device Logged"))).resolves.toBeDefined(); + }); } - } + }); } diff --git a/playwright/tests/setups/user.ts b/playwright/tests/setups/user.ts index 6c18dc92..cce93bff 100644 --- a/playwright/tests/setups/user.ts +++ b/playwright/tests/setups/user.ts @@ -4,16 +4,8 @@ import { type MailBuffer } from 'maildev'; import * as utils from '../../global-utils'; export async function createAccount(test, page: Page, user: { email: string, name: string, password: string }, mailBuffer?: MailBuffer) { - await test.step('Create user', async () => { - // Landing page - await page.goto('/', { waitUntil: 'domcontentloaded' }); - await expect(page.getByRole('button').nth(0)).toBeVisible(); - - const logged = await page.getByRole('button', { name: 'Log out' }).count(); - if( logged > 0 ){ - await page.getByRole('button', { name: 'Log out' }).click(); - await page.getByRole('button', { name: 'Log out' }).click(); - } + await test.step(`Create user ${user.name}`, async () => { + await utils.cleanLanding(page); await page.getByRole('link', { name: 'Create account' }).click(); @@ -24,13 +16,15 @@ export async function createAccount(test, page: Page, user: { email: string, nam await page.getByRole('button', { name: 'Continue' }).click(); // Vault finish Creation - await page.getByLabel('Master password (required)', { exact: true }).fill(user.password); + await page.getByLabel('New master password (required)', { exact: true }).fill(user.password); await page.getByLabel('Confirm master password (').fill(user.password); await page.getByRole('button', { name: 'Create account' }).click(); + await utils.checkNotification(page, 'Your new account has been created') + // We are now in the default vault page await expect(page).toHaveTitle('Vaults | Vaultwarden Web'); - await utils.checkNotification(page, 'Your new account has been created'); + await utils.checkNotification(page, 'You have been logged in!'); if( mailBuffer ){ await expect(mailBuffer.next((m) => m.subject === "Welcome")).resolves.toBeDefined(); @@ -39,16 +33,8 @@ export async function createAccount(test, page: Page, user: { email: string, nam } export async function logUser(test, page: Page, user: { email: string, password: string }, mailBuffer?: MailBuffer) { - await test.step('Log user', async () => { - // Landing page - await page.goto('/', { waitUntil: 'domcontentloaded' }); - await expect(page.getByRole('button').nth(0)).toBeVisible(); - - const logged = await page.getByRole('button', { name: 'Log out' }).count(); - if( logged > 0 ){ - await page.getByRole('button', { name: 'Log out' }).click(); - await page.getByRole('button', { name: 'Log out' }).click(); - } + await test.step(`Log user ${user.email}`, async () => { + await utils.cleanLanding(page); await page.getByLabel(/Email address/).fill(user.email); await page.getByRole('button', { name: 'Continue' }).click(); diff --git a/playwright/tests/sso_login.smtp.spec.ts b/playwright/tests/sso_login.smtp.spec.ts new file mode 100644 index 00000000..02a1d8d3 --- /dev/null +++ b/playwright/tests/sso_login.smtp.spec.ts @@ -0,0 +1,53 @@ +import { test, expect, type TestInfo } from '@playwright/test'; +import { MailDev } from 'maildev'; + +import { logNewUser, logUser } from './setups/sso'; +import { activateEmail, disableEmail } from './setups/2fa'; +import * as utils from "../global-utils"; + +let users = utils.loadEnv(); + +let mailserver; + +test.beforeAll('Setup', async ({ browser }, testInfo: TestInfo) => { + mailserver = new MailDev({ + port: process.env.MAILDEV_SMTP_PORT, + web: { port: process.env.MAILDEV_HTTP_PORT }, + }) + + await mailserver.listen(); + + await utils.startVaultwarden(browser, testInfo, { + SSO_ENABLED: true, + SSO_ONLY: false, + SMTP_HOST: process.env.MAILDEV_HOST, + SMTP_FROM: process.env.VAULTWARDEN_SMTP_FROM, + }); +}); + +test.afterAll('Teardown', async ({}) => { + utils.stopVaultwarden(); + if( mailserver ){ + await mailserver.close(); + } +}); + +test('Create and activate 2FA', async ({ page }) => { + const mailBuffer = mailserver.buffer(users.user1.email); + + await logNewUser(test, page, users.user1, {mailBuffer: mailBuffer}); + + await activateEmail(test, page, users.user1, mailBuffer); + + mailBuffer.close(); +}); + +test('Log and disable', async ({ page }) => { + const mailBuffer = mailserver.buffer(users.user1.email); + + await logUser(test, page, users.user1, {mailBuffer: mailBuffer, mail2fa: true}); + + await disableEmail(test, page, users.user1); + + mailBuffer.close(); +}); diff --git a/playwright/tests/sso_login.spec.ts b/playwright/tests/sso_login.spec.ts index 62500483..562ccb09 100644 --- a/playwright/tests/sso_login.spec.ts +++ b/playwright/tests/sso_login.spec.ts @@ -1,5 +1,7 @@ import { test, expect, type TestInfo } from '@playwright/test'; + import { logNewUser, logUser } from './setups/sso'; +import { activateTOTP, disableTOTP } from './setups/2fa'; import * as utils from "../global-utils"; let users = utils.loadEnv(); @@ -38,6 +40,16 @@ test('Non SSO login', async ({ page }) => { await expect(page).toHaveTitle(/Vaultwarden Web/); }); +test('SSO login with TOTP 2fa', async ({ page }) => { + await logUser(test, page, users.user1); + + let totp = await activateTOTP(test, page, users.user1); + + await logUser(test, page, users.user1, { totp }); + + await disableTOTP(test, page, users.user1); +}); + test('Non SSO login impossible', async ({ page, browser }, testInfo: TestInfo) => { await utils.restartVaultwarden(page, testInfo, { SSO_ENABLED: true, diff --git a/playwright/tests/sso_organization.smtp.spec.ts b/playwright/tests/sso_organization.smtp.spec.ts index 03b08707..36bfcef7 100644 --- a/playwright/tests/sso_organization.smtp.spec.ts +++ b/playwright/tests/sso_organization.smtp.spec.ts @@ -90,7 +90,7 @@ test('invited with new account', async ({ page }) => { await test.step('Create Vault account', async () => { await expect(page.getByRole('heading', { name: 'Join organisation' })).toBeVisible(); - await page.getByLabel('Master password (required)', { exact: true }).fill(users.user2.password); + await page.getByLabel('New master password (required)', { exact: true }).fill(users.user2.password); await page.getByLabel('Confirm master password (').fill(users.user2.password); await page.getByRole('button', { name: 'Create account' }).click(); });