feat: add qr code alternative sign in method (#1594)

Co-authored-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>
This commit is contained in:
Elias Schneider
2026-07-28 01:30:13 +02:00
committed by GitHub
co-authored by ItalyPaleAle
parent 968f97fa61
commit e1fd1d320f
38 changed files with 2215 additions and 73 deletions
+2 -2
View File
@@ -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 = [
+116
View File
@@ -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
};
}