Replace the header "Byt lösenord" link with an Inställningar menu for changing email or password. Email changes are two-step: request with password, confirmation link to the new address, then password again on confirm so a wrong inbox cannot take over the account. - Backend: EmailChangeService, V10 email_change_tokens, confirm API - Frontend: ChangeEmailPage, ConfirmEmailChangePage, header dropdown - E2E: account-settings round-trips, Mailpit verification, wrong-password guard - Flyway: V9 restore for dev DBs, CI migration checks, V10 for email tokens Co-authored-by: Cursor <cursoragent@cursor.com>
182 lines
6.2 KiB
TypeScript
182 lines
6.2 KiB
TypeScript
import { test, expect, type Page, type APIRequestContext } from '@playwright/test'
|
|
import {
|
|
clearMailpit,
|
|
countMessagesTo,
|
|
waitForEmailChangeToken,
|
|
} from './helpers/mailpit'
|
|
|
|
test.describe('Account settings', () => {
|
|
test('can change password and change back', async ({ page, request }) => {
|
|
const email = `pw-change-${Date.now()}@bilhej.se`
|
|
const originalPassword = 'original1234'
|
|
const changedPassword = 'changed12345'
|
|
|
|
await registerUser(request, email, originalPassword)
|
|
await loginViaUi(page, email, originalPassword)
|
|
|
|
await changePasswordViaUi(page, originalPassword, changedPassword)
|
|
await expect(page.getByText('Lösenordet har uppdaterats.')).toBeVisible()
|
|
|
|
await logoutViaHeader(page)
|
|
await expectLoginFails(page, email, originalPassword)
|
|
|
|
await loginViaUi(page, email, changedPassword)
|
|
|
|
await changePasswordViaUi(page, changedPassword, originalPassword)
|
|
await expect(page.getByText('Lösenordet har uppdaterats.')).toBeVisible()
|
|
|
|
await logoutViaHeader(page)
|
|
await expectLoginFails(page, email, changedPassword)
|
|
await loginViaUi(page, email, originalPassword)
|
|
})
|
|
|
|
test('can change email after confirming link sent to new address', async ({
|
|
page,
|
|
request,
|
|
}) => {
|
|
const suffix = Date.now()
|
|
const originalEmail = `email-change-${suffix}@bilhej.se`
|
|
const tempEmail = `email-change-${suffix}-new@bilhej.se`
|
|
const password = 'password1234'
|
|
|
|
await clearMailpit(request)
|
|
await registerUser(request, originalEmail, password)
|
|
await loginViaUi(page, originalEmail, password)
|
|
|
|
await page.goto('/andra-epost')
|
|
await changeEmailViaUi(page, tempEmail, password)
|
|
await expect(
|
|
page.getByText(
|
|
'Vi har skickat en bekräftelselänk till din nya e-postadress.',
|
|
),
|
|
).toBeVisible()
|
|
expect(await countMessagesTo(request, tempEmail)).toBe(1)
|
|
expect(await countMessagesTo(request, originalEmail)).toBe(0)
|
|
|
|
const token = await waitForEmailChangeToken(request, tempEmail, {
|
|
publicBaseUrl: 'http://frontend',
|
|
})
|
|
await confirmEmailChangeViaUi(page, token, password)
|
|
await expect(
|
|
page.getByText('Din e-postadress har uppdaterats.'),
|
|
).toBeVisible()
|
|
await expect(page.locator('header')).toContainText(tempEmail)
|
|
|
|
await clearMailpit(request)
|
|
await page.goto('/andra-epost')
|
|
await changeEmailViaUi(page, originalEmail, password)
|
|
await expect(
|
|
page.getByText(
|
|
'Vi har skickat en bekräftelselänk till din nya e-postadress.',
|
|
),
|
|
).toBeVisible()
|
|
expect(await countMessagesTo(request, originalEmail)).toBe(1)
|
|
|
|
const restoreToken = await waitForEmailChangeToken(request, originalEmail, {
|
|
publicBaseUrl: 'http://frontend',
|
|
})
|
|
await confirmEmailChangeViaUi(page, restoreToken, password)
|
|
await expect(
|
|
page.getByText('Din e-postadress har uppdaterats.'),
|
|
).toBeVisible()
|
|
await expect(page.locator('header')).toContainText(originalEmail)
|
|
})
|
|
|
|
test('does not change email when confirm password is wrong', async ({
|
|
page,
|
|
request,
|
|
}) => {
|
|
const suffix = Date.now()
|
|
const originalEmail = `email-wrongpw-e2e-${suffix}@bilhej.se`
|
|
const tempEmail = `email-wrongpw-e2e-${suffix}-new@bilhej.se`
|
|
const password = 'password1234'
|
|
|
|
await clearMailpit(request)
|
|
await registerUser(request, originalEmail, password)
|
|
await loginViaUi(page, originalEmail, password)
|
|
|
|
await page.goto('/andra-epost')
|
|
await changeEmailViaUi(page, tempEmail, password)
|
|
|
|
const token = await waitForEmailChangeToken(request, tempEmail, {
|
|
publicBaseUrl: 'http://frontend',
|
|
})
|
|
|
|
await page.goto(`/bekrafta-epost?token=${token}`)
|
|
await page.locator('#password').fill('wrongpassword')
|
|
await page.getByRole('button', { name: 'Bekräfta ny e-postadress' }).click()
|
|
|
|
await expect(page.getByText('Lösenordet är felaktigt')).toBeVisible()
|
|
await expect(page.locator('header')).toContainText(originalEmail)
|
|
|
|
const login = await request.post('/api/auth/login', {
|
|
data: { email: originalEmail, password },
|
|
})
|
|
expect(login.ok()).toBeTruthy()
|
|
|
|
const loginWithNewEmail = await request.post('/api/auth/login', {
|
|
data: { email: tempEmail, password },
|
|
})
|
|
expect(loginWithNewEmail.ok()).toBeFalsy()
|
|
})
|
|
})
|
|
|
|
async function registerUser(
|
|
request: APIRequestContext,
|
|
email: string,
|
|
password: string,
|
|
) {
|
|
const response = await request.post('/api/auth/register', {
|
|
data: { email, password },
|
|
})
|
|
expect(response.ok()).toBeTruthy()
|
|
}
|
|
|
|
async function loginViaUi(page: Page, email: string, password: string) {
|
|
await page.goto('/logga-in')
|
|
await page.getByLabel('E-postadress').fill(email)
|
|
await page.getByLabel('Lösenord').fill(password)
|
|
await page.getByRole('button', { name: 'Logga in' }).click()
|
|
await expect(page).toHaveURL('/')
|
|
}
|
|
|
|
async function expectLoginFails(page: Page, email: string, password: string) {
|
|
await page.goto('/logga-in')
|
|
await page.getByLabel('E-postadress').fill(email)
|
|
await page.getByLabel('Lösenord').fill(password)
|
|
await page.getByRole('button', { name: 'Logga in' }).click()
|
|
await expect(page.getByText('Felaktig e-post eller lösenord')).toBeVisible()
|
|
}
|
|
|
|
async function logoutViaHeader(page: Page) {
|
|
await page.locator('header').getByRole('button', { name: 'Logga ut' }).click()
|
|
await expect(page).toHaveURL('/')
|
|
}
|
|
|
|
async function changePasswordViaUi(
|
|
page: Page,
|
|
currentPassword: string,
|
|
newPassword: string,
|
|
) {
|
|
await page.goto('/andra-losenord')
|
|
await page.locator('#current-password').fill(currentPassword)
|
|
await page.locator('#password').fill(newPassword)
|
|
await page.locator('#confirm-password').fill(newPassword)
|
|
await page.getByRole('button', { name: 'Spara nytt lösenord' }).click()
|
|
}
|
|
|
|
async function changeEmailViaUi(page: Page, newEmail: string, password: string) {
|
|
await page.locator('#new-email').fill(newEmail)
|
|
await page.locator('#password').fill(password)
|
|
await page.getByRole('button', { name: 'Spara ny e-postadress' }).click()
|
|
}
|
|
|
|
async function confirmEmailChangeViaUi(
|
|
page: Page,
|
|
token: string,
|
|
password: string,
|
|
) {
|
|
await page.goto(`/bekrafta-epost?token=${token}`)
|
|
await page.locator('#password').fill(password)
|
|
await page.getByRole('button', { name: 'Bekräfta ny e-postadress' }).click()
|
|
}
|