Files
pocket-id/tests/specs/device-login.spec.ts
T

121 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test, type Browser } from '@playwright/test';
import { cleanupBackend } from '../utils/cleanup.util';
import passkeyUtil from '../utils/passkey.util';
test.beforeEach(async () => {
await cleanupBackend();
});
test('approves the QR link after requester review and fresh reauthentication', async ({
browser,
page
}) => {
const waiting = await openWaitingDevice(browser, '/settings/apps');
const expectedLoginCodeText = `${waiting.request.userCode.substring(0, 4)} - ${waiting.request.userCode.substring(4, 8)}`;
try {
await (await passkeyUtil.init(page)).addPasskey();
await expect(waiting.page.getByTestId('device-login-code')).toHaveText(
new RegExp(
`${waiting.request.userCode.substring(0, 4)}\\s*–\\s*${waiting.request.userCode.substring(4, 8)}`
)
);
await expect(waiting.page.getByLabel('Device login QR code')).toBeVisible();
await page.goto(waiting.request.verificationUriComplete);
await expect(page.getByText(expectedLoginCodeText)).toBeVisible();
await expect(page.getByText('Chrome', { exact: false })).toBeVisible();
const ipAddress = page.locator('dt', { hasText: 'IP Address' }).locator('..').locator('dd');
await expect(ipAddress).not.toHaveText('Unknown');
const decisionWithoutReauthentication = await page.request.post(
'/api/device-login/verification/decision',
{
data: { code: waiting.request.userCode, decision: 'approve' }
}
);
expect(decisionWithoutReauthentication.status()).toBe(401);
const reauthenticationRequest = page.waitForRequest('/api/webauthn/reauthenticate');
await page.getByRole('button', { name: 'Approve' }).click();
await reauthenticationRequest;
await expect(page.getByText('The requesting device has been signed in.')).toBeVisible();
await waiting.page.waitForURL('/settings/apps');
} finally {
await waiting.context.close();
}
});
test('authenticates a signed-out primary device before manual-code approval', async ({
browser
}) => {
const waiting = await openWaitingDevice(browser, '/settings/account');
const primaryContext = await browser.newContext({
baseURL: test.info().project.use.baseURL,
storageState: { cookies: [], origins: [] }
});
const primaryPage = await primaryContext.newPage();
try {
await (await passkeyUtil.init(primaryPage)).addPasskey();
await primaryPage.goto('/device');
await primaryPage.getByRole('textbox', { name: 'Code' }).fill(waiting.request.userCode);
await primaryPage.getByRole('button', { name: 'Authorize' }).click();
await primaryPage.getByRole('button', { name: 'Approve' }).click();
await expect(primaryPage.getByText('The requesting device has been signed in.')).toBeVisible();
await waiting.page.waitForURL('/settings/account');
} finally {
await primaryContext.close();
await waiting.context.close();
}
});
test('denies a pending request without passkey reauthentication', async ({ browser, page }) => {
const waiting = await openWaitingDevice(browser);
try {
let reauthenticationCalled = false;
await page.route('/api/webauthn/reauthenticate', async (route) => {
reauthenticationCalled = true;
await route.continue();
});
await page.goto(waiting.request.verificationUriComplete);
await page.getByRole('button', { name: 'Deny' }).click();
await expect(page.getByText('The sign-in request was denied.')).toBeVisible();
await expect(waiting.page.getByText('Device login request was denied')).toBeVisible();
expect(reauthenticationCalled).toBe(false);
} finally {
await waiting.context.close();
}
});
async function openWaitingDevice(browser: Browser, redirect = '/settings') {
const context = await browser.newContext({
baseURL: test.info().project.use.baseURL,
storageState: { cookies: [], origins: [] }
});
const page = await context.newPage();
const createResponse = page.waitForResponse(
(response) =>
response.request().method() === 'POST' &&
response.url().endsWith('/api/device-login/requests')
);
await page.goto(`/login/alternative?redirect=${encodeURIComponent(redirect)}`);
await expect(page.getByText('Sign in with another device', { exact: true })).toBeVisible();
await page.getByText('Sign in with another device', { exact: true }).click();
const response = await createResponse;
expect(response.status()).toBe(201);
const request = await response.json();
expect(request.userCode).toMatch(/^P[ABCDEFGHJKMNPQRSTUVWXYZ0123456789]{7}$/);
return {
context,
page,
request
};
}