Skip to content

Resolve mobile keyboard OTP autofill truncation in segmented inputs#10467

Open
AfraHussaindeen wants to merge 1 commit into
wso2:masterfrom
AfraHussaindeen:fix-6640-is-master
Open

Resolve mobile keyboard OTP autofill truncation in segmented inputs#10467
AfraHussaindeen wants to merge 1 commit into
wso2:masterfrom
AfraHussaindeen:fix-6640-is-master

Conversation

@AfraHussaindeen

@AfraHussaindeen AfraHussaindeen commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Problem

The following issues were reported on the OTP authentication screens:

  • Mobile keyboard OTP autofill truncates to single digit — When the OS autofill is used to enter the OTP code, only the first digit box with a single character is populated instead of
    filling all boxes.
  • Submit button remained disabled — After typing all digits without tapping the keyboard's return/enter key, the authenticate button stayed disabled and the user could not proceed.

Fix

An invisible overlay <input> is placed over the segmented digit box row. It carries autocomplete="one-time-code" so the OS autofill banner targets it correctly, and JavaScript relays its
value into the visual boxes in real time.

Key changes across totp.jsp, backup-code.jsp, smsOtp.jsp, and sms-and-email-otp.jsp:

  • Added a transparent full-width overlay input with autocomplete="one-time-code", autocorrect="off", autocapitalize="off", spellcheck="false"
  • handlePaste — intercepts paste events, strips whitespace, and populates the overlay input, triggering the input event chain
  • handleInput — syncs the overlay value to the hidden form field, updates digit boxes and cursor indicator, and enables/disables the submit button
  • Cursor blink animation on the active digit box to indicate focus position
  • Overlay CSS (.segmented-otp-field, .segmented-otp-input, div[id^="pincode-"], .active-pincode, @keyframes otpCursorBlink) added to modules/theme/src/theme-core/definitions/apps/login-portal.less, with !important on critical positioning/transparency rules to prevent branding stylesheet overrides.
  • For sms-and-email-otp.jsp, the form element now also carries the otp-form class so the segmented digit boxes don't wrap on mobile (mirrors the structure already present on
    backup-code.jsp and totp.jsp).

For sms_otp_verify.jsp, which uses a plain text input (no segmented boxes), autocomplete="one-time-code", autocorrect="off", autocapitalize="off", and spellcheck="false" were added to enable browser/OS autofill suggestions and suppress unwanted text correction on mobile. The dead movetoNext / handlePaste functions referencing non-existent pincode-* elements were removed.

For emailOtp.jsp, a stray c HTML attribute on the <input> (c size='30') was removed.

Behaviour Change

Field Before After
TOTP input Accepted alphanumeric characters Numeric only (enforced via keypress handler and inputmode="numeric")

The TOTP change aligns the input with RFC 6238, which defines TOTP codes as numeric. SMS OTP and backup codes remain unchanged in their character-set acceptance.

Before the fix

  • Backup code
backupcode.mp4
  • TOTP
totp.mp4
  • SMS OTP
smsotp.mp4
  • Legacy password recovery - Email OTP
legacy_password_recovery_emailotp.mp4
  • Legacy Password recovery - SMS OTP
legacy_password_recovery_smsotp.mp4

After the fix

  • Backup code
backupcode.mp4
  • TOTP
TOTP.mp4
  • SMS OTP (Numeric, OTP length <= 6)
SMSOTP.mp4
  • SMS OTP (Alpha Numeric, OTP length > 6)
SMSOTP_gt_6_alpha.mp4
  • SMS OTP (OTP length > 6)
SMSOTP_gt_6.mp4
  • Email OTP
EmailOTP.mp4
  • Legacy password recovery - Email OTP
leg_pswd_rec_emailotp.mp4
  • Legacy Password recovery - SMS OTP
leg-pswd-rec-sms.mp4

Related Issue

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

OTP entry screens in the authentication and recovery portals now use single-input segmented flows with synchronized hidden values, guarded submit handling, and centralized paste/sanitization logic. The login theme adds matching segmented OTP styles, and the email OTP field markup receives a small attribute fix.

Changes

Segmented OTP flow

Layer / File(s) Summary
Authentication portal OTP flow
identity-apps-core/apps/authentication-portal/src/main/webapp/backup-code.jsp, identity-apps-core/apps/authentication-portal/src/main/webapp/totp.jsp
backup-code.jsp and totp.jsp switch to page-local OTP length config, segmented rendering, hidden-field synchronization, and guarded submit handling.
OTP field attributes and verifier cleanup
identity-apps-core/apps/authentication-portal/src/main/webapp/emailOtp.jsp, identity-apps-core/apps/authentication-portal/src/main/webapp/sms_otp_verify.jsp
emailOtp.jsp corrects the OTP input size attribute, and sms_otp_verify.jsp adds OTP browser hints, removes legacy navigation and paste helpers, and updates the header year.
Recovery and SMS OTP rewrite
identity-apps-core/apps/authentication-portal/src/main/webapp/smsOtp.jsp, identity-apps-core/apps/recovery-portal/src/main/webapp/sms-and-email-otp.jsp
smsOtp.jsp and sms-and-email-otp.jsp replace per-digit OTP inputs with single-input segmented entry, disable submission until completion, and centralize input and paste handling.
Segmented OTP styles
modules/theme/src/theme-core/definitions/apps/login-portal.less
login-portal.less adds segmented OTP field styling for the overlay input, digit containers, active cursor, blink animation, and header year update.

