Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions js/twofactor_totp-main-challenge.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions js/twofactor_totp-main-challenge.js.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
SPDX-License-Identifier: AGPL-3.0-or-later
SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors


This file is generated from multiple sources. Included packages:
- nextcloud
- version: 1.0.0
- license: AGPL-3.0-or-later
1 change: 1 addition & 0 deletions js/twofactor_totp-main-challenge.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions js/twofactor_totp-main-challenge.js.map.license
28 changes: 28 additions & 0 deletions src/challenge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

/**
* Remove every non-digit character from a TOTP code
*
* @param {string} value the raw input value
* @return {string}
*/
export function sanitizeCode(value) {
return value.replace(/\D/g, '')
}

/**
* Keep only digits in the given input while the user types or pastes
*
* @param {HTMLInputElement} input the TOTP code input
*/
export function restrictToDigits(input) {
input.addEventListener('input', () => {
const sanitized = sanitizeCode(input.value)
if (sanitized !== input.value) {
input.value = sanitized
}
})
}
11 changes: 11 additions & 0 deletions src/main-challenge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { restrictToDigits } from './challenge.js'

const input = document.querySelector('.totp-form input[name="challenge"]')
if (input) {
restrictToDigits(input)
}
40 changes: 40 additions & 0 deletions src/tests/challenge.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { restrictToDigits, sanitizeCode } from '../challenge.js'

describe('sanitizeCode', () => {
it.each([
['123456', '123456'],
['123 456', '123456'],
['12-34-56', '123456'],
['abc', ''],
['', ''],
])('turns %p into %p', (value, expected) => {
expect(sanitizeCode(value)).to.equal(expected)
})
})

describe('restrictToDigits', () => {
it('strips non-digits on input', () => {
const input = document.createElement('input')
restrictToDigits(input)

input.value = '12a3 4'
input.dispatchEvent(new Event('input'))

expect(input.value).to.equal('1234')
})

it('leaves a digit-only value untouched', () => {
const input = document.createElement('input')
restrictToDigits(input)

input.value = '123456'
input.dispatchEvent(new Event('input'))

expect(input.value).to.equal('123456')
})
})
3 changes: 2 additions & 1 deletion templates/challenge.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
*/

style('twofactor_totp', 'style');
script('twofactor_totp', 'twofactor_totp-main-challenge');
?>

<img class="two-factor-icon two-factor-totp-icon" src="<?php print_unescaped(image_path('twofactor_totp', 'app.svg')); ?>" alt="">

<p><?php p($l->t('Get the authentication code from the two-factor authentication app on your device.')) ?></p>

<form method="POST" class="totp-form">
<input type="text" minlength="6" maxlength="10" name="challenge" required="required" autofocus autocomplete="one-time-code" inputmode="numeric" autocapitalize="off" placeholder="<?php p($l->t('Authentication code')) ?>">
<input class="two-factor-code-input two-factor-code-digits" type="text" minlength="6" maxlength="10" pattern="[0-9]{6,10}" name="challenge" required="required" autofocus autocomplete="one-time-code" inputmode="numeric" autocapitalize="off" placeholder="000000" aria-label="<?php p($l->t('Authentication code')) ?>">
<button class="primary two-factor-submit" type="submit">
<?php p($l->t('Submit')); ?>
</button>
Expand Down
1 change: 1 addition & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const isDev = process.env.NODE_ENV === 'development'
webpackConfig.entry = {
'main-settings': path.join(__dirname, 'src', 'main-settings.js'),
'main-login-setup': path.join(__dirname, 'src', 'main-login-setup.js'),
'main-challenge': path.join(__dirname, 'src', 'main-challenge.js'),
}

// Generate reuse license files if not in development mode
Expand Down
Loading