2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-11-14 03:46:44 +00:00

[UI] MFA Refactor (#10775)

* Install otpauth package

* Add separate MFASettings components

* Refresh methods after registering token

* Simplify layout

* Add modal for deleting TOTP code

* Display recovery codes

* Adjust text

* Register webauthn

* Add longer timeouts

* Add workflow for removing webauthn

* Cleanup SecurityContext.tsx

* Add playwright testing for TOTP registration

* Spelling fixes

* Delete unused file

* Better clipboard copy
This commit is contained in:
Oliver
2025-11-05 22:54:47 +11:00
committed by GitHub
parent d12102ba96
commit 2dfe6b5f41
8 changed files with 1115 additions and 604 deletions

View File

@@ -73,6 +73,7 @@ export const test = baseTest.extend({
!url.includes('/api/user/token/') &&
!url.includes('/api/auth/v1/auth/login') &&
!url.includes('/api/auth/v1/auth/session') &&
!url.includes('/api/auth/v1/account/authenticators/totp') &&
!url.includes('/api/auth/v1/account/password/change') &&
!url.includes('/api/barcode/') &&
!url.includes('/favicon.ico') &&

View File

@@ -3,6 +3,8 @@ import { logoutUrl } from './defaults.js';
import { navigate } from './helpers.js';
import { doLogin } from './login.js';
import { TOTP } from 'otpauth';
/**
* Test various types of login failure
*/
@@ -84,3 +86,74 @@ test('Login - Change Password', async ({ page }) => {
await page.getByText('Password Changed').waitFor();
await page.getByText('The password was set successfully').waitFor();
});
// Tests for assigning MFA tokens to users
test('Login - MFA - TOTP', async ({ page }) => {
await doLogin(page, {
username: 'noaccess',
password: 'youshallnotpass'
});
await navigate(page, 'settings/user/security', { waitUntil: 'networkidle' });
// Expand the MFA section
await page
.getByRole('button', { name: 'Multi-Factor Authentication' })
.click();
await page.getByRole('button', { name: 'add-factor-totp' }).click();
// Ensure the user starts without any codes
await page
.getByText('No multi-factor tokens configured for this account')
.waitFor();
// Try to submit with an empty code
await page.getByRole('textbox', { name: 'text-input-otp-code' }).fill('');
await page.getByRole('button', { name: 'Submit', exact: true }).click();
await page.getByText('This field is required.').waitFor();
// Try to submit with an invalid secret
await page
.getByRole('textbox', { name: 'text-input-otp-code' })
.fill('ABCDEF');
await page.getByRole('button', { name: 'Submit', exact: true }).click();
await page.getByText('Incorrect code.').waitFor();
// Submit a valid code
const secret = await page
.getByLabel('otp-secret', { exact: true })
.innerText();
// Construct a TOTP code based on the secret
const totp = new TOTP({
secret: secret,
digits: 6,
period: 30
});
const token = totp.generate();
await page.getByRole('textbox', { name: 'text-input-otp-code' }).fill(token);
await page.getByRole('button', { name: 'Submit', exact: true }).click();
await page.getByText('TOTP token registered successfully').waitFor();
// View recovery codes
await page.getByRole('button', { name: 'view-recovery-codes' }).click();
await page
.getByText('The following one time recovery codes are available')
.waitFor();
await page.getByRole('button', { name: 'Close' }).click();
// Remove TOTP token
await page.getByRole('button', { name: 'remove-totp' }).click();
await page.getByRole('button', { name: 'Remove', exact: true }).click();
await page.getByText('TOTP token removed successfully').waitFor();
// And, once again there should be no configured tokens
await page
.getByText('No multi-factor tokens configured for this account')
.waitFor();
});