Suggested reviewers

  • DonOmalVindula
  • JayaShakthi97
  • pavinduLakshan
  • savindi7

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Changeset Required ❌ Error No new .changeset/*.md file is present; the only .changeset markdown is .changeset/README.md. Add a .changeset/<name>.md entry covering the modified packages with the correct version bumps; .changeset/README.md does not count.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: fixing OTP autofill behavior in segmented inputs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description clearly explains the problem, fix, behavior changes, screenshots, and related issue, with only non-critical template sections missing.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • 🛠️ create changeset

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72.73%. Comparing base (05c202e) to head (564a7fe).
⚠️ Report is 100 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff            @@
##           master   #10467    +/-   ##
========================================
  Coverage   72.72%   72.73%            
========================================
  Files         469      469            
  Lines       70888    70908    +20     
  Branches      484      240   -244     
========================================
+ Hits        51555    51572    +17     
- Misses      19037    19225   +188     
+ Partials      296      111   -185     

see 187 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@identity-apps-core/apps/authentication-portal/src/main/webapp/backup-code.jsp`:
- Around line 283-297: The typed-input path in handleInput() is inconsistent
with handlePaste(): paste already enforces digits-only, but typing only removes
whitespace. Update handleInput() in backup-code.jsp so it sanitizes the entered
value to digits only before assigning to backupCodeField,
updateBackupDigitBoxes(), updateBackupCursor(), and submitBtn.disabled, keeping
the behavior aligned with handlePaste() and the OTP length check.

In `@identity-apps-core/apps/authentication-portal/src/main/webapp/smsOtp.jsp`:
- Around line 279-281: The segmented SMS OTP branch in smsOtp.jsp is now using a
native submit control for `#subButton`, which can bypass the reCAPTCHA callback
path. Update the reCaptchaEnabled && otpLength <= 6 flow so submission still
goes through sub() and the data-callback="sub" token flow, including Enter-key
submits from `#OTPCode`. Make the relevant submit control and any related form
handlers in the smsOtp.jsp branches at the affected sections consistent so no
native form submit can skip the captcha gate.

In
`@identity-apps-core/apps/recovery-portal/src/main/webapp/sms-and-email-otp.jsp`:
- Around line 409-423: The OTP input sanitization is inconsistent between paste
and typing: `handlePaste()` already restricts values to `[A-Za-z0-9_]`, but
`handleInput()` only strips whitespace, allowing invalid characters to be
entered and submitted. Update `handleInput()` in `sms-and-email-otp.jsp` to
apply the same character filter used in `handlePaste()` before calling
`updateDigitBoxes()`, `updateCursor()`, and toggling `submitBtn.disabled`, so
typed input matches the segmented OTP validation rules.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 3b9bbb58-5496-491b-b837-88d862b91230

📥 Commits

Reviewing files that changed from the base of the PR and between 0651514 and 564a7fe.

📒 Files selected for processing (7)
  • identity-apps-core/apps/authentication-portal/src/main/webapp/backup-code.jsp
  • identity-apps-core/apps/authentication-portal/src/main/webapp/emailOtp.jsp
  • identity-apps-core/apps/authentication-portal/src/main/webapp/smsOtp.jsp
  • identity-apps-core/apps/authentication-portal/src/main/webapp/sms_otp_verify.jsp
  • identity-apps-core/apps/authentication-portal/src/main/webapp/totp.jsp
  • identity-apps-core/apps/recovery-portal/src/main/webapp/sms-and-email-otp.jsp
  • modules/theme/src/theme-core/definitions/apps/login-portal.less

Comment on lines 283 to +297
function handlePaste(e) {
var clipboardData, value;
// Stop data actually being pasted into element
e.stopPropagation();
e.preventDefault();
// Get pasted data via clipboard API
clipboardData = e.clipboardData || window.clipboardData;
value = clipboardData.getData('Text');
const reg = new RegExp(/^\d+$/);
if (reg.test(value)) {
for (n = 0; n < 6; ++n) {
$("#pincode-" + (n+1)).val(value[n]);
$("#pincode-" + (n+1)).focus();
var pasted = (e.clipboardData || window.clipboardData).getData('Text').replace(/\s/g, '');
if (!/^\d+$/.test(pasted)) return;
backupInput.value = pasted.slice(0, <%= otpLength %>);
backupInput.dispatchEvent(new InputEvent('input', { bubbles: true }));
}

function handleInput() {
var sanitized = backupInput.value.replace(/\s/g, '');
if (sanitized !== backupInput.value) backupInput.value = sanitized;
backupCodeField.value = sanitized;
updateBackupDigitBoxes(sanitized);
updateBackupCursor(sanitized);
submitBtn.disabled = sanitized.length !== <%= otpLength %>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Match typed input sanitization to the digits-only paste path.

handlePaste() only accepts digits, but handleInput() only strips whitespace. That lets users type letters/punctuation, fill the boxes, and enable submit with values this UI is otherwise trying to reject.

Proposed fix
             function handleInput() {
-                var sanitized = backupInput.value.replace(/\s/g, '');
+                var sanitized = backupInput.value.replace(/\D/g, '');
                 if (sanitized !== backupInput.value) backupInput.value = sanitized;
                 backupCodeField.value = sanitized;
                 updateBackupDigitBoxes(sanitized);
                 updateBackupCursor(sanitized);
                 submitBtn.disabled = sanitized.length !== <%= otpLength %>;
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function handlePaste(e) {
var clipboardData, value;
// Stop data actually being pasted into element
e.stopPropagation();
e.preventDefault();
// Get pasted data via clipboard API
clipboardData = e.clipboardData || window.clipboardData;
value = clipboardData.getData('Text');
const reg = new RegExp(/^\d+$/);
if (reg.test(value)) {
for (n = 0; n < 6; ++n) {
$("#pincode-" + (n+1)).val(value[n]);
$("#pincode-" + (n+1)).focus();
var pasted = (e.clipboardData || window.clipboardData).getData('Text').replace(/\s/g, '');
if (!/^\d+$/.test(pasted)) return;
backupInput.value = pasted.slice(0, <%= otpLength %>);
backupInput.dispatchEvent(new InputEvent('input', { bubbles: true }));
}
function handleInput() {
var sanitized = backupInput.value.replace(/\s/g, '');
if (sanitized !== backupInput.value) backupInput.value = sanitized;
backupCodeField.value = sanitized;
updateBackupDigitBoxes(sanitized);
updateBackupCursor(sanitized);
submitBtn.disabled = sanitized.length !== <%= otpLength %>;
function handlePaste(e) {
e.preventDefault();
var pasted = (e.clipboardData || window.clipboardData).getData('Text').replace(/\s/g, '');
if (!/^\d+$/.test(pasted)) return;
backupInput.value = pasted.slice(0, <%= otpLength %>);
backupInput.dispatchEvent(new InputEvent('input', { bubbles: true }));
}
function handleInput() {
var sanitized = backupInput.value.replace(/\D/g, '');
if (sanitized !== backupInput.value) backupInput.value = sanitized;
backupCodeField.value = sanitized;
updateBackupDigitBoxes(sanitized);
updateBackupCursor(sanitized);
submitBtn.disabled = sanitized.length !== <%= otpLength %>;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@identity-apps-core/apps/authentication-portal/src/main/webapp/backup-code.jsp`
around lines 283 - 297, The typed-input path in handleInput() is inconsistent
with handlePaste(): paste already enforces digits-only, but typing only removes
whitespace. Update handleInput() in backup-code.jsp so it sanitizes the entered
value to digits only before assigning to backupCodeField,
updateBackupDigitBoxes(), updateBackupCursor(), and submitBtn.disabled, keeping
the behavior aligned with handlePaste() and the OTP length check.

Comment on lines +279 to +281
<input type="submit"
id="subButton"
disabled

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Avoid a native submit path when segmented SMS OTP is gated by reCAPTCHA.

In the reCaptchaEnabled && otpLength <= 6 branch, #subButton is now a submit control, but the captcha flow still depends on data-callback="sub". Pressing Enter in #OTPCode can submit the form without going through sub(), so the segmented flow can bypass the client-side captcha callback/token path.

Proposed fix
-                                            <input type="submit"
+                                            <input type="<%= reCaptchaEnabled ? "button" : "submit" %>"
                                                 id="subButton"
                                                 disabled
                                                 value="<%=AuthenticationEndpointUtil.i18n(resourceBundle, "authenticate")%>"
                                                 class="ui primary fluid large button" />

Also applies to: 327-337, 451-460

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@identity-apps-core/apps/authentication-portal/src/main/webapp/smsOtp.jsp`
around lines 279 - 281, The segmented SMS OTP branch in smsOtp.jsp is now using
a native submit control for `#subButton`, which can bypass the reCAPTCHA callback
path. Update the reCaptchaEnabled && otpLength <= 6 flow so submission still
goes through sub() and the data-callback="sub" token flow, including Enter-key
submits from `#OTPCode`. Make the relevant submit control and any related form
handlers in the smsOtp.jsp branches at the affected sections consistent so no
native form submit can skip the captcha gate.

Comment on lines +409 to 423
function handlePaste(e) {
e.preventDefault();
var pasted = (e.clipboardData || window.clipboardData).getData('Text').replace(/\s/g, '');
if (!/^[a-zA-Z0-9_]+$/.test(pasted)) return;
OTPInput.value = pasted.slice(0, otpLen);
OTPInput.dispatchEvent(new InputEvent('input', { bubbles: true }));
}

}

// Handle paste events
function handlePaste(e) {
var clipboardData, value;

// Stop data get being pasted into element
e.stopPropagation();
e.preventDefault();

// Get pasted data via clipboard API
clipboardData = e.clipboardData || window.clipboardData;
value = clipboardData.getData('Text');
const reg = new RegExp(/^[a-zA-Z0-9_]+$/);
if (reg.test(value)) {
for (n = 0; n < 6; ++n) {
$("#pincode-" + (n+1)).val(value[n]);
$("#pincode-" + (n+1)).focus();
}
function handleInput() {
var sanitized = OTPInput.value.replace(/\s/g, '');
if (sanitized !== OTPInput.value) OTPInput.value = sanitized;
updateDigitBoxes(sanitized);
updateCursor(sanitized);
if (submitBtn) submitBtn.disabled = sanitized.length !== otpLen;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the same character filter for typing that you already enforce for paste.

handlePaste() rejects anything outside [A-Za-z0-9_], but handleInput() only removes whitespace. Users can type punctuation, reach otpLen, and submit values the segmented UI otherwise treats as invalid.

Proposed fix
                 function handleInput() {
-                    var sanitized = OTPInput.value.replace(/\s/g, '');
+                    var sanitized = OTPInput.value.replace(/[^a-zA-Z0-9_]/g, '');
                     if (sanitized !== OTPInput.value) OTPInput.value = sanitized;
                     updateDigitBoxes(sanitized);
                     updateCursor(sanitized);
                     if (submitBtn) submitBtn.disabled = sanitized.length !== otpLen;
                 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function handlePaste(e) {
e.preventDefault();
var pasted = (e.clipboardData || window.clipboardData).getData('Text').replace(/\s/g, '');
if (!/^[a-zA-Z0-9_]+$/.test(pasted)) return;
OTPInput.value = pasted.slice(0, otpLen);
OTPInput.dispatchEvent(new InputEvent('input', { bubbles: true }));
}
}
// Handle paste events
function handlePaste(e) {
var clipboardData, value;
// Stop data get being pasted into element
e.stopPropagation();
e.preventDefault();
// Get pasted data via clipboard API
clipboardData = e.clipboardData || window.clipboardData;
value = clipboardData.getData('Text');
const reg = new RegExp(/^[a-zA-Z0-9_]+$/);
if (reg.test(value)) {
for (n = 0; n < 6; ++n) {
$("#pincode-" + (n+1)).val(value[n]);
$("#pincode-" + (n+1)).focus();
}
function handleInput() {
var sanitized = OTPInput.value.replace(/\s/g, '');
if (sanitized !== OTPInput.value) OTPInput.value = sanitized;
updateDigitBoxes(sanitized);
updateCursor(sanitized);
if (submitBtn) submitBtn.disabled = sanitized.length !== otpLen;
}
function handlePaste(e) {
e.preventDefault();
var pasted = (e.clipboardData || window.clipboardData).getData('Text').replace(/\s/g, '');
if (!/^[a-zA-Z0-9_]+$/.test(pasted)) return;
OTPInput.value = pasted.slice(0, otpLen);
OTPInput.dispatchEvent(new InputEvent('input', { bubbles: true }));
}
function handleInput() {
var sanitized = OTPInput.value.replace(/[^a-zA-Z0-9_]/g, '');
if (sanitized !== OTPInput.value) OTPInput.value = sanitized;
updateDigitBoxes(sanitized);
updateCursor(sanitized);
if (submitBtn) submitBtn.disabled = sanitized.length !== otpLen;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@identity-apps-core/apps/recovery-portal/src/main/webapp/sms-and-email-otp.jsp`
around lines 409 - 423, The OTP input sanitization is inconsistent between paste
and typing: `handlePaste()` already restricts values to `[A-Za-z0-9_]`, but
`handleInput()` only strips whitespace, allowing invalid characters to be
entered and submitted. Update `handleInput()` in `sms-and-email-otp.jsp` to
apply the same character filter used in `handlePaste()` before calling
`updateDigitBoxes()`, `updateCursor()`, and toggling `submitBtn.disabled`, so
typed input matches the segmented OTP validation rules.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant