Feature | Add Login UI MFA Flow#142
Conversation
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
📘 OpenAPI / Swagger preview ➡️ https://OpenStackweb.github.io/openstackid/openapi/pr-142/ This page is automatically updated on each push to this PR. |
33d34df to
fc5deae
Compare
|
📘 OpenAPI / Swagger preview ➡️ https://OpenStackweb.github.io/openstackid/openapi/pr-142/ This page is automatically updated on each push to this PR. |
fc5deae to
3481ac9
Compare
|
📘 OpenAPI / Swagger preview ➡️ https://OpenStackweb.github.io/openstackid/openapi/pr-142/ This page is automatically updated on each push to this PR. |
There was a problem hiding this comment.
Actionable comments posted: 16
🤖 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 `@resources/js/base_actions.js`:
- Around line 102-106: The issue is that params are unconditionally being added
to the URL query string via the url.query(params) call, which exposes sensitive
POST data like passwords, OTPs, and captchas in URLs and logs. Modify the code
to only add params to the query string for non-POST requests. Check the HTTP
method of the request before calling url.query(params) and only apply it when
the method is not POST, ensuring sensitive data in POST requests stays in the
request body and is never exposed in the URL.
In `@resources/js/login/components/email_error_actions.js`:
- Around line 22-42: The two Button components in this block are not respecting
the disableInput state, allowing users to trigger actions even when the UI
should be locked. Add the disabled prop set to disableInput on both buttons: the
button with onClick={emitOtpAction} and the button with
href={createAccountAction}. This will prevent users from clicking these recovery
action buttons when the input is disabled.
In `@resources/js/login/components/email_input_form.js`:
- Around line 51-55: Remove the use of dangerouslySetInnerHTML on the error
message element in the email error rendering section. Instead of using
dangerouslySetInnerHTML with the emailError property, render the emailError text
directly as a child of the paragraph element to prevent potential XSS
vulnerabilities. This applies to the conditional block where emailError is not
empty and the error paragraph is being displayed.
In `@resources/js/login/components/existing_account_actions.js`:
- Around line 34-37: The `Link` component with `disabled={disableInput}` prop
does not actually prevent navigation in MUI v4 when using the default anchor
element. To fix this, add an onClick handler to the Link component that prevents
the default navigation behavior when disableInput is true. The onClick handler
should call preventDefault() on the event whenever disableInput is true,
effectively blocking the navigation while the input is locked.
In `@resources/js/login/components/help_links.js`:
- Around line 18-21: The URL construction for forgotPasswordActionHref does not
account for existing query parameters in the forgotPasswordAction base URL. When
appending the email parameter, the code always uses `?email=...`, which will
create invalid URLs if forgotPasswordAction already contains a query string.
Modify the conditional block where forgotPasswordActionHref is assigned (when
userName exists) to check if forgotPasswordAction already contains a `?`
character. If it does, use `&` to append the email parameter; otherwise use `?`.
This ensures proper URL formatting regardless of whether the base URL already
has query parameters.
In `@resources/js/login/components/otp_input_form.js`:
- Around line 55-58: The error label paragraph element in the OTP input form is
using dangerouslySetInnerHTML to render the otpError variable, which creates an
XSS security vulnerability. Remove the dangerouslySetInnerHTML prop and instead
render the otpError directly as text content of the paragraph element so that
any HTML-like characters in the error message are displayed as plain text rather
than being interpreted as executable HTML.
- Line 1: Remove the useMemo hook to prevent stale captcha visibility state.
First, remove useMemo from the import statement at the top of the
otp_input_form.js file. Then, locate where useMemo is being used in the
component (likely wrapping captcha visibility logic) and unwrap it by removing
the useMemo function call while keeping the code it was wrapping. This ensures
the captcha visibility logic runs on every render and properly reflects the
current state of dependencies like loginAttempts, rather than relying on a stale
memoized result based only on the function reference.
- Line 49: The `hasErrored` prop being passed to the OtpInput component is not
supported in react-otp-input v3.1.1 and was removed in v3.0.0+. Remove the
`hasErrored={!otpError}` prop from the OtpInput component and instead use the
`renderInput` prop to handle error state styling. Pass the otpError state to the
custom input component through the `renderInput` prop so that the error styling
can be conditionally applied within the input component based on the parent
component's otpError state.
In `@resources/js/login/components/password_input_form.js`:
- Around line 85-88: The paragraph element with className styles.error_label is
rendering passwordError as HTML using dangerouslySetInnerHTML, which creates an
XSS vulnerability. Remove the dangerouslySetInnerHTML prop and instead render
passwordError directly as text content within the paragraph element to safely
display the error message without executing any embedded HTML or scripts.
In `@resources/js/login/components/recovery_code_form.js`:
- Around line 22-25: The handleBack function defined in the recovery_code_form
component is not connected to any UI element, so users cannot interact with it
to go back to the OTP screen. Add a button element in the form's JSX render that
calls the handleBack function on click. This button should be placed in a
logical location in the recovery form UI, likely near the form submission or as
a navigation element. Note that the same issue applies to the code around lines
65-72, so ensure both instances have the corresponding UI elements added to
enable the back navigation functionality.
- Around line 53-55: The recoveryError is being rendered as raw HTML using
dangerouslySetInnerHTML, which creates an XSS vulnerability if the error content
contains user-controlled or backend text. Replace the dangerouslySetInnerHTML
prop with direct text rendering by removing the
dangerouslySetInnerHTML={{__html: recoveryError}} attribute and instead render
recoveryError directly as plain text inside the paragraph element with the
error_label className. This will automatically escape any HTML special
characters and prevent potential XSS attacks.
In `@resources/js/login/components/two_factor_form.js`:
- Around line 91-93: The otpError variable is being rendered with
dangerouslySetInnerHTML which creates an XSS vulnerability since the value comes
from server responses. Remove the dangerouslySetInnerHTML attribute from the
error message paragraph element in the two_factor_form component and instead
render otpError as plain text content. If HTML formatting is required for the
error message, sanitize otpError using a proper HTML sanitization library before
using dangerouslySetInnerHTML.
In `@resources/js/login/login.js`:
- Around line 435-463: Add an early guard clause at the beginning of the
onVerify2FA method to prevent duplicate verification requests. Before processing
any validation logic or making the verify2FA API call, check if
this.state.disableInput is already true, and if so, return early from the
method. This will prevent rapid successive clicks or Enter key events from
triggering multiple concurrent API calls. Apply the same guard pattern to the
other verify handler mentioned in the comment (around lines 524-547).
- Around line 183-202: In the handleAuthenticateValidation method, the password
validation block that checks if user_password is empty is being executed
unconditionally for all auth flows. This causes the OTP flow validation to fail
even when a valid OTP code is provided, because the password check comes after
the OTP check and always requires a password. Wrap the password validation block
in a conditional statement that only executes when the auth flow is NOT
FLOW.OTP, so that OTP flow validation can complete successfully without
requiring a password field.
- Around line 220-241: The `default` case in the switch statement declares
`const redirect` without block scope, creating scope and TDZ issues. Wrap the
entire contents of the `default` case (from the `const redirect` declaration
through the closing of the if-else block that checks the redirect condition)
with curly braces to create proper block scope and isolate the variable
declaration from other switch cases.
In `@resources/views/auth/login.blade.php`:
- Around line 102-107: The line setting window.RESET_2FA_ENDPOINT is referencing
config.reset2faAction which is not defined in the template's configuration
object, causing the window variable to become undefined. Verify the template's
PHP configuration section where the config object is initialized and either add
the missing reset2faAction property with the appropriate endpoint URL, or
correct the reference to use the correct existing config key name that
corresponds to the reset 2FA endpoint.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: dd2029b0-3aab-49c1-b3e1-2abdc066795e
📒 Files selected for processing (17)
package.jsonresources/js/base_actions.jsresources/js/login/actions.jsresources/js/login/components/email_error_actions.jsresources/js/login/components/email_input_form.jsresources/js/login/components/existing_account_actions.jsresources/js/login/components/help_links.jsresources/js/login/components/otp_help_links.jsresources/js/login/components/otp_input_form.jsresources/js/login/components/password_input_form.jsresources/js/login/components/recovery_code_form.jsresources/js/login/components/third_party_identity_providers.jsresources/js/login/components/two_factor_form.jsresources/js/login/constants.jsresources/js/login/login.jsresources/js/login/login.module.scssresources/views/auth/login.blade.php
|
📘 OpenAPI / Swagger preview ➡️ https://OpenStackweb.github.io/openstackid/openapi/pr-142/ This page is automatically updated on each push to this PR. |
3 similar comments
|
📘 OpenAPI / Swagger preview ➡️ https://OpenStackweb.github.io/openstackid/openapi/pr-142/ This page is automatically updated on each push to this PR. |
|
📘 OpenAPI / Swagger preview ➡️ https://OpenStackweb.github.io/openstackid/openapi/pr-142/ This page is automatically updated on each push to this PR. |
|
📘 OpenAPI / Swagger preview ➡️ https://OpenStackweb.github.io/openstackid/openapi/pr-142/ This page is automatically updated on each push to this PR. |
c54e492 to
e0b8519
Compare
|
📘 OpenAPI / Swagger preview ➡️ https://OpenStackweb.github.io/openstackid/openapi/pr-142/ This page is automatically updated on each push to this PR. |
Task:
Ref: https://app.clickup.com/t/9014802374/86ba2zp3q
Changes
Preview
Overview
Frontend-only change adding a multi-factor authentication (MFA) flow to the React login UI. The monolithic
login.jswas refactored — inline form components were extracted into dedicated files, and two new MFA screens (verification code + recovery code) were added.Highlights
mfa_required, which transitions the UI into a two-factor email-OTP challenge instead of completing login.postRawRequestFullhelper captures the final URL after server-side redirects (xhr.responseURL) so the SPA can navigate the top window to the post-login destination.login.jswere moved intoresources/js/login/components/, slimming and reorganizing the main file.New Files
Actions & constants
resources/js/login/constants.js—HTTP_CODES,LOGIN_FLOW,MFA_METHODS,FLOW,MFA_ERROR_CODE, and defaults (OTP length 6, TTL 300, default methodemail_otp).Login form components (
resources/js/login/components/)two_factor_form.js— MFA email-OTP entry with expiry countdown, 30s resend cooldown, "Trust this device for 30 days", links to recovery code / cancel.recovery_code_form.js— recovery code entry screen with back-to-OTP and cancel.email_input_form.js— extracted email entry form.password_input_form.js— extracted password form (incl. failed-attempt / account-lock messaging).otp_input_form.js— extracted single-use code form.email_error_actions.js,existing_account_actions.js,help_links.js,otp_help_links.js,third_party_identity_providers.js— extracted helper/link components.Modified Files
resources/js/base_actions.js— addedpostRawRequestFull(returns{ response, status, finalUrl }).resources/js/login/actions.js— addedverify2FA,resend2FA,verifyRecoveryCode, andauthenticateWithPassword(uses the redirect-aware helper).resources/js/login/login.js— major refactor:mfaMethod,trustDevice,twoFactorCode,recoveryCode,otpLength,otpLifetime,codeVersion) and handlers (onVerify2FA,onResend2FA,onVerifyRecovery,onCancel2FA,onUseRecovery,onBackToOtp).authenticateWithPassword; onmfa_requiredit switches to the MFA flow, otherwise redirects viafinalUrl.showTwoFactorForm,showRecoveryForm,isPasswordFlow,isOtpFlow,showDefaultFlow).resources/js/login/login.module.scss— new styles:.info_message,.countdown,.trust_device_row,.disabled_link.resources/views/auth/login.blade.php— wired new endpoints/config:verify2faAction,resend2faAction,recovery2faAction,mfaMethod, plus session-drivenotpLength/otpLifetime; exposesVERIFY_2FA_ENDPOINT,RESEND_2FA_ENDPOINT,RECOVERY_2FA_ENDPOINT,FORM_ACTION_ENDPOINTonwindow.package.json— updatedservescript flag (--https→--server-type https) for the newer webpack-dev-server.User's GOAL
Current state
The login React component supports password and passwordless OTP flows. It has no MFA verification screen after password authentication.
Target state
The login UI supports a third flow state, '2fa'. When the backend returns mfa_required, the UI renders an MFA verification step with OTP input, trust-device checkbox, resend action, recovery-code link, and relevant error handling.
TASKS
( each component should have its own file )
ACCEPTANCE CRITERIA
DEVELOPMENT NOTES
Key files:
Gotchas:
Risks:
Out of scope:
Recovery-code profile management, device-management UI, method selector for future MFA methods.
Summary by CodeRabbit
New Features
Bug Fixes
Chores