mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-10-29 20:19:04 +01:00
121 lines
4.4 KiB
TypeScript
121 lines
4.4 KiB
TypeScript
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
|
||
};
|
||
}
|