6A: Enterprise IdP CRUD, SAML callback, OIDC callback, email domain lookup 6B: Magic link send/verify (15min TTL, anti-enumeration), HIBP breach check 6D: 3 new E2E specs (account-linking, step-up, enterprise) — total 8 SmartAuth specs - All 53 auth tests passing
86 lines
2.8 KiB
TypeScript
86 lines
2.8 KiB
TypeScript
/**
|
|
* SmartAuth E2E — Step-Up Authentication
|
|
* Tests re-verification flows for sensitive operations.
|
|
*/
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('SmartAuth: Step-Up Auth', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.route('**/api/auth/me', route =>
|
|
route.fulfill({
|
|
status: 200,
|
|
body: JSON.stringify({
|
|
id: 'usr_test',
|
|
email: 'admin@acme.com',
|
|
role: 'admin',
|
|
displayName: 'Test Admin',
|
|
mfaEnabled: true,
|
|
mfaMethods: ['totp'],
|
|
}),
|
|
})
|
|
);
|
|
});
|
|
|
|
test('should require step-up for sensitive operations', async ({ page }) => {
|
|
// Mock a sensitive endpoint that returns 403 without step-up
|
|
await page.route('**/api/auth/mfa/totp', route => {
|
|
const method = route.request().method();
|
|
if (method === 'DELETE') {
|
|
route.fulfill({
|
|
status: 403,
|
|
body: JSON.stringify({ error: 'Step-up authentication required' }),
|
|
});
|
|
} else {
|
|
route.fulfill({ status: 200, body: '{}' });
|
|
}
|
|
});
|
|
await page.goto('/settings/security');
|
|
// Look for step-up prompt or re-verify dialog
|
|
const disableButton = page.getByRole('button', { name: /disable mfa/i });
|
|
if (await disableButton.isVisible()) {
|
|
await disableButton.click();
|
|
await expect(page.getByText(/step-up|re-verify|confirm your identity/i)).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('should complete step-up with password', async ({ page }) => {
|
|
await page.route('**/api/auth/step-up', route =>
|
|
route.fulfill({
|
|
status: 200,
|
|
body: JSON.stringify({ stepUpToken: 'step_test_token', expiresIn: 300 }),
|
|
})
|
|
);
|
|
await page.goto('/settings/security');
|
|
// Verify step-up flow returns token
|
|
const response = await page.evaluate(async () => {
|
|
const res = await fetch('/api/auth/step-up', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ method: 'password', credential: 'test123' }),
|
|
});
|
|
return res.json();
|
|
});
|
|
expect(response).toHaveProperty('stepUpToken');
|
|
});
|
|
|
|
test('should complete step-up with TOTP', async ({ page }) => {
|
|
await page.route('**/api/auth/step-up', route =>
|
|
route.fulfill({
|
|
status: 200,
|
|
body: JSON.stringify({ stepUpToken: 'step_totp_token', expiresIn: 300 }),
|
|
})
|
|
);
|
|
await page.goto('/settings/security');
|
|
const response = await page.evaluate(async () => {
|
|
const res = await fetch('/api/auth/step-up', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ method: 'totp', credential: '123456' }),
|
|
});
|
|
return res.json();
|
|
});
|
|
expect(response).toHaveProperty('stepUpToken');
|
|
});
|
|
});
|