mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-10-05 17:29:04 +02:00
feat: add qr code alternative sign in method (#1594)
Co-authored-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>
This commit is contained in:
co-authored by
ItalyPaleAle
parent
968f97fa61
commit
e1fd1d320f
+2
-2
@@ -121,8 +121,8 @@ export const userGroups = {
|
||||
};
|
||||
|
||||
export const oneTimeAccessTokens = [
|
||||
{ token: 'HPe6k6uiDRRVuAQV', expired: false },
|
||||
{ token: 'YCGDtftvsvYWiXd0', expired: true }
|
||||
{ token: 'HPe6k6u1DRRVuAQV', expired: false },
|
||||
{ token: 'YCGDtftvsvYW1Xd0', expired: true }
|
||||
];
|
||||
|
||||
export const emailVerificationTokens = [
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
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(expectedLoginCodeText);
|
||||
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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user